Merge branch 'master' into story_speedometer

# Conflicts:
#	racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java
#	racevisionGame/src/main/resources/visualiser/scenes/newRaceView.fxml
This commit is contained in:
zwu18
2017-09-26 16:11:14 +13:00
136 changed files with 28971 additions and 367 deletions
@@ -63,6 +63,8 @@ public class Event {
*/
private SourceIdAllocator sourceIdAllocator;
private RaceLogic raceLogic;
private Thread raceThread;
private Thread connectionThread;
@@ -80,6 +82,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,16 +112,21 @@ public class Event {
}
double windAngle = 300;
double windSpeed = 12;
//Read XML files.
try {
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
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 = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true);
//This is the tutorial map.
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1);
} else {
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false);
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle);
this.raceXML = RaceXMLCreator.scaleRaceSize(raceXML, windSpeed, 15 * 60 * 1000);
}
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
@@ -131,7 +139,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 +159,7 @@ public class Event {
WindGenerator windGenerator = new ShiftingWindGenerator(
Bearing.fromDegrees(windAngle),
12
windSpeed
);
MockRace mockRace = new MockRace(
@@ -169,20 +176,20 @@ public class Event {
}
RaceLogic newRace = new RaceLogic(
this.raceLogic = new RaceLogic(
mockRace,
this.latestMessages,
this.compositeCommand);
this.raceThread = new Thread(newRace, "Event.Start()->RaceLogic thread");
this.raceThread = new Thread(raceLogic, "Event.Start()->RaceLogic thread");
raceThread.start();
//Create connection acceptor.
this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace());
this.sourceIdAllocator = new SourceIdAllocator(raceLogic.getRace());
try {
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, raceLogic);
} catch (IOException e) {
throw new EventConstructionException("Could not create ConnectionAcceptor.", e);
@@ -215,16 +222,13 @@ public class Event {
}
public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
long minutesToAdd = 10;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime));
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusMinutes(minutesToAdd)));
return raceXML;
}
@@ -239,4 +243,9 @@ public class Event {
return new HostGame(ip, 3779,(byte) 1,(byte) 1, RaceStatusEnum.PRESTART, (byte) 1, (byte) 1);
}
public RaceLogic getRaceLogic() {
return raceLogic;
}
}
@@ -61,12 +61,17 @@ public class MockRace extends RaceState {
*/
private Polars polars;
private ActiveObserverCommand activeObserverCommand;
private Map<Integer, ActiveObserverCommand> activeObserverCommands;
private long racePreStartTime = Constants.RacePreStartTime;
private long racePreparatoryTime = Constants.RacePreparatoryTime;
/**
* True if the race has been manually started, false otherwise.
*/
private boolean hasBeenStarted = false;
/**
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
* @param boatDataSource Data source for boat related data (yachts and marker boats).
@@ -82,7 +87,7 @@ public class MockRace extends RaceState {
this.setRaceDataSource(raceDataSource);
this.setRegattaDataSource(regattaDataSource);
this.activeObserverCommand = new ActiveObserverCommand();
this.activeObserverCommands = new HashMap<>();
this.polars = polars;
this.scaleFactor = timeScale;
@@ -127,6 +132,7 @@ public class MockRace extends RaceState {
getRaceDataSource().getParticipants().add(sourceID);
this.boats.add(mockBoat);
this.activeObserverCommands.put(boat.getSourceID(), new ActiveObserverCommand());
getRaceDataSource().incrementSequenceNumber();
@@ -157,6 +163,23 @@ public class MockRace extends RaceState {
}
/**
* Delays the start of the race, if needed, to ensure that the race doesn't start until host wants it to.
* If the time until start is less that 5 minutes, it is extended to 10 minutes.
*/
public void delayRaceStart() {
long timeToStart = getRaceDataSource().getStartDateTime().toInstant().toEpochMilli() - System.currentTimeMillis();
long fiveMinutesMilli = 5 * 60 * 1000;
long tenMinutesMilli = 10 * 60 * 1000;
if ((timeToStart < fiveMinutesMilli) && (timeToStart > 0) && !hasBeenStarted) {
startRace(tenMinutesMilli, false);
}
}
/**
* Updates the race status enumeration based on the current time.
*/
@@ -165,7 +188,6 @@ public class MockRace extends RaceState {
//The millisecond duration of the race. Negative means it hasn't started, so we flip sign.
long timeToStart = - this.getRaceClock().getDurationMilli();
if (timeToStart > racePreStartTime) {
//Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
@@ -195,6 +217,26 @@ public class MockRace extends RaceState {
this.racePreparatoryTime = racePreparatoryTime;
}
public long getRacePreparatoryTime() {
return racePreparatoryTime;
}
/**
* Starts the race in #timeToStartMilli milliseconds.
* @param timeToStartMilli Millseconds before starting the race.
* @param manualStart True if the race has been manually started, false otherwise.
*/
public void startRace(long timeToStartMilli, boolean manualStart) {
this.hasBeenStarted = manualStart;
ZonedDateTime startTime = ZonedDateTime.now().plus(timeToStartMilli, ChronoUnit.MILLIS);
getRaceDataSource().setStartDateTime(startTime);
getRaceDataSource().incrementSequenceNumber();
this.getRaceClock().setStartingTime(getRaceDataSource().getStartDateTime());
}
/**
* Sets the status of all boats in the race to RACING.
*/
@@ -711,11 +753,11 @@ public class MockRace extends RaceState {
super.setChanged();
}
public void addVelocityCommand(ObserverCommand c) {
this.activeObserverCommand.changeVelocityCommand(this, c);
public void addVelocityCommand(ObserverCommand c, int boatId) {
this.activeObserverCommands.get(boatId).changeVelocityCommand(this, c);
}
public void addAngularCommand(ObserverCommand c) {
this.activeObserverCommand.changeAngularCommand(this, c);
public void addAngularCommand(ObserverCommand c, int boatId) {
this.activeObserverCommands.get(boatId).changeAngularCommand(this, c);
}
}
@@ -81,6 +81,8 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
//Update race time.
race.updateRaceTime(currentTime);
race.delayRaceStart();
//Update the race status based on the current time.
race.updateRaceStatusEnum();
@@ -24,7 +24,6 @@ import java.util.logging.Logger;
public class RaceServer {
private MockRace race;
private LatestMessages latestMessages;
private static RaceServer server;
private List<YachtEvent> 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.
@@ -36,6 +36,7 @@ public class CollisionCommand extends ObserverCommand {
@Override
public void update(Observable o, Object arg) {
if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) {
boat.setVelocityDefault(false);
boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 3, azimuth));
} else {
race.deleteObserver(this);
@@ -13,7 +13,7 @@ public class SailsCommand extends ObserverCommand {
public SailsCommand(MockRace race, MockBoat boat, boolean sailsOut) {
super(race, boat);
race.addVelocityCommand(this);
race.addVelocityCommand(this, boat.getSourceID());
this.sailsOut = sailsOut;
}
@@ -37,15 +37,18 @@ public class SailsCommand extends ObserverCommand {
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);
if (!boat.isColliding()) {
boat.setVelocityDefault(false);
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);
}
}
}
}
@@ -22,7 +22,7 @@ public class TackGybeCommand extends ObserverCommand {
*/
public TackGybeCommand(MockRace race, MockBoat boat) {
super(race, boat);
race.addAngularCommand(this);
race.addAngularCommand(this, boat.getSourceID());
}
@Override
@@ -24,7 +24,7 @@ public class VMGCommand extends ObserverCommand {
*/
public VMGCommand(MockRace race, MockBoat boat) {
super(race, boat);
race.addAngularCommand(this);
race.addAngularCommand(this, boat.getSourceID());
}
@Override
@@ -20,7 +20,7 @@ public class WindCommand extends ObserverCommand {
*/
public WindCommand(MockRace race, MockBoat boat, boolean upwind) {
super(race, boat);
race.addAngularCommand(this);
race.addAngularCommand(this, boat.getSourceID());
this.direction = upwind? -1 : 1;
}
@@ -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
@@ -55,13 +56,12 @@ public class RaceXMLCreator {
* Rotates the race in a specified direction.
* @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.
* @param degrees degrees to rotate. -1 means don't rotate.
* @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 alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException {
public static String alterRaceToWind(String s, XMLFileType fileType, double degrees) throws XMLReaderException, InvalidRaceDataException {
RaceXMLReader reader = new RaceXMLReader(s, fileType);
@@ -72,11 +72,6 @@ public class RaceXMLCreator {
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
XMLRace.class);
if(tutorial){
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
} else {
setRaceXMLAtCurrentTimeToNow(race);
}
CompoundMark leewardGate = getLeewardGate(reader);
@@ -125,6 +120,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
@@ -157,11 +157,10 @@ public class LatestMessages extends Observable {
return false;
} else {
RaceServer.staticUpdateXML();
return true;
}
}
}
}
@@ -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++;
@@ -22,7 +22,7 @@ public class EmptyBoatDataSource implements BoatDataSource {
private final Map<Integer, Mark> markerMap = new HashMap<>();
private int sequenceNumber = 0;
private int sequenceNumber = -1;
public EmptyBoatDataSource() {
@@ -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() {
@@ -151,4 +151,9 @@ public class EmptyRaceDataSource implements RaceDataSource {
public void incrementSequenceNumber() {
sequenceNumber++;
}
@Override
public void setStartDateTime(ZonedDateTime time) {
raceStartTime = time;
}
}
@@ -60,7 +60,7 @@ public class EmptyRegattaDataSource implements RegattaDataSource {
private int sequenceNumber = 0;
private int sequenceNumber = -1;
public EmptyRegattaDataSource() {
@@ -67,6 +67,12 @@ public interface RaceDataSource {
*/
ZonedDateTime getStartDateTime();
/**
* Sets the start time/date of the race.
* @param time Time to start at.
*/
void setStartDateTime(ZonedDateTime time);
/**
* Returns the creation time/date of the race xml file.
* @return The race xml file's creation time.
@@ -499,8 +499,17 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
return sequenceNumber;
}
public void setSequenceNumber(int sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
@Override
public void setStartDateTime(ZonedDateTime time) {
raceStartTime = time;
}
}
@@ -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++;
@@ -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;
}
}
@@ -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();
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
@@ -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);
@@ -10,26 +10,30 @@ import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
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.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
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;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.layout.*;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import visualiser.network.HttpMatchBrowserHost;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.time.temporal.ChronoUnit;
@@ -67,13 +71,10 @@ public class InGameLobbyController extends Controller {
private Label playerLabel6;
@FXML
private Label countdownLabel;
private Button startButton;
@FXML
private AnchorPane countdownTenPane;
@FXML
private Label countdownTenText;
private Button quitButton;
private Event game;
@@ -124,61 +125,91 @@ 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<VisualiserBoat> 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());
// Subject3D subject = new Subject3D(mesh,0);
// subjects.add(subject);
Shape3D mesh = Assets3D.getBoat();
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
//mesh.setMaterial(boatColorMat);
Subject3D subject = new Subject3D(mesh, 0);
subjects.add(subject);
Sails3D sails3D = new Sails3D();
Subject3D sailsSubject = new Subject3D(sails3D, 0);
sails3D.setMaterial(boatColorMat);
subjects.add(sailsSubject);
sailsSubject.setXRot(0d);
//sails3D.startLuffing();
playerBoatToSet.setDistance(50);
playerBoatToSet.setYaw(45);
playerBoatToSet.setPitch(20);
if (boat.isClientBoat()) {
Shockwave boatHighlight = new Shockwave(10);
boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));
boatHighlight.setX(subject.getPosition().getX());
boatHighlight.setY(subject.getPosition().getY());
boatHighlight.setZ(subject.getPosition().getZ());
subjects.add(boatHighlight);
subject.getMesh().toFront();
}
AnimationTimer rotate = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(subject.getHeading().getAngle() + 0.1);
sailsSubject.setHeading(subject.getHeading().getAngle() + 0.1);
sailsSubject.setX(subject.getX());
sailsSubject.setZ(subject.getZ());
}
};
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){
@@ -188,38 +219,13 @@ public class InGameLobbyController extends Controller {
* Starts the race.
*/
private void startRace() {
//Initialises the race clock.
initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());
//Starts the race countdown timer.
countdownTimer();
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClock(VisualiserRaceState visualiserRace) {
//Remaining time.
initialiseRaceClockDuration(visualiserRace);
}
/**
* Initialises the race duration label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) {
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
countdownLabel.setText(newValue);
});
});
}
/**
* Countdown timer until race starts.
*/
@@ -227,23 +233,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());
if(interval<=10000){
countdownLabel.setVisible(false);
countdownTenPane.setVisible(true);
countdownTenText.setVisible(true);
}
countdownText(interval);
} catch (Exception e){
}
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
@@ -282,6 +274,8 @@ public class InGameLobbyController extends Controller {
this.visualiserRaceEvent.getVisualiserRaceState().getBoats().addListener(this.lobbyUpdateListener);
enableStartIfHost();
startRace();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
@@ -289,6 +283,22 @@ public class InGameLobbyController extends Controller {
}
}
/**
* Enables the start button if the client is the host of the game.
*/
private void enableStartIfHost() {
if (isHost) {
startButton.setVisible(true);
startButton.setDisable(false);
} else {
startButton.setVisible(false);
startButton.setDisable(true);
}
}
/**
* Menu button pressed. Prompt alert then return to menu
* @throws IOException socket erro
@@ -316,7 +326,7 @@ public class InGameLobbyController extends Controller {
* Start button pressed. Currently only prints out start
*/
public void startBtnPressed(){
//System.out.println("Should start the race. This button is only visible for the host");
App.game.getRaceLogic().getRace().startRace(App.game.getRaceLogic().getRace().getRacePreparatoryTime(), true);
}
public void joinSpecPressed(){
@@ -327,48 +337,5 @@ public class InGameLobbyController extends Controller {
//System.out.println("Empty race user pane pressed. Joining racers");
}
/**
* Countdown interval checker that updates the countdown text
* @param interval Countdown interval to check
*/
private void countdownText(long interval){
countdownTenPane.setVisible(false);
//Do nothing if 5 seconds or less to go
if (interval <=5000){
countdownTenPane.setVisible(false);
//countdownTenText.setText("5");
return;
}
//6 seconds left. Display 1 second for countdown
if (interval <=6000){
countdownTenText.setText("1");
return;
}
//7 seconds left. Display 2 seconds for countdown
if (interval <=7000){
countdownTenText.setText("2");
return;
}
//8 seconds left. Display 3 seconds for countdown
if (interval <=8000){
countdownTenText.setText("3");
return;
}
//9 seconds left. Display 4 seconds for countdown
if (interval <=9000){
countdownTenText.setText("4");
return;
}
//10 seconds left. Display 5 seconds for countdown
if (interval <=10000){
countdownTenText.setText("5");
return;
}
}
}
@@ -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<RaceConnection>() {
@Override
public void onChanged(Change<? extends RaceConnection> c) {
refreshBtnPressed();
}
});
/*
for (HostGame game : matchBrowserLobbyInterface.getGames()) {
connections.add(new RaceConnection(game.getIp(), 4942, "Boat Game"));
}
}*/
}
}
@@ -38,10 +38,7 @@ import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.*;
import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import visualiser.model.*;
import visualiser.utils.GPSConverter;
import java.io.IOException;
@@ -65,11 +62,14 @@ public class RaceViewController extends Controller {
private boolean isTutorial = false;
private String keyToPress;
private View3D view3D;
private WindCompass windCompass;
private ObservableList<Subject3D> viewSubjects;
private Gauge gauge;
private FGauge fGauge;
private long positionDelay = 1000;
private long positionTime = 0;
private ResizableRaceCanvas raceCanvas;
private boolean mapToggle = true;
/**
* Arrow pointing to next mark in third person
@@ -83,6 +83,7 @@ public class RaceViewController extends Controller {
// note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController;
private @FXML GridPane canvasBase;
private @FXML GridPane canvasBase1;
private @FXML SplitPane racePane;
private @FXML StackPane arrowPane;
private @FXML Label timer;
@@ -109,7 +110,6 @@ public class RaceViewController extends Controller {
* @param isHost is user a host
*/
public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
this.visualiserRace = visualiserRace;
this.raceState = visualiserRace.getVisualiserRaceState();
this.controllerClient = controllerClient;
@@ -119,6 +119,7 @@ public class RaceViewController extends Controller {
tutorialCheck();
initKeypressHandler();
initialiseRaceVisuals();
initialiseRaceCanvas();
}
/**
@@ -154,6 +155,9 @@ public class RaceViewController extends Controller {
// tab key
if (codeString.equals("TAB")){toggleTable();}
//map key
if (codeString.equals("M")){bigMap();}
// any key pressed
ControlKey controlKey = keyFactory.getKey(codeString);
if(controlKey != null) {
@@ -378,6 +382,9 @@ public class RaceViewController extends Controller {
view3D.addPointLight(pointLight);
canvasBase.add(view3D, 0, 0);
windCompass = new WindCompass(view3D, this.raceState.windProperty());
arrowPane.getChildren().add(windCompass);
// Set up projection from GPS to view
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
@@ -411,20 +418,111 @@ public class RaceViewController extends Controller {
viewSubjects.add(markModel);
}
for (VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
if (boat.isClientBoat()) {
Shockwave boatHighlight = new Shockwave(10);
boatHighlight.getMesh().setMaterial(new PhongMaterial(new Color(1, 1, 0, 0.1)));
viewSubjects.add(boatHighlight);
AnimationTimer highlightTrack = new AnimationTimer() {
@Override
public void handle(long now) {
boatHighlight.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
boatHighlight.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
}
};
highlightTrack.start();
}
}
// Position and add each boat to view
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
MeshView mesh;
if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
mesh = new MeshView(importer.getImport());
} else {
mesh = new MeshView(importerBurgerBoat.getImport());
}
// MeshView mesh;
// if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
// mesh = new MeshView(importer.getImport());
// } else {
// mesh = new MeshView(importerBurgerBoat.getImport());
// }
Shape3D mesh = Assets3D.getBoat();
PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
mesh.setMaterial(boatColorMat);
//mesh.setMaterial(boatColorMat);
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
viewSubjects.add(boatModel);
//add sail
Sails3D sails3D = new Sails3D();
Subject3D sailsSubject = new Subject3D(sails3D, 0);
sails3D.setMaterial(boatColorMat);
sailsSubject.setXRot(0d);
sailsSubject.setHeading(visualiserRace.getVisualiserRaceState().getWindDirection().degrees());
viewSubjects.add(sailsSubject);
AnimationTimer sailsFollowBoat = new AnimationTimer() {
double sailCurrent = visualiserRace.getVisualiserRaceState().getWindDirection().degrees();
boolean canLuff = true;
double turnRate = 5;
@Override
public void handle(long now) {
double sailDir;
//if sails are out etc
if (boat.isSailsOut()) {
double windDir = visualiserRace.getVisualiserRaceState().getWindDirection().degrees();
double windOffset = (360 - windDir + boat.getBearing().degrees()) % 360;
sailDir = windOffset / 180 * 270 + windDir + 180;
boolean leftOfWind = windOffset >= 180;
if (leftOfWind){
sailDir = -sailDir;
} else {
sailDir = windDir - sailDir;
}
} else {
sailDir = visualiserRace.getVisualiserRaceState().getWindDirection().degrees();
}
//get new place to move towards
double compA = ((sailCurrent - sailDir) % 360 + 360) % 360;//degrees right
if (compA > 180) compA = 360 - compA;
double compB = ((sailDir - sailCurrent) % 360 + 360) % 360;//degrees left
if (compB > compA){
if (compA > turnRate){
sailCurrent = ((sailCurrent - turnRate) % 360 + 360) % 360;
canLuff = false;
} else {
sailCurrent = sailDir;
canLuff = true;
}
} else {
if (compB > turnRate){
sailCurrent = ((sailCurrent + turnRate) % 360 + 360) % 360;
canLuff = false;
} else {
sailCurrent = sailDir;
canLuff = true;
}
}
sailsSubject.setHeading(sailCurrent);
if (canLuff) {
if (boat.isSailsOut()) {
if (sails3D.isLuffing()) {
sails3D.stopLuffing();
}
} else {
if (!sails3D.isLuffing()) {
sails3D.startLuffing();
}
}
}
sailsSubject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
sailsSubject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
}
};
sailsFollowBoat.start();
// Track this boat's movement with the new subject
AnimationTimer trackBoat = new AnimationTimer() {
@Override
@@ -483,7 +581,7 @@ public class RaceViewController extends Controller {
}
}
}
view3D.updateDistance(-10);
view3D.zoomIn();
break;
case "Zoom Out":
//Check if race is a tutorial
@@ -498,7 +596,7 @@ public class RaceViewController extends Controller {
}
}
}
view3D.updateDistance(10);
view3D.zoomOut();
break;
}
}
@@ -543,12 +641,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();
@@ -722,18 +822,9 @@ public class RaceViewController extends Controller {
e.printStackTrace();
}
} else {
raceCanvas.drawRace();
boatInfoTable.sort();
// try {
// System.out.println(raceState.getBoat(raceState.getPlayerBoatID()).getCurrentSpeed());
// gauge.setValue(raceState.getBoat(raceState.getPlayerBoatID()).getCurrentSpeed());
// for (VisualiserBoat boat : boatInfoTable.getItems()){
// if(raceState.getPlayerBoatID()==boat.getSourceID()){
// gauge.setTitle("Position: " + (boatInfoTable.getItems().indexOf(boat)+1));
// }
// }
// } catch (BoatNotFoundException e) {
// e.printStackTrace();
// }
}
//Return to main screen if we lose connection.
@@ -888,4 +979,48 @@ public class RaceViewController extends Controller {
}
}
/**
* Initialises the map
*/
private void initialiseRaceCanvas() {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(raceState);
//Set properties.
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase1.widthProperty());
raceCanvas.heightProperty().bind(canvasBase1.heightProperty());
//Draw it and show it.
raceCanvas.draw();
raceCanvas.setVisible(true);
//Add to scene.
canvasBase1.getChildren().add(0, raceCanvas);
}
private void bigMap(){
if (mapToggle){
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
raceCanvas.setFullScreen(true);
raceCanvas.setOpacity(0.6);
canvasBase1.getChildren().remove(raceCanvas);
canvasBase.getChildren().add(1, raceCanvas);
}else{
raceCanvas.widthProperty().bind(canvasBase1.widthProperty());
raceCanvas.heightProperty().bind(canvasBase1.heightProperty());
raceCanvas.setFullScreen(false);
canvasBase.getChildren().remove(raceCanvas);
canvasBase1.getChildren().add(0, raceCanvas);
}
mapToggle = !mapToggle;
}
}
@@ -25,6 +25,7 @@ import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import mock.app.Event;
import visualiser.layout.Assets3D;
public class App extends Application {
private static Stage stage;
@@ -113,6 +114,7 @@ public class App extends Application {
}
);
new Thread(boatTask).start();
Assets3D.loadAssets();
}
/**
@@ -0,0 +1,71 @@
package visualiser.layout;
import com.interactivemesh.jfx.importer.obj.ObjModelImporter;
import com.interactivemesh.jfx.importer.x3d.X3dModelImporter;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import visualiser.Controllers.HostGameController;
import java.net.URL;
/**
* Created by Gondr on 21/09/2017.
*/
public class Assets3D {
public static MeshView[] sails;
public static Subject3D windArrow;
public static Subject3D compass;
public static void loadAssets(){
loadSails();
loadWindArrow();
}
private static void loadSails(){
sails = new MeshView[40];
ObjModelImporter objModelImporter = new ObjModelImporter();
for (int i = 0; i < sails.length; i++){
String path = String.format("assets/Sails/V1.5 Sail_%06d.obj", i + 1);
URL asset = Assets3D.class.getClassLoader().getResource(path);
objModelImporter.read(asset);
if (objModelImporter.getImport().length > 0) {
sails[i] = objModelImporter.getImport()[0];
/*sails[i].setRotationAxis(new Point3D(1, 0, 0));
sails[i].setRotate(-90);*/
}
}
}
public static Shape3D getBoat(){
String path = "assets/V1.4 Boat.x3d";
return loadX3d(path);
}
private static void loadWindArrow(){
String compassPath = "assets/wind_compass.x3d";
compass = new Annotation3D(loadX3d(compassPath));
String arrowPath = "assets/wind_arrow.x3d";
windArrow = new Annotation3D(loadX3d(arrowPath));
}
private static Shape3D loadX3d(String path){
X3dModelImporter x3dModelImporter = new X3dModelImporter();
URL asset = Assets3D.class.getClassLoader().getResource(path);
x3dModelImporter.read(asset);
if (x3dModelImporter.getImport().length > 0) {
//if (x3dModelImporter.getImport()[0] instanceof MeshView) {
Group g = (Group)((Group)((Group) x3dModelImporter.getImport()[0]).getChildren().get(0)).getChildren().get(0);
Shape3D shape = (Shape3D)g.getChildren().get(0);
return shape;
/*} else {
System.err.println("Boat is not a Mesh View 0.0");
}*/
}
return null;
}
}
@@ -0,0 +1,40 @@
package visualiser.layout;
import javafx.animation.AnimationTimer;
import javafx.scene.shape.Mesh;
import javafx.scene.shape.MeshView;
/**
* Created by Gondr on 21/09/2017.
*/
public class Sails3D extends MeshView {
private int index = 0;
private boolean isLuffing = false;
public Sails3D() {
setMesh(Assets3D.sails[0].getMesh());
}
private AnimationTimer luff = new AnimationTimer() {
@Override
public void handle(long now) {
setMesh(Assets3D.sails[index].getMesh());
index = (index + 1) % 40;
}
};
public void startLuffing(){
luff.start();
isLuffing = true;
}
public void stopLuffing(){
luff.stop();
isLuffing = false;
setMesh(Assets3D.sails[0].getMesh());
}
public boolean isLuffing() {
return isLuffing;
}
}
@@ -13,5 +13,6 @@ public class Shockwave extends Subject3D {
super(new Cylinder(radius,0),0);
getMesh().getTransforms().add(new Rotate(-90, Rotate.X_AXIS));
getMesh().setMaterial(new PhongMaterial(new Color(0,0,0,0)));
getMesh().setMouseTransparent(true);
}
}
@@ -28,8 +28,13 @@ public class Subject3D {
*/
private Rotate heading;
private Rotate xRot;
private Rotate yRot;
private Scale scale;
/**
* Constructor for view subject wrapper
* @param mesh to be rendered
@@ -41,7 +46,9 @@ public class Subject3D {
this.scale = new Scale();
this.position = new Translate();
this.heading = new Rotate(0, Rotate.Y_AXIS);
this.mesh.getTransforms().addAll(position, scale, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
this.xRot = new Rotate(90, Rotate.X_AXIS);
this.yRot = new Rotate(180, Rotate.Y_AXIS);
this.mesh.getTransforms().addAll(position, scale, heading, xRot, yRot);
}
public Shape3D getMesh() {
@@ -70,15 +77,35 @@ public class Subject3D {
position.setX(x);
}
public double getX(){
return position.getX();
}
public void setY(double y) {
position.setY(y);
}
public double getY(){
return position.getY();
}
public void setZ(double z) {
position.setZ(z);
}
public double getZ(){
return position.getZ();
}
public void setHeading(double angle) {
heading.setAngle(angle);
}
public void setXRot(double angle){
xRot.setAngle(angle);
}
public void setYRot(double angle){
yRot.setAngle(angle);
}
}
@@ -3,7 +3,6 @@ package visualiser.layout;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.*;
@@ -78,11 +77,17 @@ public class View3D extends Pane {
/**
* Distance to switch from third person to bird's eye
*/
private final double THIRD_PERSON_LIMIT = 100;
private final double THIRD_PERSON_LIMIT = 500;
/**
* Distance to stop zoom
*/
private final double FIRST_PERSON_LIMIT = 2;
private final double ZOOM_IN_LIMIT = 30;
private final double ZOOM_OUT_LIMIT = 700;
private final double MAX_ZOOM_LIMIT = 1500;
private final double MAX_PITCH = 60; // birds eye view
private final double MIN_PITCH = 5; // third person view
private final double ZOOM_PER_KEYPRESS = 5; // distance changed per zoom
private double itemScale = 1;
/**
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
@@ -183,12 +188,9 @@ public class View3D extends Pane {
* Configures camera to third person view
*/
public void setThirdPerson() {
this.setDistance(THIRD_PERSON_LIMIT / 2);
this.setPitch(10);
for(Subject3D item: items) {
item.setScale(0.1);
}
this.setDistance(ZOOM_IN_LIMIT * 2);
adjustPitchForZoom();
adjustScaleForZoom();
}
/**
@@ -196,17 +198,14 @@ public class View3D extends Pane {
*/
public void setBirdsEye() {
this.setYaw(0);
this.setPitch(60);
for(Subject3D item: items) {
item.setScale(1);
}
this.setPitch(MAX_PITCH);
adjustScaleForZoom();
}
/**
* Stop camera from following the last selected subject
*/
private void untrackSubject() {
public void untrackSubject() {
if(target.get() != null) {
trackBoat.stop();
target.setValue(null);
@@ -217,7 +216,7 @@ public class View3D extends Pane {
* Set camera to follow the selected subject
* @param subject to track
*/
private void trackSubject(Subject3D subject) {
public void trackSubject(Subject3D subject) {
target.set(subject);
this.trackBoat = new AnimationTimer() {
@@ -267,17 +266,79 @@ public class View3D extends Pane {
* @param delta amount to change distance by
*/
public void updateDistance(double delta) {
double distance = -this.distance.getZ() + delta;
if(distance <= FIRST_PERSON_LIMIT) {
this.setDistance(FIRST_PERSON_LIMIT);
} else if(distance > THIRD_PERSON_LIMIT) {
this.setDistance(distance);
double newDistance = -this.distance.getZ() + delta;
if (target.get() == null){
if (newDistance > MAX_ZOOM_LIMIT){
setDistance(MAX_ZOOM_LIMIT);
} else {
setDistance(newDistance);
}
} else if(newDistance <= ZOOM_IN_LIMIT) {
setDistance(ZOOM_IN_LIMIT);
} else if (newDistance > ZOOM_OUT_LIMIT){
untrackSubject();
setBirdsEye();
setDistance(1050);
updatePivot(new Translate(250, 0, 210));
setYaw(0);
} else {
this.setDistance(distance);
setDistance(newDistance);
}
adjustPitchForZoom();
adjustScaleForZoom();
}
/**
* Adjusts the scale size of boats and markers as a user zooms in or out,
* to smooth the change between third person to birds eye view.
*/
private void adjustScaleForZoom(){
double itemScale = (((-distance.getZ() - (ZOOM_IN_LIMIT * 2)) /
((THIRD_PERSON_LIMIT - (ZOOM_IN_LIMIT * 2)) /
(1 - 0.1))) + 0.1);
// if zoomed right in
if (itemScale < 0.1){
itemScale = 0.1;
// if zoomed right out
} else if (itemScale > 1) {
itemScale = 1;
}
// update scale
for (Subject3D item : items) {
item.setScale(itemScale);
}
}
/**
* Adjusts the pitch as a user zooms in or out, to smooth the change
* between third person to birds eye view.
*/
private void adjustPitchForZoom(){
double pitch = (((-distance.getZ() - (ZOOM_IN_LIMIT*2)) /
((THIRD_PERSON_LIMIT - (ZOOM_IN_LIMIT*2)) / (MAX_PITCH -
MIN_PITCH))) + MIN_PITCH);
// if pitch is out of bounds, set it to the min/max
if (pitch < MIN_PITCH) {
pitch = MIN_PITCH;
} else if (pitch > MAX_PITCH){
pitch = MAX_PITCH;
}
setPitch(pitch);
}
/**
* Method to be called when the zoom in key is pressed.
*/
public void zoomIn(){
updateDistance(-ZOOM_PER_KEYPRESS);
}
/**
* Method to be called when the zoom out key is pressed.
*/
public void zoomOut(){
updateDistance(ZOOM_PER_KEYPRESS);
}
/**
@@ -288,6 +349,10 @@ public class View3D extends Pane {
this.yaw.setAngle(yaw);
}
public double getYaw(){
return this.yaw.getAngle();
}
/**
* Set elevation of camera
* @param pitch in degrees
@@ -296,6 +361,10 @@ public class View3D extends Pane {
this.pitch.setAngle(-pitch);
}
public double getPitch(){
return this.pitch.getAngle();
}
public void addAmbientLight(AmbientLight ambientLight) {
this.world.getChildren().add(ambientLight);
}
@@ -0,0 +1,50 @@
package visualiser.layout;
import javafx.animation.AnimationTimer;
import javafx.beans.property.Property;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import shared.model.Wind;
/**
* Created by Gondr on 25/09/2017.
*/
public class WindCompass extends View3D {
Subject3D compass;
Subject3D windArrow;
View3D view3D;
Property<Wind> wind;
AnimationTimer followView3D = new AnimationTimer() {
@Override
public void handle(long now) {
setYaw(180+ view3D.getYaw());
setPitch(30 - view3D.getPitch());
windArrow.setHeading(wind.getValue().getWindDirection().degrees());
}
};
public WindCompass(View3D view3D, Property<Wind> wind){
super();
this.wind = wind;
this.view3D = view3D;
this.followView3D.start();
ObservableList<Subject3D> subjects = FXCollections.observableArrayList();
this.setItems(subjects);
compass = Assets3D.compass;
windArrow = Assets3D.windArrow;
subjects.addAll(compass, windArrow);
this.setDistance(30);
this.setPitch(90);
this.setYaw(180);
}
public void setHeading(double heading){
windArrow.setHeading(heading);
}
public void setCompassPitch(double pitch){
setPitch(90d + pitch);
}
}
@@ -6,13 +6,11 @@ import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import network.Messages.Enums.BoatStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.enums.RoundingType;
import shared.model.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
@@ -44,13 +42,15 @@ public class ResizableRaceCanvas extends ResizableCanvas {
private VisualiserRaceState raceState;
private boolean annoName = true;
private boolean annoName = false;
private boolean annoAbbrev = true;
private boolean annoSpeed = true;
private boolean annoSpeed = false;
private boolean annoPath = true;
private boolean annoEstTime = true;
private boolean annoTimeSinceLastMark = true;
private boolean annoGuideLine = false;
private boolean annoEstTime = false;
private boolean annoTimeSinceLastMark = false;
private boolean annoGuideLine = true;
private boolean isFullScreen = false;
@@ -293,7 +293,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
List<VisualiserBoat> boats = new ArrayList<>(raceState.getBoats());
//Sort to ensure we draw boats in consistent order.
boats.sort(Comparator.comparingInt(Boat::getSourceID));
// boats.sort(Comparator.comparingInt(Boat::getSourceID));
//Current draw order:
// track points
@@ -302,29 +302,29 @@ public class ResizableRaceCanvas extends ResizableCanvas {
// text
//Track points.
for (VisualiserBoat boat : boats) {
drawTrack(boat);
if (isFullScreen){
for (VisualiserBoat boat : boats) {
drawTrack(boat);
}
//Text.
for (VisualiserBoat boat : boats) {
drawBoatText(boat);
}
}
//Wake.
for (VisualiserBoat boat : boats) {
//Only draw wake if they are currently racing.
if (boat.getStatus() == BoatStatusEnum.RACING) {
drawWake(boat);
}
}
// for (VisualiserBoat boat : boats) {
// //Only draw wake if they are currently racing.
// if (boat.getStatus() == BoatStatusEnum.RACING) {
// drawWake(boat);
// }
// }
//Boat.
for (VisualiserBoat boat : boats) {
drawBoat(boat);
}
//Text.
for (VisualiserBoat boat : boats) {
drawBoatText(boat);
}
}
/**
@@ -364,9 +364,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
gc.fillPolygon(x, y, x.length);
gc.restore();
if (boat.getSourceID() == ThisBoat.getInstance().getSourceID()) {
drawSails(boat);
}
// if (boat.getSourceID() == ThisBoat.getInstance().getSourceID()) {
// drawSails(boat);
// }
}
/**
@@ -605,7 +605,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Prepare to draw.
gc.setLineWidth(1);
gc.setFill(Color.AQUA);
gc.setFill(Color.DEEPSKYBLUE);
//Calculate the screen coordinates of the boundary.
@@ -645,19 +645,23 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Race boundary.
drawBoundary();
//Guiding Line
if (annoGuideLine){
//rounding lines
if (isFullScreen){
drawRoundingLines();
drawRaceLine();
}
//Guiding Line
// if (annoGuideLine){
// drawRaceLine();
// }
//Boats.
drawBoats();
//Marks.
drawMarks();
//TEMP
drawRoundingLines();
}
@@ -752,7 +756,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
gc.save();
gc.setLineWidth(2);
gc.setStroke(Color.MEDIUMAQUAMARINE);
gc.setStroke(getLineColor(legs.get(index)));
gc.beginPath();
gc.moveTo(startPath.getX(), startPath.getY());
@@ -772,7 +777,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
gc.save();
gc.setLineWidth(2);
gc.setStroke(Color.MEDIUMAQUAMARINE);
gc.setStroke(getLineColor(legs.get(index)));
gc.beginPath();
gc.moveTo(startPath.getX(), startPath.getY());
@@ -788,6 +793,21 @@ public class ResizableRaceCanvas extends ResizableCanvas {
}
}
private Color getLineColor(Leg leg) {
for (VisualiserBoat boat : raceState.getBoats()) {
if (boat.isClientBoat()) {
if (boat.getCurrentLeg() == leg) {
return Color.ORANGE;
} else {
return Color.MEDIUMAQUAMARINE;
}
}else{
return Color.MEDIUMAQUAMARINE;
}
}
return Color.MEDIUMAQUAMARINE;
}
private void drawArrowHead(GPSCoordinate start, GPSCoordinate end){
GraphCoordinate lineStart = this.map.convertGPS(start);
@@ -862,6 +882,11 @@ public class ResizableRaceCanvas extends ResizableCanvas {
}
public boolean isFullScreen() {
return isFullScreen;
}
public void setFullScreen(boolean fullScreen) {
isFullScreen = fullScreen;
}
}
@@ -137,6 +137,9 @@ public class VisualiserBoat extends Boat {
//Update the nextValidTime for the next track point.
nextValidTime = currentTimeMilli + trackPointTimeInterval;
if (track.size() > trackPointLimit) {
track.remove(0);
}
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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<RaceConnection> 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();
}
}
}
}
@@ -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<NameValuePair> 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<NetworkInterface> 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<InetAddress> 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();
}
}
}
}
@@ -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();
}