diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml index 444d727a..0d6cd401 100644 --- a/racevisionGame/pom.xml +++ b/racevisionGame/pom.xml @@ -84,6 +84,18 @@ 0.7 + + org.apache.httpcomponents + httpclient + 4.5.3 + + + + org.json + json + 20160810 + + diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index a9f55a7f..1e1b8966 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -80,6 +80,7 @@ public class Event { * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException { + PolarParser.parseNewPolars("mock/polars/acc_polars.csv"); this.mapIndex = mapIndex; String raceXMLFile; String boatsXMLFile = "mock/mockXML/boatTest.xml"; @@ -109,6 +110,7 @@ public class Event { } double windAngle = 300; + double windSpeed = 12; //Read XML files. try { @@ -119,6 +121,7 @@ public class Event { this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true); } else { this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false); + this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML, windSpeed, 15 * 60 * 1000); } this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); @@ -131,7 +134,6 @@ public class Event { this.xmlFileType = XMLFileType.Contents; this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); - PolarParser.parseNewPolars("mock/polars/acc_polars.csv"); //Parse the XML files into data sources. @@ -152,7 +154,7 @@ public class Event { WindGenerator windGenerator = new ShiftingWindGenerator( Bearing.fromDegrees(windAngle), - 12 + windSpeed ); MockRace mockRace = new MockRace( diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index 7f213a72..103e4d66 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -24,7 +24,6 @@ import java.util.logging.Logger; public class RaceServer { private MockRace race; private LatestMessages latestMessages; - private static RaceServer server; private List collisionEvents = new ArrayList<>(); @@ -50,16 +49,10 @@ 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. diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index 75024013..2c15e57f 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -1,17 +1,16 @@ package mock.xml; +import mock.model.NewPolars; import org.xml.sax.SAXException; import shared.dataInput.RaceXMLReader; import shared.enums.XMLFileType; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.XMLReaderException; +import shared.model.Bearing; 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.Race.*; import shared.xml.XMLUtilities; import javax.xml.bind.JAXBException; @@ -20,6 +19,8 @@ import java.io.IOException; import java.io.InputStream; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Comparator; /** * Helper Class for creating a Race XML @@ -125,6 +126,140 @@ public class RaceXMLCreator { } } + + /** + * Rotates the race in a specified direction. + * @param s xml file name or contents. + * @param windSpeed speed that the wind is at. + * @param milliseconds time the race should take at fastest + * @return the new xml file as a string + * @throws XMLReaderException if the xml is not readable + * @throws InvalidRaceDataException if the race is invalid + */ + public static String scaleRaceSize(String s, double windSpeed, double milliseconds) throws XMLReaderException, InvalidRaceDataException { + + try { + + XMLRace race = XMLUtilities.xmlToClass( + s, + RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), + XMLRace.class); + + scaleRace(race, windSpeed, milliseconds); + + return XMLUtilities.classToXML(race); + + } catch (ParserConfigurationException | IOException | SAXException | JAXBException e) { + throw new InvalidRaceDataException("Could not parse or marshall race data file.", e); + } + } + + /** + * Gets an estimate of how long a race will take using an average Speed + * @param race race to estimate + * @param averageSpeed average speed that the boats move at + * @return the estimated amount of time it will take a boat to finish the race (skewed to minimum). + */ + public static double getRaceLength(XMLRace race, double averageSpeed){ + double raceRoundingTime = 5000; //5 seconds to round a mark + double totalDistance = 0; //in nautical miles + XMLMark prevMark = null; + double avgSpeed = averageSpeed / 60 / 60; //knots is /hour + for (XMLCorner corner : race.getCompoundMarkSequence().getCorner()){ + int index = corner.getCompoundMarkID() - 1; + XMLCompoundMark cm = race.getCourse().getCompoundMark().get(index); + XMLMark mark = cm.getMark().get(0); + if (prevMark != null){ + totalDistance += getDistance(mark, prevMark); + } + prevMark = mark; + } + //total time = total dist / average speed + race extra rounding time * number of marks + double totalTime = totalDistance / avgSpeed * 1000 + + raceRoundingTime * race.getCompoundMarkSequence().getCorner().size(); + return totalTime; + } + + /** + * gets the destance between two marks + * @param a mark 1 + * @param b mark 2 + * @return + */ + private static double getDistance(XMLMark a, XMLMark b){ + GPSCoordinate coorda = new GPSCoordinate(a.getTargetLat(), a.getTargetLng()); + GPSCoordinate coordb = new GPSCoordinate(b.getTargetLat(), b.getTargetLng()); + return GPSCoordinate.calculateDistanceNauticalMiles(coorda, coordb); + } + + /** + * Scales the race based on the windspeed the race is running at and the amount of time it should be completed in. + * @param race Race to scale + * @param windSpeed windspeed of the race, this is used with the polars + * @param milliseconds milliseconds the race should take. + */ + private static void scaleRace(XMLRace race, double windSpeed, double milliseconds) { + GPSCoordinate center = getCenter(race); + //sort the compound marks + Collections.sort(race.getCompoundMarkSequence().getCorner(), (c1, c2) -> { + if (c1.getSeqID() < c2.getSeqID()) return -1; + if (c1.getSeqID() > c2.getSeqID()) return 1; + return 0; + }); + //sort compound mark id + Collections.sort(race.getCourse().getCompoundMark(), (c1, c2) -> { + if (c1.getCompoundMarkID() < c2.getCompoundMarkID()) return -1; + if (c1.getCompoundMarkID() > c2.getCompoundMarkID()) return 1; + return 0; + }); + //get the fastest time it would take. + double bestUpWindSpeed = NewPolars.setBestVMG(Bearing.fromDegrees(0), windSpeed, Bearing.fromDegrees(45)).getSpeed(); + double bestDownWindSpeed = NewPolars.setBestVMG(Bearing.fromDegrees(0), windSpeed, Bearing.fromDegrees(45)).getSpeed(); + double averageSpeed = (bestDownWindSpeed + bestUpWindSpeed) / 2; + double raceApproximateTime = getRaceLength(race, averageSpeed); + double scale = milliseconds / raceApproximateTime; + for (XMLCorner cm: race.getCompoundMarkSequence().getCorner()){ + int index = cm.getCompoundMarkID() - 1; + XMLCompoundMark mark = race.getCourse().getCompoundMark().get(index); + for (XMLMark m: mark.getMark()){ + scalePoint(m, center, scale); + } + } + for (XMLLimit limit: race.getCourseLimit().getLimit()){ + scalePoint(limit, center, scale); + } + } + + /** + * Scales a point from the the center(pivot) + * @param mark mark the scale + * @param center center as pivot + * @param scale scale to scale at. + */ + private static void scalePoint(XMLMark mark, GPSCoordinate center, double scale){ + double latDiff = mark.getTargetLat() - center.getLatitude(); + double longDiff = mark.getTargetLng() - center.getLongitude(); + double latScaled = latDiff * scale + center.getLatitude(); + double longScaled = longDiff * scale + center.getLongitude(); + mark.setTargetLat(latScaled); + mark.setTargetLng(longScaled); + } + + /** + * Scales a boundary from the center(pivot) + * @param limit boundary point + * @param center pivot + * @param scale scale + */ + private static void scalePoint(XMLLimit limit, GPSCoordinate center, double scale){ + double latDiff = limit.getLat() - center.getLatitude(); + double longDiff = limit.getLon() - center.getLongitude(); + double latScaled = latDiff * scale + center.getLatitude(); + double longScaled = longDiff * scale + center.getLongitude(); + limit.setLat(latScaled); + limit.setLon(longScaled); + } + /** * Converts a Race.CourseLimit.Limit to a GPS coordinate * @param limit limit to convert diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java index 86f02d91..8b395b1c 100644 --- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -157,11 +157,10 @@ public class LatestMessages extends Observable { return false; } else { - RaceServer.staticUpdateXML(); return true; } } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java index 25c956d9..d3e77be8 100644 --- a/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java @@ -183,6 +183,10 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { return sequenceNumber; } + public void setSequenceNumber(int sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + @Override public void incrementSequenceNumber() { sequenceNumber++; diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java index 7a8d2151..1579c0f0 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java @@ -22,7 +22,7 @@ public class EmptyBoatDataSource implements BoatDataSource { private final Map markerMap = new HashMap<>(); - private int sequenceNumber = 0; + private int sequenceNumber = -1; public EmptyBoatDataSource() { diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java index 676d7e70..e395e51f 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java @@ -84,7 +84,7 @@ public class EmptyRaceDataSource implements RaceDataSource { private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE; - private int sequenceNumber = 0; + private int sequenceNumber = -1; public EmptyRaceDataSource() { diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java index 1546972e..2fdf70c6 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java @@ -60,7 +60,7 @@ public class EmptyRegattaDataSource implements RegattaDataSource { - private int sequenceNumber = 0; + private int sequenceNumber = -1; public EmptyRegattaDataSource() { diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java index 41d60e5f..af274a9c 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -499,6 +499,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { return sequenceNumber; } + public void setSequenceNumber(int sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + @Override public void incrementSequenceNumber() { sequenceNumber++; diff --git a/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java index 36743105..3628c429 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java @@ -217,6 +217,10 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource { return sequenceNumber; } + public void setSequenceNumber(int sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + @Override public void incrementSequenceNumber() { sequenceNumber++; diff --git a/racevisionGame/src/main/java/shared/model/RaceState.java b/racevisionGame/src/main/java/shared/model/RaceState.java index b4a845e2..44debda8 100644 --- a/racevisionGame/src/main/java/shared/model/RaceState.java +++ b/racevisionGame/src/main/java/shared/model/RaceState.java @@ -148,9 +148,11 @@ public abstract class RaceState extends Observable{ * @param raceDataSource New race data source. */ public void setRaceDataSource(RaceDataSource raceDataSource) { - this.raceDataSource = raceDataSource; - this.getRaceClock().setStartingTime(raceDataSource.getStartDateTime()); - useLegsList(raceDataSource.getLegs()); + if ((this.raceDataSource == null) || (raceDataSource.getSequenceNumber() > this.raceDataSource.getSequenceNumber())) { + this.raceDataSource = raceDataSource; + this.getRaceClock().setStartingTime(raceDataSource.getStartDateTime()); + useLegsList(raceDataSource.getLegs()); + } } /** @@ -158,7 +160,9 @@ public abstract class RaceState extends Observable{ * @param boatDataSource New boat data source. */ public void setBoatDataSource(BoatDataSource boatDataSource) { - this.boatDataSource = boatDataSource; + if ((this.boatDataSource == null) || (boatDataSource.getSequenceNumber() > this.boatDataSource.getSequenceNumber())) { + this.boatDataSource = boatDataSource; + } } /** @@ -166,7 +170,9 @@ public abstract class RaceState extends Observable{ * @param regattaDataSource New regatta data source. */ public void setRegattaDataSource(RegattaDataSource regattaDataSource) { - this.regattaDataSource = regattaDataSource; + if ((this.regattaDataSource == null) || (regattaDataSource.getSequenceNumber() > this.regattaDataSource.getSequenceNumber())) { + this.regattaDataSource = regattaDataSource; + } } diff --git a/racevisionGame/src/main/java/shared/utils/JsonReader.java b/racevisionGame/src/main/java/shared/utils/JsonReader.java new file mode 100644 index 00000000..44e55c98 --- /dev/null +++ b/racevisionGame/src/main/java/shared/utils/JsonReader.java @@ -0,0 +1,70 @@ +package shared.utils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.*; +import java.net.URL; +import java.nio.charset.Charset; + +/** + * A helper class that has functions to read information from a url to json object. + */ +public class JsonReader { + + /** + * Reads all data from a Reader + * @param rd reader to read from + * @return string that the reader has currently read + * @throws IOException if the reader is invalid + */ + private static String readAll(Reader rd) throws IOException { + StringBuilder sb = new StringBuilder(); + int cp; + while ((cp = rd.read()) != -1) { + sb.append((char) cp); + } + return sb.toString(); + } + + /** + * Reads a Json Object from a URL + * @param url url to read from + * @return JSONObject that has been read + * @throws IOException if the reader cannot obtain information + * @throws JSONException if the read information is not json parsable. + */ + public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException { + InputStream is = new URL(url).openStream(); + try { + BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); + String jsonText = readAll(rd); + JSONObject json = new JSONObject(jsonText); + return json; + } catch (JSONException e) { + return null; + } finally { + is.close(); + } + } + + /** + * Reads a Json Array from a URL + * @param url url to read from + * @return JSONArray that has been read + * @throws IOException if the reader cannot obtain information + * @throws JSONException if the read information is not json parsable. + */ + public static JSONArray readJsonFromUrlArray(String url) throws IOException, JSONException { + InputStream is = new URL(url).openStream(); + try { + BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); + String jsonText = readAll(rd); + JSONArray json = new JSONArray(jsonText); + return json; + } finally { + is.close(); + } + } +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java index 7f56d745..ce58e8a1 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java @@ -41,8 +41,8 @@ public class BoatsXMLMessageCommand implements Command { @Override public void execute() { - - visualiserRace.setBoatDataSource(boatDataSource); - + if (boatDataSource.getSequenceNumber() > visualiserRace.getBoatDataSource().getSequenceNumber()) { + visualiserRace.setBoatDataSource(boatDataSource); + } } } diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java index 0b194674..dbf20544 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java @@ -38,7 +38,9 @@ public class RaceXMLMessageCommand implements Command { @Override public void execute() { - visualiserRace.setRaceDataSource(raceDataSource); + if (raceDataSource.getSequenceNumber() > visualiserRace.getRaceDataSource().getSequenceNumber()) { + visualiserRace.setRaceDataSource(raceDataSource); + } } } diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java index 6597e557..ea146d12 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java @@ -37,8 +37,9 @@ public class RegattaXMLMessageCommand implements Command { @Override public void execute() { - - visualiserRace.setRegattaDataSource(regattaDataSource); + if (regattaDataSource.getSequenceNumber() > visualiserRace.getRegattaDataSource().getSequenceNumber()) { + visualiserRace.setRegattaDataSource(regattaDataSource); + } } } diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java index b9b58f1c..c5e93d8e 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java @@ -34,17 +34,20 @@ public class XMLMessageCommandFactory { switch (message.getXmlMsgSubType()) { case BOAT: - BoatDataSource boatDataSource = new BoatXMLReader(message.getXmlMessage(), XMLFileType.Contents); + BoatXMLReader boatDataSource = new BoatXMLReader(message.getXmlMessage(), XMLFileType.Contents); + boatDataSource.setSequenceNumber(message.getSequenceNumber()); return new BoatsXMLMessageCommand(boatDataSource, visualiserRace); case RACE: - RaceDataSource raceDataSource = new RaceXMLReader(message.getXmlMessage(), XMLFileType.Contents); + RaceXMLReader raceDataSource = new RaceXMLReader(message.getXmlMessage(), XMLFileType.Contents); + raceDataSource.setSequenceNumber(message.getSequenceNumber()); return new RaceXMLMessageCommand(raceDataSource, visualiserRace); case REGATTA: - RegattaDataSource regattaDataSource = new RegattaXMLReader(message.getXmlMessage(), XMLFileType.Contents); + RegattaXMLReader regattaDataSource = new RegattaXMLReader(message.getXmlMessage(), XMLFileType.Contents); + regattaDataSource.setSequenceNumber(message.getSequenceNumber()); return new RegattaXMLMessageCommand(regattaDataSource, visualiserRace); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java index b532ded2..33583a38 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java @@ -11,6 +11,7 @@ import mock.exceptions.EventConstructionException; import network.Messages.Enums.RequestToJoinEnum; import visualiser.app.App; import visualiser.app.MatchBrowserSingleton; +import visualiser.network.HttpMatchBrowserHost; import visualiser.network.MatchBrowserInterface; import java.io.IOException; @@ -65,8 +66,11 @@ public class HostGameController extends Controller { try { App.game = new Event(false, currentMapIndex); App.gameType = currentMapIndex; + + HttpMatchBrowserHost matchBrowserHost = new HttpMatchBrowserHost(); + new Thread(matchBrowserHost).start(); connectSocket("localhost", 4942); - alertMatchBrowser(); + //alertMatchBrowser(); } catch (EventConstructionException e) { Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java index e6f6469e..38544ba6 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java @@ -15,11 +15,13 @@ import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; import javafx.scene.paint.PhongMaterial; import javafx.scene.shape.MeshView; import mock.app.Event; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RequestToJoinEnum; +import shared.model.Boat; import visualiser.app.App; import visualiser.gameController.ControllerClient; import visualiser.layout.SeaSurface; @@ -28,6 +30,7 @@ import visualiser.layout.View3D; import visualiser.model.VisualiserBoat; import visualiser.model.VisualiserRaceEvent; import visualiser.model.VisualiserRaceState; +import visualiser.network.HttpMatchBrowserHost; import java.io.IOException; import java.net.Socket; @@ -124,61 +127,69 @@ public class InGameLobbyController extends Controller { @Override public void onChanged(Change change) { - Platform.runLater( - () -> { - while (change.next()){ - if (change.wasAdded() || change.wasRemoved() || change.wasUpdated()){ - try{ - resetLobby(); - int count = 0; - int row = 0; - for (VisualiserBoat boat :visualiserRaceEvent.getVisualiserRaceState().getBoats()) { - View3D playerBoatToSet = new View3D(); - playerBoatToSet.setItems(subjects); - - playerContainer.add(playerBoatToSet, (count % 3) , row); - playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10)); - - SeaSurface sea = new SeaSurface(750, 200); - sea.setX(250); - sea.setZ(210); - subjects.add(sea); - - MeshView mesh = new MeshView(importer.getImport()); - PhongMaterial boatColorMat = new PhongMaterial(boat.getColor()); - mesh.setMaterial(boatColorMat); - Subject3D subject = new Subject3D(mesh,0); - subjects.add(subject); - - playerBoatToSet.setDistance(50); - playerBoatToSet.setYaw(45); - playerBoatToSet.setPitch(20); - - - - AnimationTimer rotate = new AnimationTimer() { - @Override - public void handle(long now) { - subject.setHeading(subject.getHeading().getAngle() + 0.1); - } - }; - rotate.start(); - - allPlayerLabels.get(count).setText(boat.getName()); - allPlayerLabels.get(count).toFront(); - count += 1; - if (count > 2){ - row = 1; - } - } - } - catch(ConcurrentModificationException e){ - } - } + Platform.runLater(() -> { + while (change.next()) { + if (change.wasAdded() || change.wasRemoved() || change.wasUpdated()) { + populateLobby(); + } + } + }); + } + + public void populateLobby() { + + try{ + resetLobby(); + int count = 0; + int row = 0; + + ArrayList copy = new ArrayList<>(visualiserRaceEvent.getVisualiserRaceState().getBoats()); + + for (VisualiserBoat boat : copy) { + View3D playerBoatToSet = new View3D(); + playerBoatToSet.setItems(subjects); + + playerContainer.add(playerBoatToSet, (count % 3) , row); + playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10)); + + SeaSurface sea = new SeaSurface(750, 200); + sea.setX(250); + sea.setZ(210); + subjects.add(sea); + + MeshView mesh = new MeshView(importer.getImport()); + PhongMaterial boatColorMat = new PhongMaterial(boat.getColor()); + mesh.setMaterial(boatColorMat); + Subject3D subject = new Subject3D(mesh,0); + subjects.add(subject); + + playerBoatToSet.setDistance(50); + playerBoatToSet.setYaw(45); + playerBoatToSet.setPitch(20); + + + + AnimationTimer rotate = new AnimationTimer() { + @Override + public void handle(long now) { + subject.setHeading(subject.getHeading().getAngle() + 0.1); } + }; + rotate.start(); + + allPlayerLabels.get(count).setText(boat.getName()); + allPlayerLabels.get(count).toFront(); + count += 1; + if (count > 2){ + row = 1; } - ); + } + } + catch(ConcurrentModificationException e){ + e.printStackTrace(); + } } + } /* private void populatePlayers(ListChangeListener.Change change){ @@ -191,7 +202,6 @@ public class InGameLobbyController extends Controller { //Initialises the race clock. initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState()); - //Starts the race countdown timer. countdownTimer(); } @@ -227,10 +237,9 @@ public class InGameLobbyController extends Controller { new AnimationTimer() { @Override public void handle(long arg0) { - //Get the current race status. RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); - + //Try catch for getting interval times try { long interval = ChronoUnit.MILLIS.between(visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getCurrentTime(), visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getStartingTime()); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java index 26d6447e..38816a0e 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java @@ -1,6 +1,7 @@ package visualiser.Controllers; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -11,8 +12,12 @@ import javafx.scene.layout.AnchorPane; import network.Messages.Enums.RequestToJoinEnum; import javafx.scene.media.AudioClip; import network.Messages.HostGame; +import org.json.JSONArray; +import org.json.JSONObject; +import shared.utils.JsonReader; import visualiser.app.MatchBrowserSingleton; import visualiser.model.RaceConnection; +import visualiser.network.HttpMatchBrowserClient; import visualiser.network.MatchBrowserLobbyInterface; import java.io.IOException; @@ -41,17 +46,17 @@ public class LobbyController extends Controller { private AudioClip sound; //the socket for match browser - private DatagramSocket udpSocket; - private MatchBrowserLobbyInterface matchBrowserLobbyInterface; + private HttpMatchBrowserClient httpMatchBrowserClient; public void initialize() { + httpMatchBrowserClient = new HttpMatchBrowserClient(); + new Thread(httpMatchBrowserClient, "Match Client").start(); // set up the connection table - connections = FXCollections.observableArrayList(); customConnections = FXCollections.observableArrayList(); //connections.add(new RaceConnection("localhost", 4942, "Local Game")); - lobbyTable.setItems(connections); + lobbyTable.setItems(httpMatchBrowserClient.connections); gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty()); hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty()); statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty()); @@ -68,7 +73,6 @@ public class LobbyController extends Controller { joinGameBtn.setDisable(true); spectateButton.setDisable(true); - this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket(); receiveMatchData(); } @@ -99,6 +103,7 @@ public class LobbyController extends Controller { * @throws IOException socket error */ public void connectSocket(RequestToJoinEnum joinType) throws IOException { + httpMatchBrowserClient.interrupt(); RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem(); Socket socket = new Socket(connection.getHostname(), connection.getPort()); InGameLobbyController iglc = (InGameLobbyController)loadScene("gameLobby.fxml"); @@ -124,7 +129,7 @@ public class LobbyController extends Controller { public void menuBtnPressed() throws IOException { sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); sound.play(); - matchBrowserLobbyInterface.closeSocket(); + httpMatchBrowserClient.interrupt(); loadScene("title.fxml"); } @@ -148,6 +153,7 @@ public class LobbyController extends Controller { } public void receiveMatchData(){ + /* matchBrowserLobbyInterface = new MatchBrowserLobbyInterface(); try { matchBrowserLobbyInterface.startReceivingHostData(new DatagramSocket(4941)); @@ -160,17 +166,24 @@ public class LobbyController extends Controller { matchBrowserLobbyInterface.addObserver(o); } catch (SocketException e) { System.err.println("Socket 4941 in use"); - } + }*/ } /** * Adds the games received from the server */ private void addServerGames() { - connections.clear(); - connections.addAll(customConnections); + httpMatchBrowserClient.connections.addAll(customConnections); + httpMatchBrowserClient.connections.addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + refreshBtnPressed(); + } + }); + + /* for (HostGame game : matchBrowserLobbyInterface.getGames()) { connections.add(new RaceConnection(game.getIp(), 4942, "Boat Game")); - } + }*/ } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java index a7c57aac..1e42ef3c 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java @@ -543,12 +543,14 @@ public class RaceViewController extends Controller { @Override public void handle(long now) { CompoundMark target = boat.getCurrentLeg().getEndCompoundMark(); - Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate()); + if (target != null) { + Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate()); - nextMarkArrow.setX(view3D.getPivot().getX()); - nextMarkArrow.setY(view3D.getPivot().getY()); - nextMarkArrow.setZ(view3D.getPivot().getZ() + 15); - nextMarkArrow.setHeading(headingToMark.degrees()); + nextMarkArrow.setX(view3D.getPivot().getX()); + nextMarkArrow.setY(view3D.getPivot().getY()); + nextMarkArrow.setZ(view3D.getPivot().getZ() + 15); + nextMarkArrow.setHeading(headingToMark.degrees()); + } } }; arrowToNextMark.start(); diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java index f546ec79..7781d2e3 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java @@ -8,6 +8,7 @@ import shared.dataInput.EmptyBoatDataSource; import shared.dataInput.EmptyRaceDataSource; import shared.dataInput.EmptyRegattaDataSource; import visualiser.gameController.ControllerClient; +import visualiser.network.HttpMatchBrowserHost; import visualiser.network.ServerConnection; import java.io.IOException; @@ -115,5 +116,8 @@ public class VisualiserRaceEvent { this.visualiserRaceServiceThread.interrupt(); this.serverConnectionThread.interrupt(); serverConnection.terminate(); + if (HttpMatchBrowserHost.httpMatchBrowserHost != null) { + HttpMatchBrowserHost.httpMatchBrowserHost.interrupt(); + } } } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java index a294d3fd..4a0316ee 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java @@ -94,7 +94,7 @@ public class VisualiserRaceState extends RaceState { super.setRaceDataSource(raceDataSource); if (getBoatDataSource() != null) { - this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), raceDataSource.getParticipants(), unassignedColors); + this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), getRaceDataSource().getParticipants(), unassignedColors); } initialiseLegCompletionOrder(); @@ -109,7 +109,7 @@ public class VisualiserRaceState extends RaceState { super.setBoatDataSource(boatDataSource); if (getRaceDataSource() != null) { - this.generateVisualiserBoats(this.boats, boatDataSource.getBoats(), getRaceDataSource().getParticipants(), unassignedColors); + this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), getRaceDataSource().getParticipants(), unassignedColors); } } diff --git a/racevisionGame/src/main/java/visualiser/network/HttpMatchBrowserClient.java b/racevisionGame/src/main/java/visualiser/network/HttpMatchBrowserClient.java new file mode 100644 index 00000000..c4a14bb9 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/network/HttpMatchBrowserClient.java @@ -0,0 +1,42 @@ +package visualiser.network; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.json.JSONArray; +import org.json.JSONObject; +import shared.utils.JsonReader; +import visualiser.model.RaceConnection; + +import java.io.IOException; + +/** + * Created by Gondr on 19/09/2017. + */ +public class HttpMatchBrowserClient extends Thread { + public ObservableList connections = FXCollections.observableArrayList(); + + /** + * Get all the matches that have been running in the past 5 seconds. + */ + @Override + public void run() { + while(!Thread.interrupted()) { + try { + JSONArray cons = JsonReader.readJsonFromUrlArray("http://api.umbrasheep.com/seng/get_matches/"); + connections.clear(); + + for (int i = 0; i < cons.length(); i++) { + JSONObject con = (JSONObject) cons.get(i); + //using "ip_address" will give their public ip + connections.add(new RaceConnection((String) con.get("local_ip"), con.getInt("port"), "Boat Game")); + } + + Thread.sleep(5000); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/racevisionGame/src/main/java/visualiser/network/HttpMatchBrowserHost.java b/racevisionGame/src/main/java/visualiser/network/HttpMatchBrowserHost.java new file mode 100644 index 00000000..331cc772 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/network/HttpMatchBrowserHost.java @@ -0,0 +1,115 @@ +package visualiser.network; + + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Creates an Http connection that hosts a game + */ +public class HttpMatchBrowserHost extends Thread { + private HttpClient httpClient; + private List params; + + public static HttpMatchBrowserHost httpMatchBrowserHost = null; + + /** + * Constructor, this sends out the creation message of the race. + * the thread should be run as soon as possible as the race is only valid for 5 seconds + * until it requires a heartbeat from the start function. + * @throws IOException if the hosting url is unreachable. + */ + public HttpMatchBrowserHost() throws IOException { + httpMatchBrowserHost = this; + httpClient = HttpClients.createDefault(); + + Enumeration e = NetworkInterface.getNetworkInterfaces(); + boolean matches = false; + String ip = ""; + Pattern ipPattern = Pattern.compile("192.168.1.*"); + while(e.hasMoreElements()) + { + if (matches){ + break; + } + NetworkInterface n = (NetworkInterface) e.nextElement(); + Enumeration ee = n.getInetAddresses(); + while (ee.hasMoreElements()) + { + InetAddress i = ee.nextElement(); + matches = ipPattern.matcher(i.getHostAddress()).matches(); + if (matches){ + ip = i.getHostAddress(); + //System.out.println(ip); + break; + } + } + } + + // Request parameters and other properties. + params = new ArrayList<>(2); + params.add(new BasicNameValuePair("ip", ip)); + params.add(new BasicNameValuePair("port", "4942")); + params.add(new BasicNameValuePair("magic", "Thomas and Seng")); + + sendHttp("http://api.umbrasheep.com/seng/registermatch/"); + } + + /** + * Sends a post to a server. + * @param domain url of to send to + * @throws IOException if the url is unreachable. + */ + public void sendHttp(String domain) throws IOException { + HttpPost httppost = new HttpPost(domain); + httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); + + //Execute and get the response. + HttpResponse response = httpClient.execute(httppost); + HttpEntity entity = response.getEntity(); + + if (entity != null) { + InputStream instream = entity.getContent(); + try { + // do something useful + } finally { + instream.close(); + } + } else { + throw new IOException("No Response from Host"); + } + } + + /** + * THe host starts sending out heartbeat messages every 2 seconds. + */ + @Override + public void run() { + while(!Thread.interrupted()){ + try { + sendHttp("http://api.umbrasheep.com/seng/keep_match_alive/"); + Thread.sleep(2000); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/racevisionGame/src/main/java/visualiser/network/MatchBrowserInterface.java b/racevisionGame/src/main/java/visualiser/network/MatchBrowserInterface.java index 02440612..e8beae5c 100644 --- a/racevisionGame/src/main/java/visualiser/network/MatchBrowserInterface.java +++ b/racevisionGame/src/main/java/visualiser/network/MatchBrowserInterface.java @@ -26,8 +26,9 @@ public class MatchBrowserInterface { public MatchBrowserInterface() { try {//132.181.16.13 is the ip of the CI as of 13/9/17 + //this.IPAddress = InetAddress.getByName("132.181.16.13"); //InetAddress.getLocalHost(); //this.IPAddress = InetAddress.getByName("umbrasheep.com"); //InetAddress.getLocalHost(); - this.IPAddress = InetAddress.getByName("132.181.16.13"); //InetAddress.getLocalHost(); + this.IPAddress = InetAddress.getByName("191.101.233.116"); //InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); }