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