Merged #story[1195]

main
Fan-Wu Yang 8 years ago
commit 2383915c78

1
.gitignore vendored

@ -183,5 +183,4 @@ local.properties
# IntelliJDEA ignore # IntelliJDEA ignore
*.iml *.iml
dedicatedServer/.idea/ dedicatedServer/.idea/
.idea/copyright/
settings/keyBindings.xml settings/keyBindings.xml

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

@ -18,3 +18,4 @@
Erika Savell <esa46@uclive.ac.nz> Erika Savell <esa46@uclive.ac.nz>
Connor Taylor-Brown <cbt24@cs17086jp.canterbury.ac.nz> <cbt24@uclive.canterbury.ac.nz> Connor Taylor-Brown <cbt24@cs17086jp.canterbury.ac.nz> <cbt24@uclive.canterbury.ac.nz>
Fraser Cope <fjc40@uclive.ac.nz> Fraser Cope <fjc40@uclive.ac.nz>
Jessica Syder <jam339@uclive.ac.nz> Jessica Syder <doctorjess@live.com>

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>team-7</artifactId>
<groupId>seng302</groupId>
<version>2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<name>matchBrowser</name>
<artifactId>matchBrowser</artifactId>
<version>2.0</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>seng302</groupId>
<artifactId>racevisionGame</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>app.Main</Main-Class>
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -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();
}
}

@ -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+"}";
}
}

@ -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<ClientAddress, HostGame> matchTable;
public MatchTable() {
this.matchTable = new HashMap<>();
}
public void addEntry(ClientAddress address, HostGame newEntry) {
this.matchTable.put(address, newEntry);
}
public HashMap<ClientAddress, HostGame> getMatchTable() {
return matchTable;
}
@Override
public String toString() {
return "MatchTable=" + matchTable;
}
}

@ -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 +
']';
}
}

@ -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<ClientAddress> 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<HostGame> games = new ArrayList<>();
for(Map.Entry<ClientAddress, HostGame> 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;
}
}
}
}

@ -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);
}
}

@ -10,6 +10,7 @@
<modules> <modules>
<module>racevisionGame</module> <module>racevisionGame</module>
<module>dedicatedServer</module> <module>dedicatedServer</module>
<module>matchBrowser</module>
</modules> </modules>
<url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url> <url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url>

@ -2,14 +2,17 @@ package mock.app;
import mock.dataInput.PolarParser; import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException; 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.commandFactory.CompositeCommand;
import mock.model.wind.RandomWindGenerator;
import mock.model.wind.ShiftingWindGenerator; import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator; import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator; import mock.xml.RaceXMLCreator;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import network.Messages.LatestMessages; import network.Messages.LatestMessages;
import org.xml.sax.SAXException;
import shared.dataInput.*; import shared.dataInput.*;
import shared.enums.XMLFileType; import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException; import shared.exceptions.InvalidBoatDataException;
@ -18,19 +21,12 @@ import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException; import shared.exceptions.XMLReaderException;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Constants; import shared.model.Constants;
import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException; import java.io.IOException;
import java.net.Inet4Address;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; 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. * Constructs an event, using various XML files.
* @param singlePlayer Whether or not to create a single player event. * @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. * @throws EventConstructionException Thrown if we cannot create an Event for any reason.
*/ */
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException { public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
@ -111,18 +108,23 @@ public class Event {
boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml"; boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
} }
double windAngle = 300;
//Read XML files. //Read XML files.
try { try {
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90); //this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8);
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
if(mapIndex==4){ 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.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, 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); throw new EventConstructionException("Could not read XML files.", e);
} }
@ -149,17 +151,26 @@ public class Event {
this.latestMessages = new LatestMessages(); this.latestMessages = new LatestMessages();
WindGenerator windGenerator = new ShiftingWindGenerator( WindGenerator windGenerator = new ShiftingWindGenerator(
Bearing.fromDegrees(225), Bearing.fromDegrees(windAngle),
12 12
); );
RaceLogic newRace = new RaceLogic(
new MockRace( MockRace mockRace = new MockRace(
boatDataSource, boatDataSource,
raceDataSource, raceDataSource,
regattaDataSource, regattaDataSource,
this.boatPolars, this.boatPolars,
Constants.RaceTimeScale, Constants.RaceTimeScale,
windGenerator ), windGenerator );
if(mapIndex==4){
mockRace.setRacePreStartTime(1000);
mockRace.setRacePreparatoryTime(5000);
}
RaceLogic newRace = new RaceLogic(
mockRace,
this.latestMessages, this.latestMessages,
this.compositeCommand); 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 * 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. * @param raceXML The race.xml contents.
@ -210,8 +220,6 @@ public class Event {
long millisecondsToAdd = racePreStartTime + racePreparatoryTime; long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000; 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"); DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now(); ZonedDateTime creationTime = ZonedDateTime.now();
@ -221,4 +229,14 @@ public class Event {
return raceXML; 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);
}
} }

@ -60,7 +60,6 @@ public class MockOutput implements RunnableWithFramePeriod {
try { try {
Thread.sleep(500); Thread.sleep(500);
} catch (InterruptedException e) { } catch (InterruptedException e) {
//If we get interrupted, exit the function. //If we get interrupted, exit the function.
Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e); 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(); long previousFrameTime = System.currentTimeMillis();

@ -34,7 +34,10 @@ public class MockBoat extends Boat {
*/ */
private boolean autoVMG = false; 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. * 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) { public void setAutoVMG(boolean autoVMG) {
this.autoVMG = autoVMG; this.autoVMG = autoVMG;
} }
public boolean isVelocityDefault() {
return velocityDefault;
}
public void setVelocityDefault(boolean velocityDefault) {
this.velocityDefault = velocityDefault;
}
} }

@ -1,18 +1,15 @@
package mock.model; package mock.model;
import mock.model.commandFactory.ActiveObserverCommand;
import mock.model.commandFactory.ObserverCommand;
import mock.model.wind.WindGenerator; import mock.model.wind.WindGenerator;
import javafx.animation.AnimationTimer;
import mock.model.collider.ColliderRegistry; 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.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.BoatDataSource; import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource; import shared.dataInput.RaceDataSource;
import shared.dataInput.RegattaDataSource; import shared.dataInput.RegattaDataSource;
import shared.exceptions.BoatNotFoundException; import shared.exceptions.BoatNotFoundException;
import shared.enums.RoundingType;
import shared.model.*; import shared.model.*;
import shared.model.Bearing; import shared.model.Bearing;
@ -64,7 +61,11 @@ public class MockRace extends RaceState {
*/ */
private Polars polars; 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. * 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.setRaceDataSource(raceDataSource);
this.setRegattaDataSource(regattaDataSource); this.setRegattaDataSource(regattaDataSource);
this.activeObserverCommand = new ActiveObserverCommand();
this.polars = polars; this.polars = polars;
this.scaleFactor = timeScale; this.scaleFactor = timeScale;
@ -119,6 +121,7 @@ public class MockRace extends RaceState {
//Construct a MockBoat using the Boat and Polars. //Construct a MockBoat using the Boat and Polars.
MockBoat mockBoat = new MockBoat(boat, polars); MockBoat mockBoat = new MockBoat(boat, polars);
mockBoat.setCurrentLeg(this.getLegs().get(0)); mockBoat.setCurrentLeg(this.getLegs().get(0));
mockBoat.setEstimatedTimeAtNextMark(this.getRaceClock().getCurrentTime());
//Update participant list. //Update participant list.
getRaceDataSource().getParticipants().add(sourceID); getRaceDataSource().getParticipants().add(sourceID);
@ -126,6 +129,7 @@ public class MockRace extends RaceState {
this.boats.add(mockBoat); this.boats.add(mockBoat);
getRaceDataSource().incrementSequenceNumber(); getRaceDataSource().incrementSequenceNumber();
} }
/** /**
@ -162,15 +166,15 @@ public class MockRace extends RaceState {
long timeToStart = - this.getRaceClock().getDurationMilli(); long timeToStart = - this.getRaceClock().getDurationMilli();
if (timeToStart > Constants.RacePreStartTime) { if (timeToStart > racePreStartTime) {
//Time > 3 minutes is the prestart period. //Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART); 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. //Time between [1, 3] minutes is the warning period.
this.setRaceStatusEnum(RaceStatusEnum.WARNING); 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. //Time between (0, 1] minutes is the preparatory period.
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY); 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. * 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. //Checks if the current boat has finished the race or not.
boolean finish = this.isLastLeg(boat.getCurrentLeg()); boolean finish = this.isLastLeg(boat.getCurrentLeg());
if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds && boat.isSailsOut()) { if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds) {
checkPosition(boat, totalElapsedMilliseconds);
if (boat.getCurrentSpeed() == 0) {
newOptimalVMG(boat);
boat.setBearing(boat.calculateBearingToNextMarker());
}
setBoatSpeed(boat); if(boat.isVelocityDefault()) setBoatSpeed(boat);
//Calculates the distance travelled, in meters, in the current timeslice. //Calculates the distance travelled, in meters, in the current timeslice.
double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds); double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds) * this.scaleFactor;
//Scale it.
distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;
checkPosition(boat, totalElapsedMilliseconds);
//Move the boat forwards that many meters, and advances its time counters by enough milliseconds. //Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
boat.moveForwards(distanceTravelledMeters); boat.moveForwards(distanceTravelledMeters);
boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds); boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds);
if (boat.getAutoVMG()) {
newOptimalVMG(boat);
boat.setAutoVMG(false);
}
} else {
boat.setCurrentSpeed(0);
} }
this.updateEstimatedTime(boat); 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) { 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( VMG vmg = new VMG(NewPolars.calculateSpeed(
this.getWindDirection(), this.getWindDirection(),
this.getWindSpeed(), this.getWindSpeed(),
@ -514,22 +479,28 @@ public class MockRace extends RaceState {
/** /**
* Checks to be run on boats rounding marks on the port side * Checks to be run on boats rounding marks on the port side
* @param boat the boat that is rounding a mark * @param boat the boat that is rounding a mark
* @param roundingChecks the checks to run * @param roundingData The data for the current leg's rounding.
* @param legBearing the direction of the leg
*/ */
private void boatRoundingCheckPort(MockBoat boat, List<GPSCoordinate> roundingChecks, Bearing legBearing) { private void boatRoundingCheckPort(MockBoat boat, MarkRoundingData roundingData) {
//boats must pass all checks in order to round a mark //boats must pass all checks in order to round a mark
//boolean for if boat has to/needs to pass through a gate //boolean for if boat has to/needs to pass through a gate
boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark()); boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark());
Mark roundingMark = boat.getCurrentLeg().getEndCompoundMark().getMarkForRounding(legBearing);
switch (boat.getRoundingStatus()) { switch (boat.getRoundingStatus()) {
case 0://hasn't started rounding case 0://hasn't started rounding
if (boat.isPortSide(roundingMark) && if (boat.isPortSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(roundingMark.getPosition(), GPSCoordinate.passesLine(
roundingChecks.get(0), boat.getPosition(), legBearing) && roundingData.getMarkToRound().getPosition(),
gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) { roundingData.getRoundCheck1(),
boat.getPosition(),
roundingData.getLegBearing()) &&
gateCheck &&
boat.isBetweenGate(
roundingData.getMarkToRound(),
Mark.tempMark(roundingData.getRoundCheck1())) ) {
boat.increaseRoundingStatus(); boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){ if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race //boat has finished race
@ -538,11 +509,18 @@ public class MockRace extends RaceState {
} }
break; break;
case 1://has been parallel to the mark; case 1://has been parallel to the mark;
if (boat.isPortSide(roundingMark) && if (boat.isPortSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(roundingMark.getPosition(), GPSCoordinate.passesLine(
roundingChecks.get(1), boat.getPosition(), roundingData.getMarkToRound().getPosition(),
Bearing.fromDegrees(legBearing.degrees() - 90)) &&//negative 90 from bearing because of port rounding roundingData.getRoundCheck2(),
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(1)))) { 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(); boat.increaseRoundingStatus();
} }
break; break;
@ -558,23 +536,27 @@ public class MockRace extends RaceState {
/** /**
* Checks to be run on boats rounding marks on the starboard side * Checks to be run on boats rounding marks on the starboard side
* @param boat the boat that is rounding a mark * @param boat the boat that is rounding a mark
* @param roundingChecks the checks to run * @param roundingData The data for the current leg's rounding.
* @param legBearing the direction of the leg
*/ */
private void boatRoundingCheckStarboard(MockBoat boat, List<GPSCoordinate> roundingChecks, Bearing legBearing){ private void boatRoundingCheckStarboard(MockBoat boat, MarkRoundingData roundingData){
//boats must pass all checks in order to round a mark //boats must pass all checks in order to round a mark
//boolean for if boat has to/needs to pass through a gate //boolean for if boat has to/needs to pass through a gate
boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark()); boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark());
Mark roundingMark = boat.getCurrentLeg().getEndCompoundMark().getMarkForRounding(legBearing);
switch (boat.getRoundingStatus()) { switch (boat.getRoundingStatus()) {
case 0://hasn't started rounding case 0://hasn't started rounding
if (boat.isStarboardSide(roundingMark) && if (boat.isStarboardSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(roundingMark.getPosition(), GPSCoordinate.passesLine(
roundingChecks.get(0), boat.getPosition(), legBearing) && roundingData.getMarkToRound().getPosition(),
roundingData.getRoundCheck1(),
boat.getPosition(),
roundingData.getLegBearing()) &&
gateCheck && gateCheck &&
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) { boat.isBetweenGate(
roundingData.getMarkToRound(),
Mark.tempMark(roundingData.getRoundCheck1()))) {
boat.increaseRoundingStatus(); boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){ if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race //boat has finished race
@ -583,10 +565,18 @@ public class MockRace extends RaceState {
} }
break; break;
case 1://has been parallel to the mark case 1://has been parallel to the mark
if (boat.isStarboardSide(roundingMark) && if (boat.isStarboardSide(roundingData.getMarkToRound()) &&
GPSCoordinate.passesLine(roundingMark.getPosition(), GPSCoordinate.passesLine(
roundingChecks.get(1), boat.getPosition(), Bearing.fromDegrees(legBearing.degrees() + 90)) && //positive 90 from bearing because of starboard rounding roundingData.getMarkToRound().getPosition(),
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(1)))) { 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(); boat.increaseRoundingStatus();
} }
break; break;
@ -605,55 +595,19 @@ public class MockRace extends RaceState {
* @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
*/ */
protected void checkPosition(MockBoat boat, long timeElapsed) { 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<GPSCoordinate> roundingChecks = new ArrayList<GPSCoordinate>(Arrays.asList(roundCheck1, roundCheck2));
switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) { switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {
case SP://Not yet implemented so these gates will be rounded port side case SP://Not yet implemented so these gates will be rounded port side
case Port: case Port:
boatRoundingCheckPort(boat, roundingChecks, bearingOfDirectionLine); boatRoundingCheckPort(
boat,
getMarkRoundingSequence().getRoundingData(boat.getCurrentLeg()) );
break; break;
case PS://not yet implemented so these gates will be rounded starboard side case PS://not yet implemented so these gates will be rounded starboard side
case Starboard: case Starboard:
boatRoundingCheckStarboard(boat, roundingChecks, bearingOfDirectionLine); boatRoundingCheckStarboard(
boat,
getMarkRoundingSequence().getRoundingData(boat.getCurrentLeg()) );
break; break;
} }
@ -669,8 +623,6 @@ public class MockRace extends RaceState {
} }
}
@ -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);
}
} }

@ -145,8 +145,6 @@ public class NewPolars {
* @return the best vmg that the boat can change to * @return the best vmg that the boat can change to
*/ */
public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){ public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
//System.out.println("VMG AUTO CALLED");
//speed
double closestSpeed = getClosest(trueWindSpeed, polars.keySet()); double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
double angle = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees()); 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){ public static double modulateAngle(double angle){
return (angle % 360 + 360) % 360; return (angle % 360 + 360) % 360;
} }
/**
* DO NOT DELETE THIS FUNCTIONS THEY ARE USED FOR TESTING PURPOSES
* @return current polars map
*/
@SuppressWarnings("unused")
private Map<Double, TreeMap<Double, Double>> getPolars(){ private Map<Double, TreeMap<Double, Double>> getPolars(){
//this function is just for testing so therefore it is private //this function is just for testing so therefore it is private
return polars; return polars;
} }
/**
* DO NOT DELETE THESE FUNCTIONS THEY ARE USED FOR TESTING PURPOSES
*/
@SuppressWarnings("unused")
private void printOutLinearInterpolated(){ private void printOutLinearInterpolated(){
for (double tws: polars.keySet()){ for (double tws: polars.keySet()){
System.out.println("=================================================="); System.out.println("==================================================");

@ -2,6 +2,7 @@ package mock.model;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import mock.model.collider.Collision; import mock.model.collider.Collision;
import mock.model.commandFactory.CollisionCommand;
import mock.model.commandFactory.Command; import mock.model.commandFactory.Command;
import mock.model.commandFactory.CompositeCommand; import mock.model.commandFactory.CompositeCommand;
import mock.model.commandFactory.CommandFactory; import mock.model.commandFactory.CommandFactory;
@ -148,9 +149,13 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
//Get the current time. //Get the current time.
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
//Execute commands from clients. // Execute commands from clients.
commands.execute(); commands.execute();
// Notify Observers
race.setChanged();
race.notifyObservers();
//Update race time. //Update race time.
race.updateRaceTime(currentTime); race.updateRaceTime(currentTime);
@ -234,11 +239,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
Collision e = (Collision)arg; if(arg instanceof Collision) {
Collision collision = (Collision)arg;
// if(e.getBearing().degrees() == 0) System.out.println("Ahead"); commands.addCommand(new CollisionCommand(race, (MockBoat)collision.getBoat()));
// 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");
} }
} }

@ -24,6 +24,7 @@ import java.util.logging.Logger;
public class RaceServer { public class RaceServer {
private MockRace race; private MockRace race;
private LatestMessages latestMessages; private LatestMessages latestMessages;
private static RaceServer server;
private List<YachtEvent> collisionEvents = new ArrayList<>(); private List<YachtEvent> collisionEvents = new ArrayList<>();
@ -49,10 +50,17 @@ public class RaceServer {
public RaceServer(MockRace race, LatestMessages latestMessages) { public RaceServer(MockRace race, LatestMessages latestMessages) {
server = this;
this.race = race; this.race = race;
this.latestMessages = latestMessages; this.latestMessages = latestMessages;
} }
public static void staticUpdateXML() {
if (server != null) {
server.updateXMLFiles();
}
}
/** /**
* Parses the race to create a snapshot, and places it in latestMessages. * 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. * Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations.
*/ */

@ -38,7 +38,7 @@ public class SourceIdAllocator {
if (!((mockRace.getRaceStatusEnum() == RaceStatusEnum.PRESTART) if (!((mockRace.getRaceStatusEnum() == RaceStatusEnum.PRESTART)
|| (mockRace.getRaceStatusEnum() == RaceStatusEnum.WARNING))) { || (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<Integer> allocatedIDs = mockRace.getRaceDataSource().getParticipants(); List<Integer> allocatedIDs = mockRace.getRaceDataSource().getParticipants();

@ -25,15 +25,9 @@ public abstract class Collider extends Observable implements Locatable {
Bearing relative = Bearing.fromDegrees(absolute.degrees() - boat.getBearing().degrees()); Bearing relative = Bearing.fromDegrees(absolute.degrees() - boat.getBearing().degrees());
if(actualDistance <= distance) { if(actualDistance <= distance) {
Collision collision = new Collision(relative, distance); Collision collision = new Collision(boat, relative, distance);
// Notify object of collision // Notify object of collision
onCollisionEnter(boat, collision); onCollisionEnter(collision);
// Notify observers of collision
notifyObservers(collision);
this.setChanged();
//Send out packet to all boats
return true; return true;
} else return false; } else return false;
} }
@ -47,8 +41,7 @@ public abstract class Collider extends Observable implements Locatable {
/** /**
* Handle a collision event * Handle a collision event
* @param collider Boat that is colliding
* @param e details of collision * @param e details of collision
*/ */
public abstract void onCollisionEnter(Boat collider, Collision e); public abstract void onCollisionEnter(Collision e);
} }

@ -1,5 +1,6 @@
package mock.model.collider; package mock.model.collider;
import mock.model.MockBoat;
import shared.model.Boat; import shared.model.Boat;
import shared.model.GPSCoordinate; import shared.model.GPSCoordinate;
@ -39,7 +40,7 @@ public class ColliderRegistry extends Collider implements Observer {
} }
@Override @Override
public void onCollisionEnter(Boat collider, Collision e) {} public void onCollisionEnter(Collision e) {}
@Override @Override
public GPSCoordinate getPosition() { public GPSCoordinate getPosition() {
@ -60,7 +61,7 @@ public class ColliderRegistry extends Collider implements Observer {
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
Collision collision = (Collision)arg; Collision collision = (Collision)arg;
notifyObservers(collision);
this.setChanged(); this.setChanged();
notifyObservers(collision);
} }
} }

@ -1,6 +1,7 @@
package mock.model.collider; package mock.model.collider;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Boat;
/** /**
* Data structure for holding collision details for ray casting and event handling. * 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 * Distance from boat centre to target centre
*/ */
private double distance; private double distance;
/**
* Boat involved in the collision
*/
private Boat boat;
/** /**
* Constructor for Collision structure * Constructor for Collision structure
* @param boat involved in collision
* @param bearing from boat heading to target * @param bearing from boat heading to target
* @param distance from boat centre to target centre * @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.bearing = bearing;
this.distance = distance; this.distance = distance;
} }
@ -32,4 +39,8 @@ public class Collision {
public double getDistance() { public double getDistance() {
return distance; return distance;
} }
public Boat getBoat() {
return boat;
}
} }

@ -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;
}
}

@ -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);
}
}
}

@ -3,6 +3,8 @@ package mock.model.commandFactory;
import mock.model.MockBoat; import mock.model.MockBoat;
import mock.model.MockRace; import mock.model.MockRace;
import java.util.Observer;
/** /**
* Allows RaceLogic to control MockRace state according to the Command pattern * Allows RaceLogic to control MockRace state according to the Command pattern
*/ */

@ -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);
}
}

@ -2,20 +2,50 @@ package mock.model.commandFactory;
import mock.model.MockBoat; import mock.model.MockBoat;
import mock.model.MockRace; import mock.model.MockRace;
import mock.model.NewPolars;
import mock.model.VMG;
public class SailsCommand implements Command { import java.util.Observable;
private MockRace race;
private MockBoat boat; public class SailsCommand extends ObserverCommand {
private boolean sailsOut; private boolean sailsOut;
private double goalVelocity;
public SailsCommand(MockRace race, MockBoat boat, Boolean sailsOut) { public SailsCommand(MockRace race, MockBoat boat, boolean sailsOut) {
this.race = race; super(race, boat);
this.boat = boat; race.addVelocityCommand(this);
this.sailsOut = sailsOut; this.sailsOut = sailsOut;
} }
@Override @Override
public void execute() { public void execute() {
this.boat.setSailsOut(this.sailsOut); 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);
}
} }
} }

@ -4,12 +4,16 @@ import mock.model.MockBoat;
import mock.model.MockRace; import mock.model.MockRace;
import shared.model.Bearing; import shared.model.Bearing;
import java.util.Observable;
/** /**
* Command class for tacking and gybing * Command class for tacking and gybing
*/ */
public class TackGybeCommand implements Command { public class TackGybeCommand extends ObserverCommand {
private MockRace race; private double goalRotation;
private MockBoat boat; private double totalRotation = 0;
private int direction; // -1 for anticlockwise, 1 for clockwise
private double goalAngle;
/** /**
* Constructor for class * Constructor for class
@ -17,24 +21,32 @@ public class TackGybeCommand implements Command {
* @param boat mock boat to update * @param boat mock boat to update
*/ */
public TackGybeCommand(MockRace race, MockBoat boat) { public TackGybeCommand(MockRace race, MockBoat boat) {
this.race = race; super(race, boat);
this.boat = boat; race.addAngularCommand(this);
} }
@Override @Override
public void execute() { public void execute() {
boat.setAutoVMG(false);
double boatAngle = boat.getBearing().degrees(); double boatAngle = boat.getBearing().degrees();
double windAngle =race.getWindDirection().degrees(); double windAngle = race.getWindDirection().degrees();
double differenceAngle = calcDistance(boatAngle, windAngle); double differenceAngle = calcDistance(boatAngle, windAngle);
double angleA = windAngle + differenceAngle; double angleA = windAngle + differenceAngle;
double angleB = windAngle - differenceAngle; double angleB = windAngle - differenceAngle;
if(angleA % 360 == boatAngle){ if (angleA % 360 == boatAngle) {
boat.setBearing(Bearing.fromDegrees(angleB)); goalAngle = angleB % 360;
} else { } 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; 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);
}
}
} }

@ -2,13 +2,20 @@ package mock.model.commandFactory;
import mock.model.MockBoat; import mock.model.MockBoat;
import mock.model.MockRace; import mock.model.MockRace;
import mock.model.NewPolars;
import mock.model.VMG;
import shared.model.Bearing;
import java.util.Observable;
/** /**
* Command class for autoVMG * Command class for autoVMG
*/ */
public class VMGCommand implements Command { public class VMGCommand extends ObserverCommand {
private MockRace race; private double goalAngle;
private MockBoat boat; private double goalRotation;
private double totalRotation = 0;
private int direction;
/** /**
* Constructor for class * Constructor for class
@ -16,8 +23,8 @@ public class VMGCommand implements Command {
* @param boat mock boat to update * @param boat mock boat to update
*/ */
public VMGCommand(MockRace race, MockBoat boat) { public VMGCommand(MockRace race, MockBoat boat) {
this.race = race; super(race, boat);
this.boat = boat; race.addAngularCommand(this);
} }
@Override @Override
@ -27,5 +34,37 @@ public class VMGCommand implements Command {
} else { } else {
boat.setAutoVMG(true); 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);
}
} }
} }

@ -4,17 +4,23 @@ import mock.model.MockBoat;
import mock.model.MockRace; import mock.model.MockRace;
import shared.model.Bearing; 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 { public class WindCommand extends ObserverCommand {
private MockRace race;
private MockBoat boat;
private int direction; 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) { public WindCommand(MockRace race, MockBoat boat, boolean upwind) {
this.race = race; super(race, boat);
this.boat = boat; race.addAngularCommand(this);
this.direction = upwind? -1 : 1; this.direction = upwind? -1 : 1;
} }
@ -34,4 +40,9 @@ public class WindCommand implements Command {
boat.setBearing(Bearing.fromDegrees(heading + offset)); boat.setBearing(Bearing.fromDegrees(heading + offset));
} }
@Override
public void update(Observable o, Object arg) {
race.deleteObserver(this);
}
} }

@ -105,7 +105,6 @@ public class ShiftingWindGenerator implements WindGenerator {
if (shiftedSoFar >= 180){ if (shiftedSoFar >= 180){
shiftAnticlockwise = Math.random() > 0.5; shiftAnticlockwise = Math.random() > 0.5;
shiftedSoFar = 0; shiftedSoFar = 0;
// System.out.println("Swapping");
} }
timeOfLastShift = System.currentTimeMillis(); timeOfLastShift = System.currentTimeMillis();

@ -1,34 +1,23 @@
package mock.xml; package mock.xml;
import org.w3c.dom.Document;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import shared.dataInput.RaceXMLReader; import shared.dataInput.RaceXMLReader;
import shared.enums.XMLFileType; import shared.enums.XMLFileType;
import shared.exceptions.InvalidRaceDataException; import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.XMLReaderException; import shared.exceptions.XMLReaderException;
import shared.model.*; import shared.model.CompoundMark;
import shared.xml.Race.*; 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 shared.xml.XMLUtilities;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; 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.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.IOException;
import java.io.StringWriter; import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -64,33 +53,54 @@ public class RaceXMLCreator {
/** /**
* Rotates the race in a specified direction. * 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 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 * @return the new xml file as a string
* @throws XMLReaderException if the xml is not readable * @throws XMLReaderException if the xml is not readable
* @throws InvalidRaceDataException if the race is invalid * @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 { public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException {
RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath);
RaceXMLReader reader = new RaceXMLReader(s, fileType);
try {
XMLRace race = XMLUtilities.xmlToClass( XMLRace race = XMLUtilities.xmlToClass(
RaceXMLCreator.class.getClassLoader().getResourceAsStream(s), s,
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
XMLRace.class); XMLRace.class);
if(tutorial){
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
} else {
setRaceXMLAtCurrentTimeToNow(race); setRaceXMLAtCurrentTimeToNow(race);
}
CompoundMark leewardGate = getLeewardGate(reader);
CompoundMark windwardGate = getWindwardGate(reader);
double raceOriginalBearing = 0;
double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position()); /*if (leewardGate != null && windwardGate != null) {
raceOriginalBearing = getLineAngle(
leewardGate.getMark1Position(),
windwardGate.getMark1Position() );
}*/
double degreesToRotate = degrees - raceOriginalBearing; double degreesToRotate = degrees - raceOriginalBearing;
if (degrees >= 0) {
alterRaceRotation(race, degreesToRotate); 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. //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; 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"); DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now(); ZonedDateTime creationTime = ZonedDateTime.now();
@ -198,4 +203,13 @@ public class RaceXMLCreator {
raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd))); 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);
}
} }

@ -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);
}
}
}

@ -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<HostGame> 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);
}
}
}

@ -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);
}
}
}

@ -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);
}
}
}

@ -39,6 +39,10 @@ public enum MessageType {
*/ */
ASSIGN_PLAYER_BOAT(121), ASSIGN_PLAYER_BOAT(121),
HOST_GAME(108),
HOSTED_GAMES_REQUEST(109),
NOTAMESSAGE(0); NOTAMESSAGE(0);

@ -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;
}
}

@ -0,0 +1,23 @@
package network.Messages;
import network.Messages.Enums.MessageType;
import java.util.List;
public class HostGamesRequest extends AC35Data{
private List<HostGame> knownGames;
/**
* Constructor
* @param knownGames games known by sender
*/
public HostGamesRequest(List knownGames) {
super(MessageType.HOSTED_GAMES_REQUEST);
this.knownGames = knownGames;
}
public List<HostGame> getKnownGames() {
return knownGames;
}
}

@ -1,5 +1,6 @@
package network.Messages; package network.Messages;
import mock.model.RaceServer;
import network.Messages.Enums.XMLMessageType; import network.Messages.Enums.XMLMessageType;
import java.util.*; import java.util.*;
@ -33,9 +34,6 @@ public class LatestMessages extends Observable {
private XMLMessage regattaXMLMessage; private XMLMessage regattaXMLMessage;
/** /**
* Ctor. * Ctor.
*/ */
@ -45,6 +43,7 @@ public class LatestMessages extends Observable {
/** /**
* Returns a copy of the race snapshot. * Returns a copy of the race snapshot.
*
* @return Copy of the race snapshot. * @return Copy of the race snapshot.
*/ */
public List<AC35Data> getSnapshot() { public List<AC35Data> getSnapshot() {
@ -54,6 +53,7 @@ public class LatestMessages extends Observable {
/** /**
* Sets the snapshot of the race. * Sets the snapshot of the race.
*
* @param snapshot New snapshot of race. * @param snapshot New snapshot of race.
*/ */
public void setSnapshot(List<AC35Data> snapshot) { public void setSnapshot(List<AC35Data> snapshot) {
@ -61,12 +61,9 @@ public class LatestMessages extends Observable {
} }
/** /**
* Returns the latest race xml message. * Returns the latest race xml message.
*
* @return The latest race xml message. * @return The latest race xml message.
*/ */
public XMLMessage getRaceXMLMessage() { public XMLMessage getRaceXMLMessage() {
@ -75,6 +72,7 @@ public class LatestMessages extends Observable {
/** /**
* Sets the latest race xml message to a specified race XML message. * Sets the latest race xml message to a specified race XML message.
*
* @param raceXMLMessage The new race XML message to use. * @param raceXMLMessage The new race XML message to use.
*/ */
public void setRaceXMLMessage(XMLMessage raceXMLMessage) { public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
@ -87,6 +85,7 @@ public class LatestMessages extends Observable {
/** /**
* Returns the latest boat xml message. * Returns the latest boat xml message.
*
* @return The latest boat xml message. * @return The latest boat xml message.
*/ */
public XMLMessage getBoatXMLMessage() { public XMLMessage getBoatXMLMessage() {
@ -95,6 +94,7 @@ public class LatestMessages extends Observable {
/** /**
* Sets the latest boat xml message to a specified boat XML message. * Sets the latest boat xml message to a specified boat XML message.
*
* @param boatXMLMessage The new boat XML message to use. * @param boatXMLMessage The new boat XML message to use.
*/ */
public void setBoatXMLMessage(XMLMessage boatXMLMessage) { public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
@ -107,6 +107,7 @@ public class LatestMessages extends Observable {
/** /**
* Returns the latest regatta xml message. * Returns the latest regatta xml message.
*
* @return The latest regatta xml message. * @return The latest regatta xml message.
*/ */
public XMLMessage getRegattaXMLMessage() { public XMLMessage getRegattaXMLMessage() {
@ -115,6 +116,7 @@ public class LatestMessages extends Observable {
/** /**
* Sets the latest regatta xml message to a specified regatta XML message. * Sets the latest regatta xml message to a specified regatta XML message.
*
* @param regattaXMLMessage The new regatta XML message to use. * @param regattaXMLMessage The new regatta XML message to use.
*/ */
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) { 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. * Checks the type of xml message, and places it in this LatestMessages object.
*
* @param xmlMessage The new xml message to use. * @param xmlMessage The new xml message to use.
*/ */
public void setXMLMessage(XMLMessage xmlMessage) { public void setXMLMessage(XMLMessage xmlMessage) {
@ -145,13 +148,16 @@ public class LatestMessages extends Observable {
/** /**
* Returns whether or not there is an xml message for each message type. * 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. * @return True if race, boat, and regatta have an xml message, false otherwise.
*/ */
public boolean hasAllXMLMessages() { 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; return false;
} else { } else {
RaceServer.staticUpdateXML();
return true; return true;
} }

@ -41,7 +41,6 @@ public class AC35DumpReader {
messLen[1] = dump[pointer + 13]; messLen[1] = dump[pointer + 13];
messLen[0] = dump[pointer + 14]; messLen[0] = dump[pointer + 14];
int messageLength = ByteBuffer.wrap(messLen).getShort(); int messageLength = ByteBuffer.wrap(messLen).getShort();
//System.out.println(messageLength);
packets.add(new AC35Packet(Arrays.copyOfRange(dump, pointer, pointer + messageLength + 19))); packets.add(new AC35Packet(Arrays.copyOfRange(dump, pointer, pointer + messageLength + 19)));

@ -403,22 +403,20 @@ public class Boat extends Collider {
public boolean isSailsOut() { public boolean isSailsOut() {
return sailsOut; return sailsOut;
} }
public void bounce(double repulsionRadius) {
Azimuth reverseAzimuth = Azimuth.fromDegrees(getBearing().degrees() - 180d);
setPosition(GPSCoordinate.calculateNewPosition(getPosition(), 2 * repulsionRadius, reverseAzimuth));
}
@Override @Override
public boolean rayCast(Boat boat) { public boolean rayCast(Boat boat) {
if(boat != this) { if(boat != this) {
return rayCast(boat, 15); return rayCast(boat, 100);
} else return false; } else return false;
} }
@Override @Override
public void onCollisionEnter(Boat collider, Collision e) { public void onCollisionEnter(Collision e) {
if(e.getBearing().degrees() > 270 || e.getBearing().degrees() < 90) { if(e.getBearing().degrees() > 270 || e.getBearing().degrees() < 90) {
collider.bounce(15); // Notify observers of collision
this.setChanged();
notifyObservers(e);
} }
} }
} }

@ -187,7 +187,7 @@ public class CompoundMark extends XMLCompoundMark{
} }
//finds the mark furthest west and east //finds the mark furthest west and east
if(this.getMark1Position().getLongitude() > this.getMark2Position().getLongitude()){ if(this.getMark1Position().getLongitude() < this.getMark2Position().getLongitude()){
westMostMark = this.mark1; westMostMark = this.mark1;
eastMostMark = this.mark2; eastMostMark = this.mark2;
}else{ }else{

@ -5,7 +5,6 @@ package shared.model;
* Created by Erika on 19-Mar-17. * Created by Erika on 19-Mar-17.
*/ */
public class Constants { public class Constants {
/** /**
* Multiply by this factor to convert nautical miles to meters. * Multiply by this factor to convert nautical miles to meters.
* <br> * <br>
@ -15,8 +14,6 @@ public class Constants {
*/ */
public static final int NMToMetersConversion = 1852; public static final int NMToMetersConversion = 1852;
/** /**
* Multiply by this factor to convert Knots to millimeters per second. * Multiply by this factor to convert Knots to millimeters per second.
* <br> * <br>
@ -26,33 +23,24 @@ public class Constants {
*/ */
public static final double KnotsToMMPerSecond = 514.444; public static final double KnotsToMMPerSecond = 514.444;
/** /**
* The scale factor of the race. * The scale factor of the race.
* Frame periods are multiplied by this to get the amount of time a single frame represents. * 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. * 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 = 30 * 1000;
public static final long RacePreStartTime = 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 = 10 * 1000;
public static final long RacePreparatoryTime = 1 * 60 * 1000;
/** /**
* The number of milliseconds in one hour. * The number of milliseconds in one hour.
* <br> * <br>
@ -70,7 +58,4 @@ public class Constants {
* Divide by this factor to convert hours to seconds. * Divide by this factor to convert hours to seconds.
*/ */
public static long OneHourSeconds = 1 * 60 * 60; public static long OneHourSeconds = 1 * 60 * 60;
} }

@ -29,6 +29,11 @@ public class Mark extends Collider{
*/ */
private GPSCoordinate position; private GPSCoordinate position;
/**
* Repulsion radius of the mark
*/
private double repulsionRadius = 50;
/** /**
* Constructs a mark with a given source ID, name, and position. * Constructs a mark with a given source ID, name, and position.
* @param sourceID The source ID of the mark. * @param sourceID The source ID of the mark.
@ -92,11 +97,12 @@ public class Mark extends Collider{
@Override @Override
public boolean rayCast(Boat boat) { public boolean rayCast(Boat boat) {
return rayCast(boat, 15); return rayCast(boat, repulsionRadius);
} }
@Override @Override
public void onCollisionEnter(Boat collider, Collision e) { public void onCollisionEnter(Collision e) {
collider.bounce(15); this.setChanged();
notifyObservers(e);
} }
} }

@ -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;
}
}

@ -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<Leg> legs;
/**
* For each leg, mark rounding information.
*/
private Map<Leg, MarkRoundingData> roundingPoints;
public MarkRoundingSequence(List<Leg> 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<Leg> 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<Leg> 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);
}
}
}

@ -3,6 +3,7 @@ package shared.model;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import visualiser.Controllers.RaceStartController;
import visualiser.model.ResizableRaceCanvas; import visualiser.model.ResizableRaceCanvas;
import java.time.Duration; 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 * This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the * displays times relevant to a race. This is displayed on the
* {@link ResizableRaceCanvas} via the * {@link ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController} and the * {@link visualiser.Controllers.RaceViewController} and the
* {@link visualiser.Controllers.StartController}. * {@link RaceStartController}.
*/ */
public class RaceClock { public class RaceClock {

@ -13,6 +13,7 @@ import shared.dataInput.RegattaDataSource;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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}. * 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}. * 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<Leg> legs; private ObservableList<Leg> 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. * 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<Leg> legs) { protected void useLegsList(List<Leg> legs) {
this.legs.setAll(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. //We add a "dummy" leg at the end of the race.
if (getLegs().size() > 0) { if (getLegs().size() > 0) {
getLegs().add(new Leg("Finish", getLegs().size())); 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;
}
} }

@ -50,7 +50,7 @@ import javax.xml.bind.annotation.XmlType;
}) })
public class XMLParticipants { public class XMLParticipants {
@XmlElement(name = "Yacht", required = true) @XmlElement(name = "Yacht", required = false)
protected List<XMLYacht> yacht; protected List<XMLYacht> yacht;
/** /**

@ -49,7 +49,7 @@ public class XMLUtilities {
return xmlToClass(document, schemaURL, c); return xmlToClass(document, schemaURL, c);
} }
public static Object xmlToClass(String xml, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { public static <T> T xmlToClass(String xml, URL schemaURL, Class<T> c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = parser.parse(new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8")))); 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 c The XML class to convert to.
* @param <T> The XML class to convert to. * @param <T> The XML class to convert to.
* @return The XML class object. * @return The XML class object.
* @throws ParserConfigurationException * @throws ParserConfigurationException Thrown if input cannot be converted to class.
* @throws IOException * @throws IOException Thrown if input cannot be converted to class.
* @throws SAXException * @throws SAXException Thrown if input cannot be converted to class.
* @throws JAXBException * @throws JAXBException Thrown if input cannot be converted to class.
*/ */
public static <T> T xmlToClass(InputStream i, URL schemaURL, Class<T> c) throws ParserConfigurationException, IOException, SAXException, JAXBException { public static <T> T xmlToClass(InputStream i, URL schemaURL, Class<T> c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();

@ -184,7 +184,7 @@ public class BoatConfig {
* Objects of the following type(s) are allowed in the list * Objects of the following type(s) are allowed in the list
* {@link BoatConfig.Boats.Boat } * {@link BoatConfig.Boats.Boat }
* *
* * @return List of Boat entries.
*/ */
public List<BoatConfig.Boats.Boat> getBoat() { public List<BoatConfig.Boats.Boat> getBoat() {
if (boat == null) { if (boat == null) {
@ -327,7 +327,7 @@ public class BoatConfig {
/** /**
* Gets the value of the sourceID property. * Gets the value of the sourceID property.
* * @return source id.
*/ */
public int getSourceID() { public int getSourceID() {
return sourceID; return sourceID;
@ -335,7 +335,7 @@ public class BoatConfig {
/** /**
* Sets the value of the sourceID property. * Sets the value of the sourceID property.
* * @param value new source id.
*/ */
public void setSourceID(int value) { public void setSourceID(int value) {
this.sourceID = value; this.sourceID = value;
@ -494,7 +494,7 @@ public class BoatConfig {
/** /**
* Gets the value of the y property. * Gets the value of the y property.
* * @return Y value.
*/ */
public double getY() { public double getY() {
return y; return y;
@ -502,7 +502,7 @@ public class BoatConfig {
/** /**
* Sets the value of the y property. * Sets the value of the y property.
* * @param value new y value.
*/ */
public void setY(double value) { public void setY(double value) {
this.y = value; this.y = value;
@ -510,7 +510,7 @@ public class BoatConfig {
/** /**
* Gets the value of the z property. * Gets the value of the z property.
* * @return z value.
*/ */
public double getZ() { public double getZ() {
return z; return z;
@ -518,7 +518,7 @@ public class BoatConfig {
/** /**
* Sets the value of the z property. * Sets the value of the z property.
* * @param value new z value.
*/ */
public void setZ(double value) { public void setZ(double value) {
this.z = value; this.z = value;

@ -38,7 +38,7 @@ public class ObjectFactory {
/** /**
* Create an instance of {@link BoatConfig } * Create an instance of {@link BoatConfig }
* * @return BoatConfig.
*/ */
public BoatConfig createBoatConfig() { public BoatConfig createBoatConfig() {
return new BoatConfig(); return new BoatConfig();
@ -46,7 +46,7 @@ public class ObjectFactory {
/** /**
* Create an instance of {@link BoatConfig.Boats } * Create an instance of {@link BoatConfig.Boats }
* * @return Boats.
*/ */
public BoatConfig.Boats createBoatConfigBoats() { public BoatConfig.Boats createBoatConfigBoats() {
return new BoatConfig.Boats(); return new BoatConfig.Boats();
@ -54,7 +54,7 @@ public class ObjectFactory {
/** /**
* Create an instance of {@link BoatConfig.Boats.Boat } * Create an instance of {@link BoatConfig.Boats.Boat }
* * @return Boat.
*/ */
public BoatConfig.Boats.Boat createBoatConfigBoatsBoat() { public BoatConfig.Boats.Boat createBoatConfigBoatsBoat() {
return new BoatConfig.Boats.Boat(); return new BoatConfig.Boats.Boat();
@ -62,7 +62,7 @@ public class ObjectFactory {
/** /**
* Create an instance of {@link BoatConfig.Boats.Boat.GPSposition } * Create an instance of {@link BoatConfig.Boats.Boat.GPSposition }
* * @return GPSposition.
*/ */
public BoatConfig.Boats.Boat.GPSposition createBoatConfigBoatsBoatGPSposition() { public BoatConfig.Boats.Boat.GPSposition createBoatConfigBoatsBoatGPSposition() {
return new BoatConfig.Boats.Boat.GPSposition(); return new BoatConfig.Boats.Boat.GPSposition();

@ -38,7 +38,7 @@ public class ObjectFactory {
/** /**
* Create an instance of {@link RegattaConfig } * Create an instance of {@link RegattaConfig }
* * @return RegattaConfig.
*/ */
public RegattaConfig createRegattaConfig() { public RegattaConfig createRegattaConfig() {
return new RegattaConfig(); return new RegattaConfig();

@ -74,7 +74,7 @@ public class RegattaConfig {
/** /**
* Gets the value of the regattaID property. * Gets the value of the regattaID property.
* * @return regatta id.
*/ */
public int getRegattaID() { public int getRegattaID() {
return regattaID; return regattaID;
@ -82,7 +82,7 @@ public class RegattaConfig {
/** /**
* Sets the value of the regattaID property. * Sets the value of the regattaID property.
* * @param value new regatta id.
*/ */
public void setRegattaID(int value) { public void setRegattaID(int value) {
this.regattaID = value; this.regattaID = value;
@ -138,7 +138,7 @@ public class RegattaConfig {
/** /**
* Gets the value of the centralLatitude property. * Gets the value of the centralLatitude property.
* * @return central latitude.
*/ */
public double getCentralLatitude() { public double getCentralLatitude() {
return centralLatitude; return centralLatitude;
@ -146,7 +146,7 @@ public class RegattaConfig {
/** /**
* Sets the value of the centralLatitude property. * Sets the value of the centralLatitude property.
* * @param value new central latitude.
*/ */
public void setCentralLatitude(double value) { public void setCentralLatitude(double value) {
this.centralLatitude = value; this.centralLatitude = value;
@ -154,7 +154,7 @@ public class RegattaConfig {
/** /**
* Gets the value of the centralLongitude property. * Gets the value of the centralLongitude property.
* * @return central longitude.
*/ */
public double getCentralLongitude() { public double getCentralLongitude() {
return centralLongitude; return centralLongitude;
@ -162,7 +162,7 @@ public class RegattaConfig {
/** /**
* Sets the value of the centralLongitude property. * Sets the value of the centralLongitude property.
* * @param value new central longitude.
*/ */
public void setCentralLongitude(double value) { public void setCentralLongitude(double value) {
this.centralLongitude = value; this.centralLongitude = value;
@ -170,7 +170,7 @@ public class RegattaConfig {
/** /**
* Gets the value of the centralAltitude property. * Gets the value of the centralAltitude property.
* * @return central altitude.
*/ */
public double getCentralAltitude() { public double getCentralAltitude() {
return centralAltitude; return centralAltitude;
@ -178,7 +178,7 @@ public class RegattaConfig {
/** /**
* Sets the value of the centralAltitude property. * Sets the value of the centralAltitude property.
* * @param value new central altitude.
*/ */
public void setCentralAltitude(double value) { public void setCentralAltitude(double value) {
this.centralAltitude = value; this.centralAltitude = value;
@ -186,7 +186,7 @@ public class RegattaConfig {
/** /**
* Gets the value of the utcOffset property. * Gets the value of the utcOffset property.
* * @return utc offset.
*/ */
public double getUtcOffset() { public double getUtcOffset() {
return utcOffset; return utcOffset;
@ -194,7 +194,7 @@ public class RegattaConfig {
/** /**
* Sets the value of the utcOffset property. * Sets the value of the utcOffset property.
* * @param value new utc offset.
*/ */
public void setUtcOffset(double value) { public void setUtcOffset(double value) {
this.utcOffset = value; this.utcOffset = value;
@ -202,7 +202,7 @@ public class RegattaConfig {
/** /**
* Gets the value of the magneticVariation property. * Gets the value of the magneticVariation property.
* * @return magnetic variation.
*/ */
public double getMagneticVariation() { public double getMagneticVariation() {
return magneticVariation; return magneticVariation;
@ -210,7 +210,7 @@ public class RegattaConfig {
/** /**
* Sets the value of the magneticVariation property. * Sets the value of the magneticVariation property.
* * @param value new magnetic variation.
*/ */
public void setMagneticVariation(double value) { public void setMagneticVariation(double value) {
this.magneticVariation = value; this.magneticVariation = value;

@ -3,6 +3,8 @@ package visualiser.Commands.VisualiserRaceCommands;
import javafx.scene.media.AudioClip; import javafx.scene.media.AudioClip;
import mock.model.commandFactory.Command; import mock.model.commandFactory.Command;
import network.Messages.YachtEvent; import network.Messages.YachtEvent;
import shared.exceptions.BoatNotFoundException;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceState; import visualiser.model.VisualiserRaceState;
/** /**
@ -34,6 +36,11 @@ public class BoatCollisionCommand implements Command {
sound.play(); sound.play();
} }
//System.out.println("Collision command executed!"); try {
VisualiserBoat boat = visualiserRace.getBoat(yachtEvent.getSourceID());
boat.setHasCollided(true);
} catch (BoatNotFoundException e) {
e.printStackTrace();
}
} }
} }

@ -1,65 +1,31 @@
package visualiser.Controllers; package visualiser.Controllers;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Wind; import shared.model.Wind;
/** /**
* Controller for the arrow.fxml view. * Controller for the wind direction arrow on the race screen.
*/ */
public class ArrowController { public class ArrowController {
private @FXML StackPane arrowStackPane;
private @FXML ImageView arrowImage;
@FXML private @FXML Label speedLabel;
private Pane compass; private final static Integer MIN_KNOTS = 2; // knots for min_height
private final static Integer MAX_KNOTS = 30; // knots for max_height
@FXML private final static Integer MIN_HEIGHT = 25; // min arrow height
private StackPane arrowStackPane; private final static Integer MAX_HEIGHT = 75; // max arrow height
@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> wind;
/**
* Constructor.
*/
public ArrowController() {
}
/** /**
* Sets which wind property the arrow control should bind to. * Sets which wind property the arrow control should bind to.
* @param wind The wind property to bind to. * @param wind The wind property to bind to.
*/ */
public void setWindProperty(Property<Wind> wind) { public void setWindProperty(Property<Wind> wind) {
this.wind = wind;
wind.addListener((observable, oldValue, newValue) -> { wind.addListener((observable, oldValue, newValue) -> {
if (newValue != null) { if (newValue != null) {
Platform.runLater(() -> updateWind(newValue)); Platform.runLater(() -> updateWind(newValue));
@ -67,7 +33,6 @@ public class ArrowController {
}); });
} }
/** /**
* Updates the control to use the new wind value. * 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). * 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()); updateWindSpeed(wind.getWindSpeed());
} }
/** /**
* Updates the control to account for the new wind speed. * Updates the control to account for the new wind speed.
* This changes the length (height) of the wind arrow, and updates the speed label. * 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. * @param speedKnots Wind speed, in knots.
*/ */
private void updateWindArrowLength(double speedKnots) { private void updateWindArrowLength(double speedKnots) {
double deltaKnots = MAX_KNOTS - MIN_KNOTS;
//At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height. double deltaHeight = MAX_HEIGHT - MIN_HEIGHT;
double minKnots = 2;
double maxKnots = 30;
double deltaKnots = maxKnots - minKnots;
double minHeight = 25;
double maxHeight = 75;
double deltaHeight = maxHeight - minHeight;
//Clamp speed. //Clamp speed.
if (speedKnots > maxKnots) { if (speedKnots > MAX_KNOTS) {
speedKnots = maxKnots; speedKnots = MAX_KNOTS;
} else if (speedKnots < minKnots) { } else if (speedKnots < MIN_KNOTS) {
speedKnots = minKnots; speedKnots = MIN_KNOTS;
} }
//How far between the knots bounds is the current speed? //How far between the knots bounds is the current speed?
double currentDeltaKnots = speedKnots - minKnots; double currentDeltaKnots = speedKnots - MIN_KNOTS;
double currentKnotsScalar = currentDeltaKnots / deltaKnots; double currentKnotsScalar = currentDeltaKnots / deltaKnots;
//Thus, how far between the pixel height bounds should the arrow height be? //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); arrowImage.setFitHeight(newHeight);
} }
@ -129,7 +86,6 @@ public class ArrowController {
speedLabel.setText(String.format("%.1fkn", speedKnots)); speedLabel.setText(String.format("%.1fkn", speedKnots));
} }
/** /**
* Updates the control to account for a new wind bearing. * Updates the control to account for a new wind bearing.
* This rotates the arrow according to the bearing. * This rotates the arrow according to the bearing.
@ -140,7 +96,4 @@ public class ArrowController {
arrowStackPane.setRotate(bearing.degrees()); arrowStackPane.setRotate(bearing.degrees());
} }
} }

@ -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<RaceConnection> connectionTable;
@FXML
private TableColumn<RaceConnection, String> hostnameColumn;
@FXML
private TableColumn<RaceConnection, String> 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<RaceConnection> 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");
}
}
}

@ -1,32 +1,108 @@
package visualiser.Controllers; package visualiser.Controllers;
import javafx.fxml.Initializable; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import java.net.URL; import javafx.scene.Scene;
import java.util.ResourceBundle; 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. * Abstract controller class to give each subclass the functionality to load
* Created by fwy13 on 15/03/2017. * a new scene into the existing stage, or create a new popup window.
*/ */
public abstract class Controller implements Initializable { public abstract class Controller {
protected MainController parent; private Stage stage = App.getStage();
/** /**
* Sets the parent of the application * Loads the title screen again when app is already running.
* * @throws IOException if a problem with the title.fxml
* @param parent controller
*/ */
public void setParent(MainController parent) { protected void loadTitleScreen() throws IOException {
this.parent = parent; 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();
} }
/** /**
* Initialisation class that is run on start up. * Used to load a new scene in the currently open stage.
* * @param fxmlUrl the URL of the FXML file to be loaded
* @param location resources location * @return the controller of the new scene
* @param resources resources bundle * @throws IOException if there is an issue with the fxmlUrl
*/ */
@Override protected Controller loadScene(String fxmlUrl) throws IOException {
public abstract void initialize(URL location, ResourceBundle resources); // 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();
}
/**
* 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
*/
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();
}
/**
* Adds the relevant CSS styling to the scene being loaded.
* @param scene new scene to be loaded and displayed
*/
private void addCssStyle(Scene scene){
if (App.dayMode) {
scene.getStylesheets().add("/css/dayMode.css");
} else {
scene.getStylesheets().add("/css/nightMode.css");
}
}
} }

@ -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<VisualiserBoat> boatInfoTable;
@FXML
TableColumn<VisualiserBoat, String> boatRankColumn;
@FXML
TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML
Label raceWinnerLabel;
/**
* The boats to display on the table.
*/
private ObservableList<VisualiserBoat> 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<VisualiserBoat> 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<VisualiserBoat> boats){
finishWrapper.setVisible(true);
setFinishTable(boats);
}
}

@ -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<Image> 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; }
}

@ -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<Image> 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<ButtonType> 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;
}
}

@ -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<Label> allPlayerLabels;
private ObservableList<Subject3D> subjects = FXCollections.observableArrayList();
private StlMeshImporter importer;
private PopulatePlayers lobbyUpdateListener;
public void initialize() {
allPlayerLabels = new ArrayList(Arrays.asList(playerLabel, playerLabel2,
playerLabel3,
playerLabel4,
playerLabel5,
playerLabel6));
URL asset = HostGameController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
importer = new StlMeshImporter();
importer.read(asset);
lobbyUpdateListener = new PopulatePlayers();
}
private void resetLobby(){
for (Label label: allPlayerLabels){
label.setText("No Player");
}
List<Node> nodeCopy = new ArrayList(playerContainer.getChildren());
for (Node node: nodeCopy){
if (node instanceof View3D){
playerContainer.getChildren().remove(node);
}
}
}
public class PopulatePlayers implements ListChangeListener {
@Override
public void onChanged(Change change) {
Platform.runLater(
() -> {
while (change.next()){
if (change.wasAdded() || change.wasRemoved() || change.wasUpdated()){
try{
resetLobby();
int count = 0;
int row = 0;
for (VisualiserBoat boat :visualiserRaceEvent.getVisualiserRaceState().getBoats()) {
View3D playerBoatToSet = new View3D();
playerBoatToSet.setItems(subjects);
playerContainer.add(playerBoatToSet, (count % 3) , row);
playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10));
SeaSurface sea = new SeaSurface(750, 200);
sea.setX(250);
sea.setZ(210);
subjects.add(sea);
MeshView mesh = new MeshView(importer.getImport());
Subject3D subject = new Subject3D(mesh,0);
subjects.add(subject);
playerBoatToSet.setDistance(50);
playerBoatToSet.setYaw(45);
playerBoatToSet.setPitch(20);
AnimationTimer rotate = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(subject.getHeading().getAngle() + 0.1);
}
};
rotate.start();
allPlayerLabels.get(count).setText("Player: " + boat.getSourceID());
allPlayerLabels.get(count).toFront();
count += 1;
if (count > 2){
row = 1;
}
}
}
catch(ConcurrentModificationException e){
}
}
}
}
);
}
}
/*
private void populatePlayers(ListChangeListener.Change change){
}*/
/**
* Starts the race.
*/
private void startRace() {
//Initialises the race clock.
initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());
//Starts the race countdown timer.
countdownTimer();
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClock(VisualiserRaceState visualiserRace) {
//Remaining time.
initialiseRaceClockDuration(visualiserRace);
}
/**
* Initialises the race duration label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) {
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
countdownLabel.setText(newValue);
});
});
}
/**
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//Try catch for getting interval times
try {
long interval = ChronoUnit.MILLIS.between(visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getCurrentTime(), visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getStartingTime());
if(interval<=10000){
countdownLabel.setVisible(false);
countdownTenPane.setVisible(true);
countdownTenText.setVisible(true);
}
countdownText(interval);
} catch (Exception e){
}
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
try {
visualiserRaceEvent.getVisualiserRaceState().getBoats().removeListener(lobbyUpdateListener);
RaceViewController rvc = (RaceViewController)
loadScene("raceView.fxml");
rvc.startRace(visualiserRaceEvent, controllerClient,
isHost);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
public void enterGameLobby(Socket socket, boolean isHost){
try {
this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);
this.isHost = isHost;
this.controllerClient = visualiserRaceEvent.getControllerClient();
this.visualiserRaceEvent.getVisualiserRaceState().getBoats().addListener(this.lobbyUpdateListener);
startRace();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e);
}
}
/**
* Menu button pressed. Prompt alert then return to menu
* @throws IOException socket erro
*/
public void menuBtnPressed() throws IOException {
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<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.OK){
visualiserRaceEvent.terminate();
try{
if(isHost) {
App.game.endEvent();
}
loadScene("title.fxml");
}catch (IOException ignore){};
}
}
/**
* Start button pressed. Currently only prints out start
*/
public void startBtnPressed(){
//System.out.println("Should start the race. This button is only visible for the host");
}
public void joinSpecPressed(){
//System.out.println("Spectator list pressed. Joining spectators");
}
public void joinRacePressed(){
//System.out.println("Empty race user pane pressed. Joining racers");
}
/**
* Countdown interval checker that updates the countdown text
* @param interval Countdown interval to check
*/
private void countdownText(long interval){
//Do nothing if 5 seconds or less to go
if (interval <=5000){
countdownTenPane.setVisible(false);
//countdownTenText.setText("5");
return;
}
//6 seconds left. Display 1 second for countdown
if (interval <=6000){
countdownTenText.setText("1");
return;
}
//7 seconds left. Display 2 seconds for countdown
if (interval <=7000){
countdownTenText.setText("2");
return;
}
//8 seconds left. Display 3 seconds for countdown
if (interval <=8000){
countdownTenText.setText("3");
return;
}
//9 seconds left. Display 4 seconds for countdown
if (interval <=9000){
countdownTenText.setText("4");
return;
}
//10 seconds left. Display 5 seconds for countdown
if (interval <=10000){
countdownTenText.setText("5");
return;
}
}
}

@ -2,9 +2,6 @@ package visualiser.Controllers;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
@ -20,12 +17,10 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static visualiser.app.App.keyFactory;
/** /**
* Controller for the scene used to display and update current key bindings. * Controller for the scene used to display and update current key bindings.
*/ */
public class KeyBindingsController { public class KeyBindingsController extends Controller {
private @FXML Button btnSave; private @FXML Button btnSave;
private @FXML Button btnCancel; private @FXML Button btnCancel;
private @FXML Button btnReset; private @FXML Button btnReset;
@ -33,23 +28,27 @@ public class KeyBindingsController {
private @FXML ListView lstKey; private @FXML ListView lstKey;
private @FXML ListView lstDescription; private @FXML ListView lstDescription;
private @FXML AnchorPane anchor; private @FXML AnchorPane anchor;
private KeyFactory existingKeyFactory;
private KeyFactory newKeyFactory; private KeyFactory newKeyFactory;
private Boolean changed = false; // keyBindings have been modified private Boolean changed = false; // keyBindings have been modified
private Button currentButton = null; // last button clicked private Button currentButton = null; // last button clicked
public void initialize(){ public void initialize(){
// create new key factory to modify, keeping the existing one safe // create new key factory to modify, keeping the existing one safe
existingKeyFactory = new KeyFactory();
existingKeyFactory.load();
newKeyFactory = copyExistingFactory(); newKeyFactory = copyExistingFactory();
initializeTable(); initializeTable();
populateTable(); populateTable();
setKeyListener(); setKeyListener();
setClosedListener();
} }
/** /**
* Sets up table before populating it. * Sets up table before populating it.
* Set up includes headings, CSS styling and modifying default properties. * Set up includes headings, CSS styling and modifying default properties.
*/ */
public void initializeTable(){ private void initializeTable(){
// set the headings for each column // set the headings for each column
lstKey.getItems().add("Key"); lstKey.getItems().add("Key");
lstControl.getItems().add("Command"); lstControl.getItems().add("Command");
@ -67,15 +66,15 @@ public class KeyBindingsController {
// stop the columns from being selectable, so only the buttons are // stop the columns from being selectable, so only the buttons are
lstKey.getSelectionModel().selectedItemProperty() lstKey.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) -> .addListener((observable, oldValue, newValue) ->
Platform.runLater(() -> Platform.runLater(() ->
lstKey.getSelectionModel().select(0))); lstKey.getSelectionModel().select(0)));
lstDescription.getSelectionModel().selectedItemProperty() lstDescription.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) -> .addListener((observable, oldValue, newValue) ->
Platform.runLater(() -> Platform.runLater(() ->
lstDescription.getSelectionModel().select(0))); lstDescription.getSelectionModel().select(0)));
lstControl.getSelectionModel().selectedItemProperty() lstControl.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) -> .addListener((observable, oldValue, newValue) ->
Platform.runLater(() -> Platform.runLater(() ->
lstControl.getSelectionModel().select(0))); lstControl.getSelectionModel().select(0)));
} }
@ -83,7 +82,7 @@ public class KeyBindingsController {
/** /**
* Populates the table with commands and their key binding details. * Populates the table with commands and their key binding details.
*/ */
public void populateTable(){ private void populateTable(){
// add each command to the table // add each command to the table
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet()) { for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet()) {
// create button for command // create button for command
@ -100,11 +99,11 @@ public class KeyBindingsController {
/** /**
* Makes a copy of the {@link KeyFactory} that does not modify the original. * Makes a copy of the {@link KeyFactory} that does not modify the original.
* @return new keyfactory to be modified * @return new keyFactory to be modified
*/ */
public KeyFactory copyExistingFactory(){ private KeyFactory copyExistingFactory(){
newKeyFactory = new KeyFactory(); newKeyFactory = new KeyFactory();
Map<String, ControlKey> oldKeyState = keyFactory.getKeyState(); Map<String, ControlKey> oldKeyState = existingKeyFactory.getKeyState();
Map<String, ControlKey> newKeyState = new HashMap<>(); Map<String, ControlKey> newKeyState = new HashMap<>();
// copy over commands and their keys // copy over commands and their keys
@ -116,11 +115,32 @@ public class KeyBindingsController {
} }
/** /**
* Creates a listener for the base anchorpane for key presses. * Creates a listener for when a user tries to close the current window.
*/
private void setClosedListener(){
anchor.sceneProperty().addListener((obsS, oldS, newS) -> {
if (newS != null) {
newS.windowProperty().addListener((obsW, oldW, newW) -> {
if (newW != null) {
Stage stage = (Stage)newW;
// WE is processed by onExit method
stage.setOnCloseRequest(we -> {
if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) {
onExit(we);
}
});
}
});
}
});
}
/**
* Creates a listener for the base anchorPane for key presses.
* It updates the current key bindings of the {@link KeyFactory} if * It updates the current key bindings of the {@link KeyFactory} if
* required. * required.
*/ */
public void setKeyListener(){ private void setKeyListener(){
anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> { anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
// if esc, cancel current button click // if esc, cancel current button click
if (event.getCode() == KeyCode.ESCAPE){ if (event.getCode() == KeyCode.ESCAPE){
@ -179,15 +199,16 @@ public class KeyBindingsController {
*/ */
public void save(){ public void save(){
if (isFactoryValid()) { if (isFactoryValid()) {
keyFactory = newKeyFactory; existingKeyFactory = newKeyFactory;
newKeyFactory = new KeyFactory(); newKeyFactory = new KeyFactory();
changed = false; changed = false;
keyFactory.save(); // save persistently existingKeyFactory.save(); // save persistently
loadNotification("Key bindings were successfully saved.", false); loadNotification("Key bindings were successfully saved.", false);
} else { } else {
loadNotification("One or more key bindings are missing. " + loadNotification("One or more key bindings are missing. " +
"Failed to save.", true); "Failed to save.", true);
} }
((Stage)btnCancel.getScene().getWindow()).close();
} }
/** /**
@ -195,10 +216,10 @@ public class KeyBindingsController {
* commands are missing a key binding. * commands are missing a key binding.
* @return True if valid, false if invalid * @return True if valid, false if invalid
*/ */
public Boolean isFactoryValid(){ private Boolean isFactoryValid(){
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet
()) { ()) {
if (entry.getKey().toString()==entry.getValue().toString()){ if (entry.getKey().equals(entry.getValue().toString())){
return false; return false;
} }
} }
@ -211,7 +232,7 @@ public class KeyBindingsController {
* @param we {@link WindowEvent} close request to be consumed if settings * @param we {@link WindowEvent} close request to be consumed if settings
* have not been successfully saved. * have not been successfully saved.
*/ */
public void onExit(WindowEvent we){ private void onExit(WindowEvent we){
// if modified KeyFactory hasn't been saved // if modified KeyFactory hasn't been saved
if (changed){ if (changed){
loadNotification("Please cancel or save your changes before exiting" + loadNotification("Please cancel or save your changes before exiting" +
@ -225,23 +246,15 @@ public class KeyBindingsController {
* @param message the message to be displayed to the user * @param message the message to be displayed to the user
* @param warning true if the message to be displayed is due to user error * @param warning true if the message to be displayed is due to user error
*/ */
public void loadNotification(String message, Boolean warning){ private void loadNotification(String message, Boolean warning){
Parent root = null;
FXMLLoader loader = new FXMLLoader(getClass().getResource
("/visualiser/scenes/notification.fxml"));
try { try {
root = loader.load(); NotificationController nc = (NotificationController)
loadPopupScene("notification.fxml",
"", Modality.APPLICATION_MODAL);
nc.setMessage(message, warning);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
NotificationController controller = loader.getController();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.centerOnScreen();
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
// displays given message in the window
controller.setMessage(message, warning);
} }
} }

@ -9,47 +9,47 @@ import javafx.scene.control.TableView;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.media.AudioClip; import javafx.scene.media.AudioClip;
import network.Messages.HostGame;
import visualiser.app.MatchBrowserSingleton;
import visualiser.model.RaceConnection; import visualiser.model.RaceConnection;
import visualiser.network.MatchBrowserLobbyInterface;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket; import java.net.Socket;
import java.net.URL; import java.net.SocketException;
import java.util.ResourceBundle; import java.util.Observable;
import java.util.Observer;
/** /**
* Controller for the Lobby for entering games * Controller for the Lobby for entering games
*/ */
public class LobbyController extends Controller { public class LobbyController extends Controller {
private @FXML TableView<RaceConnection> lobbyTable;
@FXML private @FXML TableColumn<RaceConnection, String> gameNameColumn;
private AnchorPane lobbyWrapper; private @FXML TableColumn<RaceConnection, String> hostNameColumn;
@FXML private @FXML TableColumn<RaceConnection, String> statusColumn;
private TableView<RaceConnection> lobbyTable; private @FXML Button joinGameBtn;
@FXML private @FXML TextField addressFld;
private TableColumn<RaceConnection, String> gameNameColumn; private @FXML TextField portFld;
@FXML
private TableColumn<RaceConnection, String> hostNameColumn;
@FXML
private TableColumn<RaceConnection, String> statusColumn;
@FXML
private Button joinGameBtn;
@FXML
private TextField addressFld;
@FXML
private TextField portFld;
private ObservableList<RaceConnection> connections; private ObservableList<RaceConnection> connections;
private ObservableList<RaceConnection> customConnections;
private AudioClip sound; private AudioClip sound;
//the socket for match browser
private DatagramSocket udpSocket;
private MatchBrowserLobbyInterface matchBrowserLobbyInterface;
@Override
public void initialize(URL location, ResourceBundle resources) { public void initialize() {
// set up the connection table
connections = FXCollections.observableArrayList(); connections = FXCollections.observableArrayList();
customConnections = FXCollections.observableArrayList();
//connections.add(new RaceConnection("localhost", 4942, "Local Game")); //connections.add(new RaceConnection("localhost", 4942, "Local Game"));
lobbyTable.setItems(connections); lobbyTable.setItems(connections);
gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty()); gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty());
hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty()); hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty()); statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
@ -57,12 +57,14 @@ public class LobbyController extends Controller {
lobbyTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> { lobbyTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> {
if (curr != null && curr.statusProperty().getValue().equals("Ready")) { if (curr != null && curr.statusProperty().getValue().equals("Ready")) {
joinGameBtn.setDisable(false); joinGameBtn.setDisable(false);
} } else {
else {
joinGameBtn.setDisable(true); joinGameBtn.setDisable(true);
} }
}); });
joinGameBtn.setDisable(true); joinGameBtn.setDisable(true);
this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket();
receiveMatchData();
} }
/** /**
@ -71,6 +73,7 @@ public class LobbyController extends Controller {
public void refreshBtnPressed(){ public void refreshBtnPressed(){
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play(); sound.play();
addServerGames();
for(RaceConnection connection: connections) { for(RaceConnection connection: connections) {
connection.check(); connection.check();
} }
@ -80,28 +83,25 @@ public class LobbyController extends Controller {
} else { } else {
joinGameBtn.setDisable(true); joinGameBtn.setDisable(true);
} }
} catch (Exception e){} } catch (Exception ignored){}
} }
/** /**
* Connect to a connection. * Connect to a connection.
* @throws IOException socket error
*/ */
public void connectSocket() { public void connectSocket() throws IOException {
try{
RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem(); RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort()); Socket socket = new Socket(connection.getHostname(), connection.getPort());
lobbyWrapper.setVisible(false); InGameLobbyController iglc = (InGameLobbyController)loadScene("gameLobby.fxml");
parent.enterLobby(socket, false); iglc.enterGameLobby(socket, false);
} catch (IOException e) { /* Never reached */
e.printStackTrace();
}
} }
public void menuBtnPressed(){ public void menuBtnPressed() throws IOException {
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play(); sound.play();
lobbyWrapper.setVisible(false); matchBrowserLobbyInterface.closeSocket();
parent.enterTitle(); loadScene("title.fxml");
} }
/** /**
@ -112,24 +112,41 @@ public class LobbyController extends Controller {
sound.play(); sound.play();
String hostName = addressFld.getText(); String hostName = addressFld.getText();
String portString = portFld.getText(); String portString = portFld.getText();
try{ try {
int port = Integer.parseInt(portString); int port = Integer.parseInt(portString);
connections.add(new RaceConnection(hostName, port, "Boat Game")); customConnections.add(new RaceConnection(hostName, port, "Boat Game"));
connections.addAll(customConnections);
addressFld.clear(); addressFld.clear();
portFld.clear(); portFld.clear();
}catch(NumberFormatException e){ } catch (NumberFormatException e) {
System.err.println("Port number entered is not a number"); System.err.println("Port number entered is not a number");
} }
} }
public AnchorPane startWrapper(){ public void receiveMatchData(){
return lobbyWrapper; matchBrowserLobbyInterface = new MatchBrowserLobbyInterface();
try {
matchBrowserLobbyInterface.startReceivingHostData(new DatagramSocket(4941));
Observer o = new Observer() {
@Override
public void update(Observable o, Object arg) {
refreshBtnPressed();
}
};
matchBrowserLobbyInterface.addObserver(o);
} catch (SocketException e) {
System.err.println("Socket 4941 in use");
}
} }
/** /**
* Enter the lobby page. * Adds the games received from the server
*/ */
public void enterLobby(){ private void addServerGames() {
lobbyWrapper.setVisible(true); connections.clear();
connections.addAll(customConnections);
for (HostGame game : matchBrowserLobbyInterface.getGames()) {
connections.add(new RaceConnection(game.getIp(), 4942, "Boat Game"));
}
} }
} }

@ -1,158 +0,0 @@
package visualiser.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
import javafx.scene.media.AudioClip;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Controller that everything is overlayed onto. This makes it so that changing scenes does not resize your stage.
*/
public class MainController extends Controller {
@FXML private StartController startController;
@FXML private RaceController raceController;
@FXML private ConnectionController connectionController;
@FXML private FinishController finishController;
@FXML private TitleController titleController;
@FXML private HostController hostController;
@FXML private LobbyController lobbyController;
private AudioClip sound;
/**
* Ctor.
*/
public MainController() {
}
/**
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost if the client is the host of a race or not.
*/
public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
raceController.startRace(visualiserRace, controllerClient, isHost);
}
public void endEvent() throws IOException { hostController.endEvent(); }
/**
* Transitions from the server selection screen to the pre-race lobby for a given server.
* @param socket The server to read data from.
* @param isHost is connection a host
*/
public void enterLobby(Socket socket, Boolean isHost) {
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
startController.enterLobby(socket, isHost);
}
/**
* Transitions from the {@link RaceController} screen to the {@link FinishController} screen.
* @param boats The boats to display on the finish screen.
*/
public void enterFinish(ObservableList<VisualiserBoat> boats) {
finishController.enterFinish(boats);
}
/**
* Transitions into the title screen
*/
public void enterTitle() {
titleController.enterTitle();
}
/**
* Transitions into lobby screen
*/
public void enterLobby(){
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
lobbyController.enterLobby(); }
/**
* Transitions into host game screen
*/
public void hostGame(){
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
hostController.hostGame(); }
/**
* Sets up the css for the start of the program
*/
public void startCss(){titleController.setDayMode();}
/**
* host controller host a game
* @throws IOException throws exception
*/
public void beginGame() throws IOException {
hostController.hostGamePressed();
}
public void setGameType(int gameType){
hostController.setGameType(gameType);
}
public int getGameType(){ return hostController.getGameType(); }
/**
* Main Controller for the applications will house the menu and the displayed pane.
*
* @param location of resources
* @param resources bundle
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
startController.setParent(this);
raceController.setParent(this);
connectionController.setParent(this);
finishController.setParent(this);
titleController.setParent(this);
hostController.setParent(this);
lobbyController.setParent(this);
AnchorPane.setTopAnchor(startController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(startController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setBottomAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setLeftAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setRightAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setTopAnchor(titleController.titleWrapper, 0.0);
AnchorPane.setBottomAnchor(titleController.titleWrapper, 0.0);
AnchorPane.setLeftAnchor(titleController.titleWrapper, 0.0);
AnchorPane.setRightAnchor(titleController.titleWrapper, 0.0);
}
}

@ -9,7 +9,7 @@ import javafx.stage.Stage;
/** /**
* Controller for a popup notification regarding user activity. * Controller for a popup notification regarding user activity.
*/ */
public class NotificationController { public class NotificationController extends Controller{
private @FXML Label lblDescription; private @FXML Label lblDescription;
private @FXML Text txtMessage; private @FXML Text txtMessage;

@ -0,0 +1,39 @@
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 visualiser.model.VisualiserBoat;
/**
* Finish Screen for when the race finishes.
*/
public class RaceFinishController extends Controller {
private @FXML TableView<VisualiserBoat> boatInfoTable;
private @FXML TableColumn<VisualiserBoat, String> boatRankColumn;
private @FXML TableColumn<VisualiserBoat, String> boatNameColumn;
private @FXML Label raceWinnerLabel;
/**
* Display the table
* @param boats boats to display on the table.
*/
public void loadFinish(ObservableList<VisualiserBoat> boats) {
// set table 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);
}
}
}

@ -0,0 +1,141 @@
package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller to for waiting for the race to start.
*/
public class RaceStartController extends Controller {
private @FXML Label raceTitleLabel;
private @FXML Label raceStartLabel;
private @FXML Label timeZoneTime;
private @FXML Label timer;
private @FXML TableView<VisualiserBoat> boatNameTable;
private @FXML TableColumn<VisualiserBoat, String> boatNameColumn;
private @FXML TableColumn<VisualiserBoat, String> boatCodeColumn;
private @FXML Label raceStatusLabel;
private VisualiserRaceEvent visualiserRaceEvent;
private VisualiserRaceState raceState;
private ControllerClient controllerClient;
private boolean isHost;
/**
* Show starting information for a race given a socket.
* Intended to be called on loading the scene.
* @param socket network source of information
* @param isHost is user a host
*/
public void enterLobby(Socket socket, Boolean isHost) {
try {
this.isHost = isHost;
this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);
this.controllerClient = visualiserRaceEvent.getControllerClient();
this.raceState = visualiserRaceEvent.getVisualiserRaceState();
showRaceDetails();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
Logger.getGlobal()
.log(Level.WARNING, "Could not connect to server.", e);
}
}
/**
* Displays details and starts the timer for the race being started
*/
private void showRaceDetails() {
raceTitleLabel.setText(this.raceState.getRegattaName());
initialiseBoatTable();
initialiseRaceClock();
countdownTimer();
}
/**
* Initialises the boat table that is to be shown on the pane.
*/
private void initialiseBoatTable() {
//Get the boats.
ObservableList<VisualiserBoat> boats =
this.raceState.getBoats();
//Populate table.
boatNameTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
*/
private void initialiseRaceClock() {
raceStartLabel.setText(
this.raceState.getRaceClock().getStartingTimeString());
// init clock start time
this.raceState.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
raceStartLabel.setText(newValue);
});
});
// init clock current time
this.raceState.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timeZoneTime.setText(newValue);
});
});
// init clock remaining time
this.raceState.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
// display current race status
RaceStatusEnum raceStatus = raceState.getRaceStatusEnum();
raceStatusLabel.setText("Race Status: " + raceStatus.name());
// if race is in PREPARATORY or STARTED status
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
stop(); // stop this timer
// load up the race scene
try {
RaceViewController rvc = (RaceViewController)
loadScene("raceView.fxml");
rvc.startRace(visualiserRaceEvent, controllerClient,
isHost);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
}

@ -21,10 +21,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.Material; import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial; import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box; import javafx.scene.shape.*;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Translate; import javafx.scene.transform.Translate;
import javafx.util.Callback; import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
@ -37,111 +34,120 @@ import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.ControlKey;
import visualiser.layout.*; import visualiser.layout.*;
import visualiser.model.*; import visualiser.model.*;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import visualiser.utils.GPSConverter; import visualiser.utils.GPSConverter;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static visualiser.app.App.keyFactory;
/** /**
* Controller used to display a running race. * Controller used to display a running race.
*/ */
public class RaceController extends Controller { public class RaceViewController extends Controller {
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRaceEvent visualiserRace; private VisualiserRaceEvent visualiserRace;
private VisualiserRaceState raceState;
/**
* Service for sending keystrokes to server
*/
private ControllerClient controllerClient; private ControllerClient controllerClient;
private KeyFactory keyFactory = new KeyFactory();
private boolean infoTableShow = true; // shown or hidden
private boolean isHost; private boolean isHost;
private TutorialState currentState; private TutorialState currentState;
private ArrayList<TutorialState> tutorialStates; private ArrayList<TutorialState> tutorialStates;
private boolean isTutorial = false; private boolean isTutorial = false;
private String keyToPress; private String keyToPress;
/**
* state of the info table
*/
private boolean infoTableShow;
private View3D view3D; private View3D view3D;
private ObservableList<Subject3D> viewSubjects; private ObservableList<Subject3D> viewSubjects;
/**
* Arrow pointing to next mark in third person
*/
private Subject3D nextMarkArrow; private Subject3D nextMarkArrow;
private ChangeListener<? super GPSCoordinate> pointToMark;
/** /**
* The arrow controller. * Animation loop for rotating mark arrow
*/ */
@FXML private ArrowController arrowController; private AnimationTimer pointToMark;
@FXML private GridPane canvasBase; // note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController;
@FXML private SplitPane racePane; private @FXML GridPane canvasBase;
private @FXML SplitPane racePane;
@FXML private Label tutorialText; private @FXML StackPane arrowPane;
private @FXML Label timer;
private @FXML Label FPS;
private @FXML Label timeZone;
private @FXML CheckBox showFPS;
private @FXML TableView<VisualiserBoat> boatInfoTable;
private @FXML TableColumn<VisualiserBoat, String> boatPlacingColumn;
private @FXML TableColumn<VisualiserBoat, String> boatTeamColumn;
private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
private @FXML LineChart<Number, Number> sparklineChart;
private @FXML Label tutorialText;
/** /**
* This is the pane we place the actual arrow control inside of. * Displays a specified race.
* Intended to be called on loading the scene.
* @param visualiserRace Object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost is user a host
*/ */
@FXML private StackPane arrowPane; public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
@FXML private Label timer; this.visualiserRace = visualiserRace;
@FXML private Label FPS; this.raceState = visualiserRace.getVisualiserRaceState();
@FXML private Label timeZone; this.controllerClient = controllerClient;
@FXML private CheckBox showFPS; this.isHost = isHost;
@FXML private TableView<VisualiserBoat> boatInfoTable; keyFactory.load();
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
private AnimationTimer arrowToNextMark; tutorialCheck();
initKeypressHandler();
initialiseRaceVisuals();
}
/** /**
* Ctor. * Checks if the current game is a tutorial race and sets up initial
* tutorial displays if it is.
*/ */
public RaceController() { private void tutorialCheck(){
if (App.gameType == 4) {
// Import arrow mesh isTutorial = true;
URL asset = HostController.class.getClassLoader().getResource("assets/arrow V1.0.4.stl"); tutorialText.setVisible(true);
StlMeshImporter importer = new StlMeshImporter(); tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));
importer.read(asset);
MeshView arrow = new MeshView(importer.getImport()); currentState = tutorialStates.get(0);
PhongMaterial arrowMat = new PhongMaterial(Color.RED); tutorialStates.remove(0);
arrow.setMaterial(arrowMat); searchMapForKey("Upwind");
this.nextMarkArrow = new Annotation3D(arrow); tutorialText.setText(
this.nextMarkArrow.setScale(0.1); "Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " +
keyToPress + " to turn upwind.");
} else {
isTutorial = false;
tutorialText.setVisible(false);
}
} }
@Override private AnimationTimer arrowToNextMark;
public void initialize(URL location, ResourceBundle resources) {
infoTableShow = true;
// Initialise keyboard handler private void initKeypressHandler() {
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> { racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString(); String codeString = event.getCode().toString();
// tab key
if (codeString.equals("TAB")){toggleTable();} if (codeString.equals("TAB")){toggleTable();}
// any key pressed
ControlKey controlKey = keyFactory.getKey(codeString); ControlKey controlKey = keyFactory.getKey(codeString);
if(controlKey != null) { if(controlKey != null) {
try { try {
@ -160,15 +166,15 @@ public class RaceController extends Controller {
event.consume(); event.consume();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
Logger.getGlobal().log(Level.WARNING, "RaceViewController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
// escape key
if(event.getCode() == KeyCode.ESCAPE) { if(event.getCode() == KeyCode.ESCAPE) {
try { try {
if (isHost) { if (isHost) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
@ -176,9 +182,8 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race? You are the host"); alert.setContentText("Do you wish to quit the race? You are the host");
Optional<ButtonType> result = alert.showAndWait(); Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) { if (result.get() == ButtonType.OK) {
parent.endEvent(); App.game.endEvent();
racePane.setVisible(false); loadTitleScreen();
App.app.showMainStage(App.getStage());
} }
} else { } else {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
@ -186,12 +191,9 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race?"); alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait(); Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) { if (result.get() == ButtonType.OK) {
racePane.setVisible(false); loadTitleScreen();
App.app.showMainStage(App.getStage());
} }
} }
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -199,30 +201,32 @@ public class RaceController extends Controller {
}); });
} }
/** /**
* Initialises the various UI components to listen to the {@link #visualiserRace}. * Initialises the various UI components to listen to the {@link #visualiserRace}.
*/ */
private void initialiseRace() { private void initialiseRaceVisuals() {
//Fps display.
initialiseFps(this.visualiserRace);
//Information table. // Import arrow mesh
initialiseInfoTable(this.visualiserRace); URL asset = this.getClass().getClassLoader().getResource("assets/arrow V1.0.4.stl");
StlMeshImporter importer = new StlMeshImporter();
//Arrow. importer.read(asset);
initialiseArrow(this.visualiserRace);
initialiseView3D(this.visualiserRace);
//Race timezone label. MeshView arrow = new MeshView(importer.getImport());
initialiseRaceTimezoneLabel(this.visualiserRace); PhongMaterial arrowMat = new PhongMaterial(Color.RED);
arrow.setMaterial(arrowMat);
//Race clock. this.nextMarkArrow = new Annotation3D(arrow);
initialiseRaceClock(this.visualiserRace); this.nextMarkArrow.setScale(0.1);
//Start the race animation timer. // initialise displays
raceTimer(); initialiseFps();
initialiseInfoTable();
initialiseView3D(this.visualiserRace);
initialiseRaceClock();
raceTimer(); // start the timer
new Sparkline(this.raceState, this.sparklineChart);
timeZone.setText(this.raceState.getRaceClock().getTimeZone());
arrowController.setWindProperty(this.raceState.windProperty());
} }
private void initialiseView3D(VisualiserRaceEvent race) { private void initialiseView3D(VisualiserRaceEvent race) {
@ -239,16 +243,16 @@ public class RaceController extends Controller {
pointLight.setLightOn(true); pointLight.setLightOn(true);
// Import boat mesh // Import boat mesh
URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter(); StlMeshImporter importer = new StlMeshImporter();
importer.read(asset); importer.read(asset);
// Configure camera angles and control // Configure camera angles and control
URL markerAsset = HostController.class.getClassLoader().getResource("assets/Bouy V1.1.stl"); URL markerAsset = RaceViewController.class.getClassLoader().getResource("assets/Bouy V1.1.stl");
StlMeshImporter importerMark = new StlMeshImporter(); StlMeshImporter importerMark = new StlMeshImporter();
importerMark.read(markerAsset); importerMark.read(markerAsset);
URL alternateBoatAsset = HostController.class.getClassLoader().getResource("assets/V1.3 BurgerBoat.stl"); URL alternateBoatAsset = RaceViewController.class.getClassLoader().getResource("assets/V1.3 BurgerBoat.stl");
StlMeshImporter importerBurgerBoat = new StlMeshImporter(); StlMeshImporter importerBurgerBoat = new StlMeshImporter();
importerBurgerBoat.read(alternateBoatAsset); importerBurgerBoat.read(alternateBoatAsset);
@ -323,8 +327,12 @@ public class RaceController extends Controller {
if(nextMark.getMark2() != null) { if(nextMark.getMark2() != null) {
view3D.getShape(nextMark.getMark2().getSourceID()).setMaterial(markColor); view3D.getShape(nextMark.getMark2().getSourceID()).setMaterial(markColor);
} }
Subject3D shockwave = new Shockwave(10);
viewSubjects.add(shockwave);
boat.legProperty().addListener((o, prev, curr) -> swapColours(curr)); boat.legProperty().addListener((o, prev, curr) -> swapColours(curr));
boat.hasCollidedProperty().addListener((o, prev, curr) -> showCollision(boatModel, shockwave));
} }
// Fix initial bird's-eye position // Fix initial bird's-eye position
view3D.updatePivot(new Translate(250, 0, 210)); view3D.updatePivot(new Translate(250, 0, 210));
@ -382,6 +390,29 @@ public class RaceController extends Controller {
}); });
} }
private void showCollision(Subject3D boat, Subject3D shockwave) {
AnimationTimer shockFront = new AnimationTimer() {
double opacity = 1;
@Override
public void handle(long now) {
shockwave.setX(boat.getPosition().getX());
shockwave.setY(boat.getPosition().getY());
shockwave.setZ(boat.getPosition().getZ());
if(opacity <= 0) {
shockwave.getMesh().setMaterial(new PhongMaterial(new Color(1,0,0,0)));
this.stop();
}
else {
shockwave.getMesh().setMaterial(new PhongMaterial(new Color(1,0,0,opacity)));
opacity -= 0.1;
}
}
};
shockFront.start();
}
private void addThirdPersonAnnotations(Subject3D subject3D) { private void addThirdPersonAnnotations(Subject3D subject3D) {
viewSubjects.add(nextMarkArrow); viewSubjects.add(nextMarkArrow);
final VisualiserBoat boat; final VisualiserBoat boat;
@ -407,7 +438,9 @@ public class RaceController extends Controller {
/* /*
try { try {
VisualiserBoat boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID()); VisualiserBoat boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
this.pointToMark = (o, prev, curr) -> { this.pointToMark = new AnimationTimer() {
@Override
public void handle(long now) {
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark(); CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate()); Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
@ -415,9 +448,9 @@ public class RaceController extends Controller {
nextMarkArrow.setY(view3D.getPivot().getY()); nextMarkArrow.setY(view3D.getPivot().getY());
nextMarkArrow.setZ(view3D.getPivot().getZ() + 10); nextMarkArrow.setZ(view3D.getPivot().getZ() + 10);
nextMarkArrow.setHeading(headingToMark.degrees()); nextMarkArrow.setHeading(headingToMark.degrees());
}
}; };
pointToMark.start();
boat.positionProperty().addListener(pointToMark);
} catch (BoatNotFoundException e) { } catch (BoatNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
}*/ }*/
@ -433,6 +466,7 @@ public class RaceController extends Controller {
} catch (BoatNotFoundException e) { } catch (BoatNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
}*/ }*/
//pointToMark.stop();
} }
/** /**
@ -463,86 +497,57 @@ public class RaceController extends Controller {
} }
/** /**
* Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property. * Initialises the frame rate functionality. This allows for toggling the
* @param visualiserRace The race to connect the fps label to. * frame rate, and connect the fps label to the race's fps property.
*/
private void initialiseFps(VisualiserRaceEvent visualiserRace) {
//On/off toggle.
initialiseFpsToggle();
//Label value.
initialiseFpsLabel(visualiserRace);
}
/**
* Initialises a listener for the fps toggle.
*/ */
private void initialiseFpsToggle() { private void initialiseFps() {
// fps toggle listener
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) { if (showFPS.isSelected()) {
FPS.setVisible(true); FPS.setVisible(true);
} else { } else {
FPS.setVisible(false); FPS.setVisible(false);
} }
}); });
} // fps label display
this.visualiserRace.getFrameRateProperty().addListener((observable,
/** oldValue, newValue) -> {
* Initialises the fps label to update when the race fps changes. Platform.runLater(() ->
* @param visualiserRace The race to monitor the frame rate of. this.FPS.setText("FPS: " + newValue.toString()));
*/
private void initialiseFpsLabel(VisualiserRaceEvent visualiserRace) {
visualiserRace.getFrameRateProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString()));
}); });
} }
/** /**
* Initialises the information table view to listen to a given race. * Initialises the information table view to listen to a given race.
* @param race Race to listen to.
*/ */
public void initialiseInfoTable(VisualiserRaceEvent race) { private void initialiseInfoTable() {
// list of boats to display data for
//Copy list of boats. ObservableList<VisualiserBoat> boats = FXCollections
ObservableList<VisualiserBoat> boats = FXCollections.observableArrayList(race.getVisualiserRaceState().getBoats()); .observableArrayList(this.visualiserRace.getVisualiserRaceState().getBoats());
SortedList<VisualiserBoat> sortedBoats = new SortedList<>(boats); SortedList<VisualiserBoat> sortedBoats = new SortedList<>(boats);
sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty()); sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty());
//Update copy when original changes. // update list when boat information changes
race.getVisualiserRaceState().getBoats().addListener((ListChangeListener.Change<? extends VisualiserBoat> c) -> Platform.runLater(() -> { this.visualiserRace.getVisualiserRaceState().getBoats().addListener(
boats.setAll(race.getVisualiserRaceState().getBoats()); (ListChangeListener.Change<? extends VisualiserBoat> c) -> Platform.runLater(() -> {
boats.setAll(this.visualiserRace.getVisualiserRaceState().getBoats());
})); }));
// set table data
//Set up table.
boatInfoTable.setItems(sortedBoats); boatInfoTable.setItems(sortedBoats);
//Set up each column.
//Name.
boatTeamColumn.setCellValueFactory( boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty() ); cellData -> cellData.getValue().nameProperty());
//Speed.
boatSpeedColumn.setCellValueFactory( boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty() ); cellData -> cellData.getValue().currentSpeedProperty());
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty());
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().placingProperty());
//Kind of ugly, but allows for formatting an observed speed. //Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory( boatSpeedColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() { new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
//Callback function.
@Override @Override
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) { public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
//We return a table cell that populates itself with a Number, and formats it. //We return a table cell that populates itself with a Number, and formats it.
@ -551,30 +556,18 @@ public class RaceController extends Controller {
//Function to update the cell text. //Function to update the cell text.
@Override @Override
protected void updateItem(Number item, boolean empty) { protected void updateItem(Number item, boolean empty) {
if (item != null) { if (item != null) {
super.updateItem(item, empty); super.updateItem(item, empty);
setText(String.format("%.2fkn", item.doubleValue())); setText(String.format("%.2fkn", item.doubleValue()));
} }
} }
}; };
} }
});
} );
//Last mark.
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty() );
//Kind of ugly, but allows for turning an observed Leg into a string. //Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn.setCellFactory( boatMarkColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() { new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
//Callback function.
@Override @Override
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) { public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
//We return a table cell that populates itself with a Leg's name. //We return a table cell that populates itself with a Leg's name.
@ -583,105 +576,37 @@ public class RaceController extends Controller {
//Function to update the cell text. //Function to update the cell text.
@Override @Override
protected void updateItem(Leg item, boolean empty) { protected void updateItem(Leg item, boolean empty) {
if (item != null) { if (item != null) {
super.updateItem(item, empty); super.updateItem(item, empty);
setText(item.getName()); setText(item.getName());
} }
} }
}; };
}
} );
//Current place within race.
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().placingProperty() );
} }
});
/**
* Initialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) {
timeZone.setText(race.getVisualiserRaceState().getRaceClock().getTimeZone());
} }
/** /**
* Initialises the race clock to listen to the specified race. * Initialises the race clock to listen to the specified race.
* @param race The race to listen to.
*/ */
private void initialiseRaceClock(VisualiserRaceEvent race) { private void initialiseRaceClock() {
raceState.getRaceClock().durationProperty().addListener((observable,
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update. oldValue, newValue) -> {
race.getVisualiserRaceState().getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> { Platform.runLater(() -> {
timer.setText(newValue); timer.setText(newValue);
}); });
}); });
} }
/**
* Displays a specified race.
* @param visualiserRace Object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost is user a host
*/
public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
this.visualiserRace = visualiserRace;
this.controllerClient = controllerClient;
this.isHost = isHost;
//Check if the game is a tutorial
if (parent.getGameType()==4){
isTutorial = true;
tutorialText.setVisible(true);
tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));
currentState = tutorialStates.get(0);
tutorialStates.remove(0);
searchMapForKey("Upwind");
tutorialText.setText("Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " + keyToPress + " to turn upwind.");
} else {
isTutorial = false;
tutorialText.setVisible(false);
}
initialiseRace();
//Display this controller.
racePane.setVisible(true);
}
/** /**
* Transition from the race view to the finish view. * Transition from the race view to the finish view.
* @param boats boats there are in the race.
*/ */
public void finishRace(ObservableList<VisualiserBoat> boats) { private void finishRace() throws IOException {
racePane.setVisible(false); RaceFinishController fc =
parent.enterFinish(boats); (RaceFinishController)loadScene("raceFinish.fxml");
fc.loadFinish(raceState.getBoats());
} }
/**
* Initialises the arrow controller with data from the race to observe.
* @param race The race to observe.
*/
private void initialiseArrow(VisualiserRaceEvent race) {
arrowController.setWindProperty(race.getVisualiserRaceState().windProperty());
}
/** /**
* Timer which monitors the race. * Timer which monitors the race.
*/ */
@ -689,40 +614,28 @@ public class RaceController extends Controller {
new AnimationTimer() { new AnimationTimer() {
@Override @Override
public void handle(long arg0) { public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getVisualiserRaceState().getRaceStatusEnum();
//If the race has finished, go to finish view. //If the race has finished, go to finish view.
if (raceStatus == RaceStatusEnum.FINISHED) { if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
//Stop this timer. stop(); // stop the timer
stop(); try {
finishRace();
//Hide this, and display the finish controller. } catch (IOException e) {
finishRace(visualiserRace.getVisualiserRaceState().getBoats()); e.printStackTrace();
}
} else { } else {
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort(); boatInfoTable.sort();
} }
//Return to main screen if we lose connection. //Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) { if (!visualiserRace.getServerConnection().isAlive()) {
racePane.setVisible(false);
//parent.enterTitle();
try { try {
App.app.showMainStage(App.getStage()); loadTitleScreen();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
//TODO currently this doesn't work correctly - the title screen remains visible after clicking join game
//TODO we should display an error to the user //TODO we should display an error to the user
//TODO also need to "reset" any state (race, connections, etc...). //TODO also need to "reset" any state (race, connections, etc...).
} }
} }
}.start(); }.start();
} }
@ -731,24 +644,22 @@ public class RaceController extends Controller {
* toggles if the info table is shown * toggles if the info table is shown
*/ */
private void toggleTable() { private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth(); double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() +
boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth()
+ boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow){ if (infoTableShow) {
racePane.setDividerPositions(tablePercent); racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5); arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5); arrowPane.setScaleY(0.5);
arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent); arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent);
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4); arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
} else {
}else{
racePane.setDividerPositions(1); racePane.setDividerPositions(1);
arrowPane.setScaleX(1); arrowPane.setScaleX(1);
arrowPane.setScaleY(1); arrowPane.setScaleY(1);
arrowPane.setTranslateX(0); arrowPane.setTranslateX(0);
arrowPane.setTranslateY(0); arrowPane.setTranslateY(0);
} }
boatInfoTable.refresh(); boatInfoTable.refresh();
infoTableShow = !infoTableShow; infoTableShow = !infoTableShow;
@ -831,7 +742,7 @@ public class RaceController extends Controller {
//Set next key to press as the zoom-out key //Set next key to press as the zoom-out key
searchMapForKey("Zoom Out"); searchMapForKey("Zoom Out");
//Update tutorial text //Update tutorial text
tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. To zoom out press " + keyToPress + "."); tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. \nTo zoom out press " + keyToPress + ".");
updateTutorialState(); updateTutorialState();
break; break;
case ZOOMOUT: case ZOOMOUT:
@ -843,19 +754,16 @@ public class RaceController extends Controller {
alert.setContentText("Now you know the controls you are ready to race!"); alert.setContentText("Now you know the controls you are ready to race!");
Optional<ButtonType> result = alert.showAndWait(); Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) { if (result.get() == ButtonType.OK) {
parent.endEvent(); App.game.endEvent();
racePane.setVisible(false); loadTitleScreen();
App.app.showMainStage(App.getStage());
} }
break; break;
default: default:
//State not found. Exit tutorial to title menu //State not found. Exit tutorial to title menu
parent.endEvent(); App.game.endEvent();
racePane.setVisible(false); loadTitleScreen();
App.app.showMainStage(App.getStage());
break; break;
} }
} }
} }

@ -1,271 +0,0 @@
package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
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 javafx.scene.layout.GridPane;
import mock.model.commandFactory.CompositeCommand;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.LatestMessages;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserRaceState;
import visualiser.network.ServerConnection;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller to for waiting for the race to start.
*/
public class StartController extends Controller {
@FXML private GridPane start;
@FXML private AnchorPane startWrapper;
/**
* The name of the race/regatta.
*/
@FXML private Label raceTitleLabel;
/**
* The time the race starts at.
*/
@FXML private Label raceStartLabel;
/**
* The current time at the race location.
*/
@FXML private Label timeZoneTime;
/**
* Time until the race starts.
*/
@FXML private Label timer;
@FXML private TableView<VisualiserBoat> boatNameTable;
@FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;
/**
* The status of the race.
*/
@FXML private Label raceStatusLabel;
/**
* The race + connection to server.
*/
private VisualiserRaceEvent visualiserRaceEvent;
/**
* Writes BoatActions to outgoing message queue.
*/
private ControllerClient controllerClient;
private boolean isHost;
/**
* Ctor.
*/
public StartController() {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Starts the race.
*/
private void startRace() {
//Initialise the boat table.
initialiseBoatTable(this.visualiserRaceEvent.getVisualiserRaceState());
//Initialise the race name.
initialiseRaceName(this.visualiserRaceEvent.getVisualiserRaceState());
//Initialises the race clock.
initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());
//Starts the race countdown timer.
countdownTimer();
}
public AnchorPane startWrapper(){
return startWrapper;
}
/**
* Initialises the boat table that is to be shown on the pane.
* @param visualiserRace The race to get data from.
*/
private void initialiseBoatTable(VisualiserRaceState visualiserRace) {
//Get the boats.
ObservableList<VisualiserBoat> boats = visualiserRace.getBoats();
//Populate table.
boatNameTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
}
/**
* Initialises the race name which is shown on the pane.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceName(VisualiserRaceState visualiserRace) {
raceTitleLabel.setText(visualiserRace.getRegattaName());
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClock(VisualiserRaceState visualiserRace) {
//Start time.
initialiseRaceClockStartTime(visualiserRace);
//Current time.
initialiseRaceClockCurrentTime(visualiserRace);
//Remaining time.
initialiseRaceClockDuration(visualiserRace);
}
/**
* Initialises the race current time label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockStartTime(VisualiserRaceState visualiserRace) {
raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString());
visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
raceStartLabel.setText(newValue);
});
});
}
/**
* Initialises the race current time label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockCurrentTime(VisualiserRaceState visualiserRace) {
visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timeZoneTime.setText(newValue);
});
});
}
/**
* Initialises the race duration label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) {
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.WARNING
|| raceStatus == RaceStatusEnum.PREPARATORY
|| raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
startWrapper.setVisible(false);
//start.setVisible(false);//TODO is this needed?
parent.beginRace(visualiserRaceEvent, controllerClient, isHost);
}
}
}.start();
}
/**
* Show starting information for a race given a socket.
* @param socket network source of information
* @param isHost is user a host
*/
public void enterLobby(Socket socket, Boolean isHost) {
try {
this.isHost = isHost;
this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);
this.controllerClient = visualiserRaceEvent.getControllerClient();
startWrapper.setVisible(true);
startRace();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e);
}
}
}

@ -1,25 +1,16 @@
package visualiser.Controllers; package visualiser.Controllers;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.RadioButton; import javafx.scene.control.RadioButton;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.media.AudioClip; import javafx.scene.media.AudioClip;
import javafx.scene.media.Media; import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaPlayer;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage;
import mock.app.Event;
import mock.exceptions.EventConstructionException; import mock.exceptions.EventConstructionException;
import javafx.stage.WindowEvent;
import visualiser.app.App; import visualiser.app.App;
import java.io.File; import java.io.File;
@ -37,21 +28,11 @@ import java.util.logging.Logger;
* the game. * the game.
*/ */
public class TitleController extends Controller { public class TitleController extends Controller {
//FXML stuff private @FXML RadioButton dayModeRD;
@FXML private @FXML RadioButton nightModeRD;
Button btnJoin; private @FXML Label tutorialLabel;
@FXML private @FXML Pane menuPane;
AnchorPane titleWrapper; private @FXML ImageView imgSun;
@FXML
RadioButton dayModeRD;
@FXML
RadioButton nightModeRD;
@FXML
Label tutorialLabel;
@FXML
Pane menuPane;
@FXML
ImageView imgSun;
/** /**
* Method called when the 'host a game' button is pressed. * Method called when the 'host a game' button is pressed.
@ -59,28 +40,17 @@ public class TitleController extends Controller {
* Currently used to run the RaceVision mock. * Currently used to run the RaceVision mock.
* @throws IOException if main has problems * @throws IOException if main has problems
*/ */
public void hostAGame() throws IOException, URISyntaxException { public void hostAGame() throws IOException {
titleWrapper.setVisible(false); loadScene("hostGame.fxml");
parent.setGameType(0);
parent.hostGame();
App.getStage().setResizable(true);
}
/**
* Switch the scene to the title page.
*/
public void enterTitle(){
titleWrapper.setVisible(true);
} }
/** /**
* To be implemented at a later date- will open the next scene displaying * To be implemented at a later date- will open the next scene displaying
* games a player can join. Place holder method for now! * games a player can join. Place holder method for now!
* @throws IOException socket error
*/ */
public void joinAGame() { public void joinAGame() throws IOException {
titleWrapper.setVisible(false); loadScene("lobby.fxml");
parent.enterLobby();
App.getStage().setResizable(true);
} }
/** /**
@ -93,6 +63,7 @@ public class TitleController extends Controller {
dayModeRD.getScene().getStylesheets().add("/css/dayMode.css"); dayModeRD.getScene().getStylesheets().add("/css/dayMode.css");
menuPane.setStyle("-fx-background-color: #6be6ff;"); menuPane.setStyle("-fx-background-color: #6be6ff;");
nightModeRD.setSelected(false); nightModeRD.setSelected(false);
App.dayMode = true;
} }
/** /**
@ -105,51 +76,27 @@ public class TitleController extends Controller {
nightModeRD.getScene().getStylesheets().add("/css/nightMode.css"); nightModeRD.getScene().getStylesheets().add("/css/nightMode.css");
menuPane.setStyle("-fx-background-color: #1f2c60;"); menuPane.setStyle("-fx-background-color: #1f2c60;");
dayModeRD.setSelected(false); dayModeRD.setSelected(false);
} App.dayMode = false;
@Override
public void initialize(URL location, ResourceBundle resources) {
tutorialLabel.setWrapText(true);
} }
/** /**
* Called when control button is pressed. New pop up window displaying controls * Called when control button is pressed. New pop up window displaying controls
*/ */
public void controlBtnPressed(){ public void showControls(){
try { try {
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); loadPopupScene("keyBindings.fxml",
sound.play(); "Game Controls", Modality.WINDOW_MODAL);
FXMLLoader loader = new FXMLLoader(); } catch (IOException e) {
loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml"));
Parent layout = loader.load();
Scene scene = new Scene(layout);
Stage popupStage = new Stage();
popupStage.setResizable(false);
popupStage.setTitle("Game Controls");
popupStage.initModality(Modality.WINDOW_MODAL);
popupStage.centerOnScreen();
popupStage.setScene(scene);
popupStage.show();
KeyBindingsController controller = loader.getController();
popupStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent we) {
if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) {
controller.onExit(we);
}
}
});
} catch (Exception e){
e.printStackTrace(); e.printStackTrace();
} }
} }
public void tutorialStartPressed() throws IOException,
public void tutorialStartPressed() throws IOException { EventConstructionException {
titleWrapper.setVisible(false); App.gameType = 4;
parent.setGameType(4); HostGameController hgc = new HostGameController();
parent.beginGame(); hgc.setCurrentMapIndex(4);
hgc.hostGamePressed();
} }
} }

@ -3,19 +3,16 @@ package visualiser.app;
import javafx.animation.FadeTransition; import javafx.animation.FadeTransition;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.concurrent.Worker; import javafx.concurrent.Worker;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D; import javafx.geometry.Rectangle2D;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar; import javafx.scene.control.ProgressBar;
import javafx.scene.effect.DropShadow; import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image; import javafx.scene.image.Image;
@ -26,25 +23,22 @@ import javafx.scene.paint.Color;
import javafx.stage.Screen; import javafx.stage.Screen;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
import javafx.util.Duration; import javafx.util.Duration;
import visualiser.Controllers.MainController; import mock.app.Event;
import visualiser.gameController.Keys.KeyFactory;
public class App extends Application { public class App extends Application {
private static Stage stage; private static Stage stage;
public static Event game;
public static Boolean dayMode = true;
public static Integer gameType = 0;
private Pane splashLayout; private Pane splashLayout;
private ProgressBar loadProgress; private ProgressBar loadProgress;
private Label progressText; private Label progressText;
private static final int SPLASH_WIDTH = 676; private static final int SPLASH_WIDTH = 676;
private static final int SPLASH_HEIGHT = 227; private static final int SPLASH_HEIGHT = 227;
public static KeyFactory keyFactory = new KeyFactory();
public static App app;
/** /**
* Entry point for running the programme * Entry point for running the programme
*
* @param args for starting the programme * @param args for starting the programme
*/ */
public static void main(String[] args) { public static void main(String[] args) {
@ -53,9 +47,6 @@ public class App extends Application {
@Override @Override
public void init() { public void init() {
// load the user's personalised key bindings
keyFactory.load();
ImageView splash = new ImageView(new Image( ImageView splash = new ImageView(new Image(
getClass().getClassLoader().getResourceAsStream("images/splashScreen.png") getClass().getClassLoader().getResourceAsStream("images/splashScreen.png")
)); ));
@ -81,15 +72,15 @@ public class App extends Application {
/** /**
* Method that sets up and displays the splash screen * Method that sets up and displays the splash screen
* @param initStage the inital stage * @param stage the initial stage
* @throws Exception if something wrong with title screen. * @throws Exception if something wrong with title screen.
*/ */
public void start(Stage initStage) throws Exception { public void start(Stage stage) throws Exception {
final Task<ObservableList<String>> boatTask = new Task<ObservableList<String>>() { final Task<ObservableList<String>> boatTask = new Task<ObservableList<String>>() {
@Override @Override
protected ObservableList<String> call() throws InterruptedException { protected ObservableList<String> call() throws InterruptedException {
ObservableList<String> addedFilling = ObservableList<String> addedFilling =
FXCollections.<String>observableArrayList(); FXCollections.observableArrayList();
ObservableList<String> burgerFilling = ObservableList<String> burgerFilling =
FXCollections.observableArrayList( FXCollections.observableArrayList(
"Buns", "Patties", "Lettuce", "Onions", "Tomato", "Buns", "Patties", "Lettuce", "Onions", "Tomato",
@ -107,69 +98,49 @@ public class App extends Application {
} }
Thread.sleep(100); Thread.sleep(100);
updateMessage("Burger's done!"); updateMessage("Burger's done!");
return addedFilling; return addedFilling;
} }
}; };
showSplash( showSplash(
initStage, stage,
boatTask, boatTask,
() -> { () -> {
try { try {
showMainStage(new Stage()); loadTitleScreen();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
); );
new Thread(boatTask).start(); new Thread(boatTask).start();
} }
/** /**
* Get main stage * Get the main stage to be shared for all regular game play scenes.
* @return main stage * @return shared stage
*/ */
public static Stage getStage() { public static Stage getStage() {
return App.stage; return App.stage;
} }
/** /**
* Set main stage * Loads the title screen for the first time on app start.
* @param stage stage to set main stage * @throws Exception if there is a problem with a resource loaded
*/
public static void setStage(Stage stage) {
App.stage = stage;
}
/**
* Show the main stage after the splash screen
* @param stage main stage for application
* @throws Exception Throws an exception on error
*/ */
public void showMainStage(Stage stage) throws Exception { private void loadTitleScreen() throws Exception {
App.stage = stage; stage = new Stage();
App.app = this; FXMLLoader loader = new FXMLLoader(getClass().getResource
FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/main.fxml")); ("/visualiser/scenes/title.fxml"));
Parent root = loader.load(); Parent root = loader.load();
stage.setResizable(false); stage.setResizable(false);
MainController mc = (MainController) loader.getController();
mc.enterTitle();
Scene scene = new Scene(root); Scene scene = new Scene(root);
stage.setTitle("The Boat Game - Burgers & Boats");
stage.setScene(scene);
stage.setTitle("RaceVision - Team 7");
stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png"))); stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png")));
mc.startCss(); stage.setScene(scene);
setStage(stage);
stage.show(); stage.show();
stage.setOnCloseRequest(new EventHandler<WindowEvent>() { stage.setOnCloseRequest(event -> {
@Override
public void handle(WindowEvent event) {
Platform.exit(); Platform.exit();
System.exit(0); System.exit(0);
}
}); });
} }

@ -0,0 +1,37 @@
package visualiser.app;
import visualiser.network.MatchBrowserInterface;
import java.io.IOException;
import java.net.DatagramSocket;
public class MatchBrowserSingleton {
private DatagramSocket udpSocket;
private MatchBrowserInterface matchBrowserInterface;
private static MatchBrowserSingleton instance = null;
public MatchBrowserSingleton() {
this.matchBrowserInterface = new MatchBrowserInterface();
try{
this.udpSocket = matchBrowserInterface.setupMatchBrowserConnection();
}catch (IOException e){
System.err.println("Error in setting up connection with match browser");
}
}
public static MatchBrowserSingleton getInstance() {
if (instance == null){
instance = new MatchBrowserSingleton();
}
return instance;
}
public DatagramSocket getUdpSocket() {
return udpSocket;
}
public MatchBrowserInterface getMatchBrowserInterface() {
return matchBrowserInterface;
}
}

@ -1,12 +1,6 @@
package visualiser.enums; package visualiser.enums;
import javafx.scene.input.KeyCode;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.BoatActionEnum;
import visualiser.gameController.Keys.ControlKey;
import static visualiser.app.App.keyFactory;
import java.util.ArrayList;
/** /**
* State of which stage the tutorial is currently in * State of which stage the tutorial is currently in

@ -3,15 +3,14 @@ package visualiser.gameController;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import javafx.scene.Scene; import javafx.scene.Scene;
import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import java.util.HashMap; import java.util.HashMap;
import static visualiser.app.App.keyFactory;
/** /**
* Class for checking what keys are currently being used * Class for checking what keys are currently being used
*/ */
public class InputChecker { public class InputChecker {
private KeyFactory keyFactory;
private HashMap<String, Boolean> currentlyActiveKeys = new HashMap<>(); private HashMap<String, Boolean> currentlyActiveKeys = new HashMap<>();
/** /**
@ -19,7 +18,8 @@ public class InputChecker {
* @param scene Scene the controller is to run in parallel with. * @param scene Scene the controller is to run in parallel with.
*/ */
public void runWithScene(Scene scene){ public void runWithScene(Scene scene){
// KeyFactory keyFactory = KeyFactory.getFactory(); KeyFactory keyFactory = new KeyFactory();
keyFactory.load();
scene.setOnKeyPressed(event -> { scene.setOnKeyPressed(event -> {
String codeString = event.getCode().toString(); String codeString = event.getCode().toString();
@ -27,7 +27,6 @@ public class InputChecker {
ControlKey controlKey = keyFactory.getKey(codeString); ControlKey controlKey = keyFactory.getKey(codeString);
if (controlKey != null) { if (controlKey != null) {
controlKey.onAction(); controlKey.onAction();
// System.out.println(controlKey.toString() + " is Pressed.");
} }
currentlyActiveKeys.put(codeString, true); currentlyActiveKeys.put(codeString, true);
} }
@ -38,7 +37,6 @@ public class InputChecker {
ControlKey controlKey = keyFactory.getKey(codeString); ControlKey controlKey = keyFactory.getKey(codeString);
if (controlKey != null) { if (controlKey != null) {
controlKey.onRelease(); controlKey.onRelease();
// System.out.println(controlKey.toString() + " is Released.");
} }
currentlyActiveKeys.remove(event.getCode().toString()); currentlyActiveKeys.remove(event.getCode().toString());
}); });
@ -50,15 +48,8 @@ public class InputChecker {
ControlKey controlKey = keyFactory.getKey(key); ControlKey controlKey = keyFactory.getKey(key);
if (controlKey != null){ if (controlKey != null){
controlKey.onHold(); controlKey.onHold();
// System.out.println(controlKey.toString() + " is Held.");
} }
} }
// for (String key : InputKeys.stringKeysMap.keySet()){
// if (removeActiveKey(key)) {
// System.out.println(key);
// }
// }
} }
}.start(); }.start();
} }

@ -62,12 +62,12 @@ public class Boundary3D {
double c = Math.sqrt(a * a + b * b); double c = Math.sqrt(a * a + b * b);
Subject3D bound1 = new Subject3D(new Sphere(thickness * 2),0); Subject3D bound1 = new Annotation3D(new Sphere(thickness * 2));
bound1.setX(graphCoord1.getX()); bound1.setX(graphCoord1.getX());
bound1.setZ(graphCoord1.getY()); bound1.setZ(graphCoord1.getY());
boundaryNodes.add(bound1); boundaryNodes.add(bound1);
Subject3D connector = new Subject3D(new Box(c, thickness, thickness),0); Subject3D connector = new Annotation3D(new Box(c, thickness, thickness));
connector.setX(avgCoord.getX()); connector.setX(avgCoord.getX());
connector.setZ(avgCoord.getY()); connector.setZ(avgCoord.getY());
double angle = 90 + Math.toDegrees(GPSConverter.getAngle(graphCoord2, graphCoord1)); double angle = 90 + Math.toDegrees(GPSConverter.getAngle(graphCoord2, graphCoord1));

@ -8,6 +8,9 @@ import javafx.scene.shape.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import javafx.scene.shape.TriangleMesh;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**

@ -18,20 +18,22 @@ import visualiser.utils.PerlinNoiseGenerator;
*/ */
public class SeaSurface extends Subject3D { public class SeaSurface extends Subject3D {
private static Image image; private static Image image;
/** /**
* Sea Surface Constructor * Sea Surface Constructor
*
* @param size size of the sea surface (has to be square for simplicity's sake) * @param size size of the sea surface (has to be square for simplicity's sake)
* @param freq frequency the perlin noise is to be generated at * @param freq frequency the perlin noise is to be generated at
*/ */
public SeaSurface(int size, double freq){ public SeaSurface(int size, double freq) {
super(createSurface(size, freq),0); super(createSurface(size, freq), 0);
image = new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterDown2048.png")); image = new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/ThickCloudsWaterDown2048.png"));
} }
/** /**
* Creates the sea surface * Creates the sea surface
*/ */
private static Shape3D createSurface(int size, double freq){ private static Shape3D createSurface(int size, double freq) {
float[][] noiseArray = PerlinNoiseGenerator.createNoise(size, freq); float[][] noiseArray = PerlinNoiseGenerator.createNoise(size, freq);
Image diffuseMap = createImage(noiseArray.length, noiseArray); //image Image diffuseMap = createImage(noiseArray.length, noiseArray); //image
@ -51,6 +53,7 @@ public class SeaSurface extends Subject3D {
/** /**
* Create texture for uv mapping * Create texture for uv mapping
*
* @param size size of the image to make * @param size size of the image to make
* @param noise array of noise * @param noise array of noise
* @return image that is created * @return image that is created
@ -90,6 +93,7 @@ public class SeaSurface extends Subject3D {
/** /**
* Nomalises the values so that the colours are correct * Nomalises the values so that the colours are correct
*
* @param value value to normalise * @param value value to normalise
* @param min current min * @param min current min
* @param max current max * @param max current max
@ -105,6 +109,7 @@ public class SeaSurface extends Subject3D {
/** /**
* clamps a value between a min and max * clamps a value between a min and max
*
* @param value value to clamp * @param value value to clamp
* @param min minimum value it can be * @param min minimum value it can be
* @param max maximum value it can be * @param max maximum value it can be
@ -123,8 +128,10 @@ public class SeaSurface extends Subject3D {
/** /**
* Prevent rescaling of sea surface * Prevent rescaling of sea surface
*
* @param scale ignored * @param scale ignored
*/ */
@Override @Override
public void setScale(double scale) {} public void setScale(double scale) {
}
} }

@ -0,0 +1,17 @@
package visualiser.layout;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.transform.Rotate;
/**
* Created by cbt24 on 14/09/17.
*/
public class Shockwave extends Subject3D {
public Shockwave(double radius) {
super(new Cylinder(radius,0),0);
getMesh().getTransforms().add(new Rotate(-90, Rotate.X_AXIS));
getMesh().setMaterial(new PhongMaterial(new Color(0,0,0,0)));
}
}

@ -1,5 +1,6 @@
package visualiser.layout; package visualiser.layout;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
@ -71,21 +72,9 @@ public class View3D extends Pane {
*/ */
private Rotate pitch; private Rotate pitch;
/** /**
* Single listener for subject heading changes * Animation loop for camera tracking
*/ */
private ChangeListener<? super Number> pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr); private AnimationTimer trackBoat;
/**
* Single listener for subject position (x) changes
*/
private ChangeListener<? super Number> pivotX = (o, prev, curr) -> pivot.setX((double)curr);
/**
* Single listener for subject position (y) changes
*/
private ChangeListener<? super Number> pivotY = (o, prev, curr) -> pivot.setY((double)curr);
/**
* Single listener for subject position (z) changes
*/
private ChangeListener<? super Number> pivotZ = (o, prev, curr) -> pivot.setZ((double)curr);
/** /**
* Distance to switch from third person to bird's eye * Distance to switch from third person to bird's eye
*/ */
@ -219,10 +208,7 @@ public class View3D extends Pane {
*/ */
private void untrackSubject() { private void untrackSubject() {
if(target.get() != null) { if(target.get() != null) {
target.get().getPosition().xProperty().removeListener(pivotX); trackBoat.stop();
target.get().getPosition().yProperty().removeListener(pivotY);
target.get().getPosition().zProperty().removeListener(pivotZ);
target.get().getHeading().angleProperty().removeListener(pivotHeading);
target.setValue(null); target.setValue(null);
} }
} }
@ -234,13 +220,14 @@ public class View3D extends Pane {
private void trackSubject(Subject3D subject) { private void trackSubject(Subject3D subject) {
target.set(subject); target.set(subject);
this.trackBoat = new AnimationTimer() {
@Override
public void handle(long now) {
updatePivot(target.get().getPosition()); updatePivot(target.get().getPosition());
setYaw(target.get().getHeading().getAngle()); setYaw(target.get().getHeading().getAngle());
}
target.get().getPosition().xProperty().addListener(pivotX); };
target.get().getPosition().yProperty().addListener(pivotY); trackBoat.start();
target.get().getPosition().zProperty().addListener(pivotZ);
target.get().getHeading().angleProperty().addListener(pivotHeading);
} }
public void setNearClip(double nearClip) { public void setNearClip(double nearClip) {

@ -15,7 +15,7 @@ import java.util.Map;
* Class that processes user selected annotation visibility options to * Class that processes user selected annotation visibility options to
* display the requested information on the * display the requested information on the
* {@link ResizableRaceCanvas}. These are displayed * {@link ResizableRaceCanvas}. These are displayed
* via the {@link visualiser.Controllers.RaceController}. <br> * via the {@link visualiser.Controllers.RaceViewController}. <br>
* Annotation options for a {@link VisualiserBoat} include: its name, * Annotation options for a {@link VisualiserBoat} include: its name,
* abbreviation, speed, the time since it passed the last * abbreviation, speed, the time since it passed the last
* {@link shared.model.Mark}, estimated time to the next marker, * {@link shared.model.Mark}, estimated time to the next marker,

@ -18,7 +18,7 @@ import java.util.List;
/** /**
* This JavaFX Canvas is used to update and display details for a * This JavaFX Canvas is used to update and display details for a
* {@link RaceMap} via the * {@link RaceMap} via the
* {@link visualiser.Controllers.RaceController}.<br> * {@link visualiser.Controllers.RaceViewController}.<br>
* It fills it's parent and cannot be downsized. <br> * It fills it's parent and cannot be downsized. <br>
* Details displayed include: * Details displayed include:
* {@link VisualiserBoat}s (and their * {@link VisualiserBoat}s (and their
@ -325,18 +325,6 @@ public class ResizableRaceCanvas extends ResizableCanvas {
} }
/*
//If the race hasn't started, we set the time since last mark to the current time, to ensure we don't start counting until the race actually starts.
if ((boat.getStatus() != BoatStatusEnum.RACING) && (boat.getStatus() == BoatStatusEnum.FINISHED)) {
boat.setTimeAtLastMark(visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime());
}
*/
} }
/** /**
@ -504,6 +492,52 @@ public class ResizableRaceCanvas extends ResizableCanvas {
} }
private void drawRoundingLines() {
//ugly hack
//rounding lines
//Boat is within an acceptable distance from the mark.
VisualiserBoat boat = null;
for (VisualiserBoat visualiserBoat : new ArrayList<>(raceState.getBoats())) {
if (visualiserBoat.isClientBoat()) {
boat = visualiserBoat;
}
}
if (boat == null) {
return;
}
Leg currentLeg = boat.getCurrentLeg();
MarkRoundingData roundingData = raceState.getMarkRoundingSequence().getRoundingData(currentLeg);
//To screen coords.
GraphCoordinate legEnd = map.convertGPS(roundingData.getMarkToRound().getPosition());
GraphCoordinate round1 = map.convertGPS(roundingData.getRoundCheck1());
GraphCoordinate round2 = map.convertGPS(roundingData.getRoundCheck2());
gc.strokeLine(
legEnd.getX(),
legEnd.getY(),
round1.getX(),
round1.getY() );
gc.strokeLine(
legEnd.getX(),
legEnd.getY(),
round2.getX(),
round2.getY() );
drawCircle(round1, 12, Color.ORANGE);
drawCircle(round2, 12, Color.GREEN);
}
/** /**
* Draws all of the {@link Mark}s on the canvas. * Draws all of the {@link Mark}s on the canvas.
@ -513,6 +547,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
for (Mark mark : new ArrayList<>(raceState.getMarks())) { for (Mark mark : new ArrayList<>(raceState.getMarks())) {
drawMark(mark); drawMark(mark);
} }
} }
@ -621,6 +656,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Marks. //Marks.
drawMarks(); drawMarks();
//TEMP
drawRoundingLines();
} }
/** /**
@ -655,7 +693,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//finds the direction of the current leg as a bearing //finds the direction of the current leg as a bearing
startDirectionLinePoint = legStartPoint; startDirectionLinePoint = legStartPoint;
GPSCoordinate tempEndDirectionLinePoint = legs.get(index).getEndCompoundMark().getAverageGPSCoordinate(); GPSCoordinate tempEndDirectionLinePoint = legs.get(index).getEndCompoundMark().getMark1Position();
bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, tempEndDirectionLinePoint); bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, tempEndDirectionLinePoint);

@ -21,7 +21,7 @@ import java.util.Map;
* placing position as they complete each {@link shared.model.Leg} by * placing position as they complete each {@link shared.model.Leg} by
* passing a course {@link shared.model.Mark}. <br> * passing a course {@link shared.model.Mark}. <br>
* This sparkline is displayed using the * This sparkline is displayed using the
* {@link visualiser.Controllers.RaceController}. * {@link visualiser.Controllers.RaceViewController}.
*/ */
public class Sparkline { public class Sparkline {

@ -8,8 +8,8 @@ import shared.model.GPSCoordinate;
* {@link VisualiserBoat Boat} has travelled in a race. <br> * {@link VisualiserBoat Boat} has travelled in a race. <br>
* TrackPoints are displayed on a * TrackPoints are displayed on a
* {@link ResizableRaceCanvas}, via the * {@link ResizableRaceCanvas}, via the
* {@link visualiser.Controllers.RaceController}. <br> * {@link visualiser.Controllers.RaceViewController}. <br>
* Track points can be made visible or hidden via the RaceController's * Track points can be made visible or hidden via the RaceViewController's
* {@link Annotations}. * {@link Annotations}.
*/ */
public class TrackPoint { public class TrackPoint {

@ -1,6 +1,8 @@
package visualiser.model; package visualiser.model;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.BoatStatusEnum;
@ -17,7 +19,7 @@ import java.util.List;
* This class is used to represent and store information about a boat which may * This class is used to represent and store information about a boat which may
* travel around in a race. It is displayed on the * travel around in a race. It is displayed on the
* {@link ResizableRaceCanvas ResizableRaceCanvas} via the * {@link ResizableRaceCanvas ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController RaceController}. * {@link visualiser.Controllers.RaceViewController RaceViewController}.
*/ */
public class VisualiserBoat extends Boat { public class VisualiserBoat extends Boat {
@ -62,6 +64,7 @@ public class VisualiserBoat extends Boat {
private ObjectProperty<GPSCoordinate> positionProperty; private ObjectProperty<GPSCoordinate> positionProperty;
private ObjectProperty<Bearing> bearingProperty; private ObjectProperty<Bearing> bearingProperty;
private BooleanProperty hasCollided;
/** /**
@ -74,6 +77,7 @@ public class VisualiserBoat extends Boat {
super(boat.getSourceID(), boat.getName(), boat.getCountry()); super(boat.getSourceID(), boat.getName(), boat.getCountry());
this.color = color; this.color = color;
this.hasCollided = new SimpleBooleanProperty(false);
} }
@ -253,10 +257,6 @@ public class VisualiserBoat extends Boat {
this.positionProperty.set(position); this.positionProperty.set(position);
} }
public ObjectProperty<GPSCoordinate> positionProperty() {
return positionProperty;
}
@Override @Override
public Bearing getBearing() { public Bearing getBearing() {
return bearingProperty.get(); return bearingProperty.get();
@ -270,7 +270,15 @@ public class VisualiserBoat extends Boat {
this.bearingProperty.set(bearing); this.bearingProperty.set(bearing);
} }
public ObjectProperty<Bearing> bearingProperty() { public boolean hasCollided() {
return bearingProperty; return hasCollided.get();
}
public BooleanProperty hasCollidedProperty() {
return hasCollided;
}
public void setHasCollided(boolean hasCollided) {
this.hasCollided.set(hasCollided);
} }
} }

@ -66,7 +66,7 @@ public class VisualiserRaceEvent {
this.serverConnection = new ServerConnection(socket, visualiserRaceState, raceCommands, requestType); this.serverConnection = new ServerConnection(socket, visualiserRaceState, raceCommands, requestType);
this.serverConnectionThread = new Thread(serverConnection, "StartController.enterLobby()->serverConnection thread " + serverConnection); this.serverConnectionThread = new Thread(serverConnection, "RaceStartController.enterLobby()->serverConnection thread " + serverConnection);
this.serverConnectionThread.start(); this.serverConnectionThread.start();
@ -112,8 +112,8 @@ public class VisualiserRaceEvent {
* Terminates the server connection and race service. * Terminates the server connection and race service.
*/ */
public void terminate() { public void terminate() {
this.serverConnectionThread.interrupt();
this.visualiserRaceServiceThread.interrupt(); this.visualiserRaceServiceThread.interrupt();
this.serverConnectionThread.interrupt();
serverConnection.terminate();
} }
} }

@ -0,0 +1,34 @@
package visualiser.network;
import network.Messages.AC35Data;
import shared.model.RunnableWithFramePeriod;
import java.io.IOException;
import java.net.DatagramSocket;
public class MatchBrowserClientRunnable implements RunnableWithFramePeriod {
private MatchBrowserLobbyInterface matchBrowserLobbyInterface;
private DatagramSocket socket;
public MatchBrowserClientRunnable(MatchBrowserLobbyInterface matchBrowserInterface, DatagramSocket socket) {
this.matchBrowserLobbyInterface = matchBrowserInterface;
this.socket = socket;
}
@Override
public void run(){
long previousFrameTime = System.currentTimeMillis();
while (!Thread.interrupted()) {
try{
matchBrowserLobbyInterface.receiveGameInfo(socket);
}catch (IOException e){
System.err.println("HostGameMessage could not be received");
}
long currentFrameTime = System.currentTimeMillis();
waitForFramePeriod(previousFrameTime, currentFrameTime, 10000);
previousFrameTime = currentFrameTime;
}
}
}

@ -0,0 +1,40 @@
package visualiser.network;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.HostGame;
import shared.model.RunnableWithFramePeriod;
import java.io.IOException;
import java.net.DatagramSocket;
public class MatchBrowserHostRunnable implements RunnableWithFramePeriod {
private MatchBrowserInterface matchBrowserInterface;
private DatagramSocket socket;
private AC35Data gameInfo;
public MatchBrowserHostRunnable(MatchBrowserInterface matchBrowserInterface, DatagramSocket socket, AC35Data gameInfo) {
this.matchBrowserInterface = matchBrowserInterface;
this.socket = socket;
this.gameInfo = gameInfo;
}
@Override
public void run(){
long previousFrameTime = System.currentTimeMillis();
while (!Thread.interrupted()) {
try{
matchBrowserInterface.sendOutGameInfo(gameInfo, socket);
}catch (IOException e){
System.err.println("HostGameMessage could not be sent");
}
long currentFrameTime = System.currentTimeMillis();
waitForFramePeriod(previousFrameTime, currentFrameTime, 10000);
previousFrameTime = currentFrameTime;
}
}
}

@ -0,0 +1,93 @@
package visualiser.network;
import network.BinaryMessageEncoder;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.HostGameMessageEncoder;
import network.MessageEncoders.HostedGamesRequestEncoder;
import network.Messages.AC35Data;
import network.Messages.Enums.MessageType;
import network.Messages.HostGamesRequest;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
/**
* UDP interface for the matchBrowser to send out hosted game info and get in other hosts info
*/
public class MatchBrowserInterface {
//ip of the server
private InetAddress IPAddress;
//port server is hosted on
private int port;
public MatchBrowserInterface() {
try {//132.181.16.13 is the ip of the CI as of 13/9/17
this.IPAddress = InetAddress.getByName("132.181.16.13"); //InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
this.port = 3779;
}
/**
* Used by host to send out race information to the server
* @param gameInfo the hostGame info for message
* @param socket the udp socket assigned on startup
* @throws IOException socket error
*/
protected void sendOutGameInfo(AC35Data gameInfo, DatagramSocket socket) throws IOException{
byte[] fullMessageToSend;
try{
HostGameMessageEncoder encoder = new HostGameMessageEncoder();
byte[] message = encoder.encode(gameInfo);
BinaryMessageEncoder messageEncoder = new BinaryMessageEncoder(MessageType.HOST_GAME
,System.currentTimeMillis(), 1,(short) 13 ,message);
fullMessageToSend = messageEncoder.getFullMessage();
DatagramPacket sendPacket = new DatagramPacket(fullMessageToSend, fullMessageToSend.length, IPAddress, port);
socket.send(sendPacket);
}catch (InvalidMessageException e){
System.err.println("HostGameMessage could not be encoded");
}
}
/**
* start to send these messages on repeat until game stopped
* @param gameInfo hostgame data
* @param socket socket to send to
*/
public void startSendingHostData(AC35Data gameInfo, DatagramSocket socket){
MatchBrowserHostRunnable hostRunnable = new MatchBrowserHostRunnable(this, socket, gameInfo);
Thread hostRunnableThread = new Thread(hostRunnable, "Socket: " + socket.toString());
hostRunnableThread.start();
}
/**
* Used by a client to setup a connection with the match browser server
* @return the socket created for this connection
* @throws IOException socket error
*/
public DatagramSocket setupMatchBrowserConnection() throws IOException{
DatagramSocket clientSocket = new DatagramSocket();
//creates and empty hostedGamesRequest packet that can be sent to the server
//this lets the server check the udp fields for ip and port to know that this client exists
try{
HostedGamesRequestEncoder encoder = new HostedGamesRequestEncoder();
byte[] message = encoder.encode(new HostGamesRequest(new ArrayList()));
BinaryMessageEncoder messageEncoder = new BinaryMessageEncoder(MessageType.HOSTED_GAMES_REQUEST
,System.currentTimeMillis(), 1,(short) 13 ,message);
DatagramPacket sendPacket = new DatagramPacket(messageEncoder.getFullMessage(), messageEncoder.getFullMessage().length, IPAddress, port);
clientSocket.send(sendPacket);
}catch (InvalidMessageException e){
System.err.println("HostedGamesRequestMessage could not be encoded");
}
return clientSocket;
}
}

@ -0,0 +1,76 @@
package visualiser.network;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.HostedGamesRequestDecoder;
import network.Messages.HostGame;
import network.Messages.HostGamesRequest;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
/**
* Used to receive lobby information from server
*/
public class MatchBrowserLobbyInterface extends Observable {
private DatagramSocket socket;
private MatchBrowserClientRunnable clientRunnable;
private List<HostGame> games = new ArrayList<>();
private Thread clientRunnableThread;
public MatchBrowserLobbyInterface() {
}
/**
* start receiving game info
* @param socket to receive from
*/
public void startReceivingHostData(DatagramSocket socket) {
this.socket = socket;
clientRunnable = new MatchBrowserClientRunnable(this, socket);
clientRunnableThread = new Thread(clientRunnable, "Socket: " + socket.toString());
clientRunnableThread.start();
}
/**
* Used by client to received race information from the server
* @param socket socket to read from
* @throws IOException socket error
*/
protected void receiveGameInfo(DatagramSocket socket) throws IOException {
byte[] data = new byte[64];
DatagramPacket receivedPacket = new DatagramPacket(data, 64);
socket.receive(receivedPacket);
HostedGamesRequestDecoder hostedGamesRequestDecoder = new HostedGamesRequestDecoder();
try {
HostGamesRequest message = (HostGamesRequest) hostedGamesRequestDecoder.decode(data);
games = message.getKnownGames();
// System.out.println(games.get(0).getIp());
setChanged();
notifyObservers();
} catch (InvalidMessageException e) {
System.err.println("HostedGamesRequestMessage could not be decoded");
}
}
/**
* Gets the host games
* @return games to be returned in list
*/
public List<HostGame> getGames() {
return games;
}
/**
* Used to close the socket and runnable once out of the lobby
*/
public void closeSocket() {
clientRunnableThread.interrupt();
this.socket.close();
}
}

@ -7,14 +7,12 @@ import network.MessageRouters.MessageRouter;
import network.Messages.AC35Data; import network.Messages.AC35Data;
import network.Messages.Enums.MessageType; import network.Messages.Enums.MessageType;
import network.Messages.Enums.RequestToJoinEnum; import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.LatestMessages;
import network.StreamRelated.MessageDeserialiser; import network.StreamRelated.MessageDeserialiser;
import network.StreamRelated.MessageSerialiser; import network.StreamRelated.MessageSerialiser;
import shared.model.RunnableWithFramePeriod; import shared.model.RunnableWithFramePeriod;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceController;
import visualiser.enums.ConnectionToServerState; import visualiser.enums.ConnectionToServerState;
import visualiser.gameController.ControllerClient; import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserRaceController;
import visualiser.model.VisualiserRaceState; import visualiser.model.VisualiserRaceState;
import java.io.IOException; import java.io.IOException;
@ -310,7 +308,7 @@ public class ServerConnection implements RunnableWithFramePeriod {
} }
//TODO create input controller here. RaceController should query for it, if it exists. //TODO create input controller here. RaceViewController should query for it, if it exists.
private void createPlayerInputController() { private void createPlayerInputController() {
this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend()); this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend());
@ -468,7 +466,15 @@ public class ServerConnection implements RunnableWithFramePeriod {
this.visualiserRaceControllerThread.interrupt(); this.visualiserRaceControllerThread.interrupt();
} }
if (this.socket != null) {
try {
this.socket.getInputStream().close();
this.socket.getOutputStream().close();
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//TODO input controller? //TODO input controller?

@ -15,7 +15,7 @@
<xs:element name="Participants"> <xs:element name="Participants">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element name="Yacht" maxOccurs="unbounded"> <xs:element name="Yacht" maxOccurs="unbounded" minOccurs="0">
<xs:complexType> <xs:complexType>
<xs:attribute name="SourceID" type="xs:int" use="required"/> <xs:attribute name="SourceID" type="xs:int" use="required"/>
<xs:attribute name="Entry" type="xs:string"/> <xs:attribute name="Entry" type="xs:string"/>

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ConnectionController">
<children>
<GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="182.0" minHeight="10.0" prefHeight="182.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="434.0" minHeight="10.0" prefHeight="434.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="174.0" minHeight="10.0" prefHeight="174.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="50.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="connectionTable" prefHeight="200.0" prefWidth="1080.0" GridPane.columnSpan="2" GridPane.rowIndex="1">
<columns>
<TableColumn fx:id="hostnameColumn" prefWidth="453.99998474121094" text="Host" />
<TableColumn fx:id="statusColumn" prefWidth="205.0" text="Status" />
</columns>
<GridPane.margin>
<Insets left="50.0" right="50.0" />
</GridPane.margin>
</TableView>
<Button mnemonicParsing="false" onAction="#checkConnections" text="Refresh" GridPane.halignment="RIGHT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets right="20.0" />
</GridPane.margin>
</Button>
<Button fx:id="connectButton" mnemonicParsing="false" onAction="#connectSocket" text="Connect" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Label text="Welcome to RaceVision" GridPane.columnSpan="2" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<GridPane GridPane.columnSpan="2" GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField fx:id="urlField" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="portField" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Button mnemonicParsing="false" onAction="#addConnection" text="Add New Connection" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Label text="Host Name:" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
<Label text="Port:" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
</children>
</GridPane>
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ImageView fitHeight="385.0" fitWidth="600.0" layoutY="-2.0" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="-1.9456787109375" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-2.0">
<image>
<Image url="@../images/game_controls.png" />
</image>
</ImageView>
</children>
</AnchorPane>

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="gameLobbyWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" style="-fx-background-color: rgba(100, 100, 100, 0.2);" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.InGameLobbyController">
<children>
<GridPane style="-fx-background-color: rgba(0, 0, 0, 0.3);" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Quit" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Label fx:id="countdownLabel" alignment="CENTER" contentDisplay="CENTER" textFill="WHITE" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2">
<font>
<Font size="16.0" />
</font>
</Label>
<GridPane fx:id="playerContainer" GridPane.columnSpan="3" GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label fx:id="playerLabel" text="No Player" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel4" text="No Player" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel2" text="No Player" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel3" text="No Player" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel5" text="No Player" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel6" text="No Player" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
</children>
</GridPane>
<Label text="Get Ready For The Next Race" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<font>
<Font name="Cabin-Bold" size="17.0" />
</font>
</Label>
</children>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="250.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
<AnchorPane fx:id="countdownTenPane" prefHeight="200.0" prefWidth="200.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="countdownTenText" alignment="CENTER" text="COUNTDOWN" textFill="#ee0000" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>
<Font name="System Bold" size="41.0" />
</font>
</Label>
<Label fx:id="preRacelabel" alignment="CENTER" text="Entering Race In:" textFill="RED" AnchorPane.bottomAnchor="80.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
</children>
</AnchorPane>
</children>
</AnchorPane>

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostGameController">
<children>
<GridPane layoutY="14.0" AnchorPane.bottomAnchor="-14.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="14.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="435.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<font>
<Font size="20.0" />
</font>
<GridPane.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</GridPane.margin>
</Button>
<Label text="Address: 127.0.0.1" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="TOP">
<font>
<Font size="17.0" />
</font>
</Label>
<Label text="Port: 4942" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<font>
<Font size="17.0" />
</font>
</Label>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Main Menu" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Button>
<ImageView fx:id="mapImage" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" />
<Button fx:id="previousButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#previousImage" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Button fx:id="nextButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#nextImage" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostController">
<children>
<GridPane layoutY="14.0" AnchorPane.bottomAnchor="-14.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="14.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="435.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<font>
<Font size="20.0" />
</font>
<GridPane.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</GridPane.margin>
</Button>
<Label text="Address: 127.0.0.1" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="TOP">
<font>
<Font size="17.0" />
</font>
</Label>
<Label text="Port: 4942" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<font>
<Font size="17.0" />
</font>
</Label>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Main Menu" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin></Button>
<ImageView fx:id="mapImage" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" />
<Button fx:id="previousButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#previousImage" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Button fx:id="nextButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#nextImage" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
@ -17,7 +12,7 @@
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<AnchorPane fx:id="lobbyWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.LobbyController"> <AnchorPane fx:id="lobbyWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.LobbyController">
<children> <children>
<GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints> <columnConstraints>

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="main" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.MainController">
<children>
<fx:include fx:id="race" source="race.fxml" />
<fx:include fx:id="start" source="start.fxml" />
<fx:include fx:id="connection" source="connect.fxml" />
<fx:include fx:id="finish" source="finish.fxml" />
<fx:include fx:id="host" source="hostgame.fxml" />
<fx:include fx:id="title" source="titleScreen.fxml" />
<fx:include fx:id="lobby" source="lobby.fxml" />
</children>
</AnchorPane>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save