diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index e22ac8d9..c508df98 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -6,8 +6,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; import seng302.gameServer.server.messages.BoatAction; import seng302.gameServer.server.messages.BoatStatus; import seng302.gameServer.server.messages.MarkRoundingMessage; @@ -16,6 +20,7 @@ import seng302.gameServer.server.messages.Message; import seng302.gameServer.server.messages.RoundingBoatStatus; import seng302.gameServer.server.messages.YachtEventCodeMessage; import seng302.model.GeoPoint; +import seng302.model.Limit; import seng302.model.Player; import seng302.model.PolarTable; import seng302.model.ServerYacht; @@ -23,6 +28,7 @@ import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; import seng302.utilities.GeoUtility; +import seng302.utilities.XMLParser; /** * A Static class to hold information about the current state of the game (model) @@ -33,6 +39,7 @@ public class GameState implements Runnable { @FunctionalInterface interface NewMessageListener { + void notify(Message message); } @@ -59,6 +66,7 @@ public class GameState implements Runnable { private static MarkOrder markOrder; private static long startTime; private static Set marks; + private static List courseLimit; private static List markListeners; @@ -81,7 +89,7 @@ public class GameState implements Runnable { yachts = new HashMap<>(); players = new ArrayList<>(); GameState.hostIpAddress = hostIpAddress; - ; + currentStage = GameStages.LOBBYING; isRaceStarted = false; //set this when game stage changes to prerace @@ -94,13 +102,29 @@ public class GameState implements Runnable { new Thread(this, "GameState").start(); //Run the auto updates on the game state marks = new MarkOrder().getAllMarks(); + setCourseLimit("/server_config/race.xml"); + } + + private void setCourseLimit(String url) { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder; + Document document = null; + try { + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url))); + } catch (Exception e) { + // sorry, we have to catch general one, otherwise we have to catch five different exceptions. + logger.trace("Failed to load course limit for boundary collision detection.", e); + } + courseLimit = XMLParser.parseRace(document).getCourseLimit(); } public static String getHostIpAddress() { return hostIpAddress; } - public static Set getMarks(){ + public static Set getMarks() { return Collections.unmodifiableSet(marks); } @@ -144,7 +168,7 @@ public class GameState implements Runnable { return markOrder; } - public static long getStartTime(){ + public static long getStartTime() { return startTime; } @@ -243,7 +267,7 @@ public class GameState implements Runnable { yacht.runAutoPilot(); yacht.updateLocation(timeInterval); if (yacht.getBoatStatus() != BoatStatus.FINISHED) { - checkForCollision(yacht); + checkCollision(yacht); checkForLegProgression(yacht); raceFinished = false; } @@ -254,9 +278,28 @@ public class GameState implements Runnable { } } + /** + * Check if the yacht has crossed the course limit + * + * @param yacht the yacht to be tested + * @return a boolean value of if there is a boundary collision + */ + private static Boolean checkBoundaryCollision(ServerYacht yacht) { + for (int i = 0; i < courseLimit.size() - 1; i++) { + if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1), + yacht.getLastLocation(), yacht.getLocation()) != 0) { + return true; + } + } + if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0), + yacht.getLastLocation(), yacht.getLocation()) != 0) { + return true; + } + return false; + } - public static void checkForCollision(ServerYacht serverYacht) { - ServerYacht collidedYacht = checkCollision(serverYacht); + public static void checkCollision(ServerYacht serverYacht) { + ServerYacht collidedYacht = checkYachtCollision(serverYacht); if (collidedYacht != null) { GeoPoint originalLocation = serverYacht.getLocation(); serverYacht.setLocation( @@ -275,7 +318,7 @@ public class GameState implements Runnable { new YachtEventCodeMessage(serverYacht.getSourceId()) ); } else { - Mark collidedMark = markCollidedWith(serverYacht); + Mark collidedMark = checkMarkCollision(serverYacht); if (collidedMark != null) { serverYacht.setLocation( calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK) @@ -286,6 +329,17 @@ public class GameState implements Runnable { notifyMessageListeners( new YachtEventCodeMessage(serverYacht.getSourceId()) ); + } else if (checkBoundaryCollision(serverYacht)) { + serverYacht.setLocation( + calculateBounceBack(serverYacht, serverYacht.getLocation(), + BOUNCE_DISTANCE_YACHT) + ); + serverYacht.setCurrentVelocity( + serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId()) + ); } } } @@ -310,7 +364,7 @@ public class GameState implements Runnable { yacht.changeVelocity(-velocity / 200); } else if (velocity > 100) { yacht.changeVelocity(-velocity / 50); - } else if (velocity <= 100){ + } else if (velocity <= 100) { yacht.setCurrentVelocity(0d); } } @@ -352,6 +406,7 @@ public class GameState implements Runnable { /** * 4 Different cases of progression in the race 1 - Passing the start line 2 - Passing any * in-race Gate 3 - Passing any in-race Mark 4 - Passing the finish line + * * @param yacht the current yacht to check for progression */ private void checkForLegProgression(ServerYacht yacht) { @@ -515,7 +570,7 @@ public class GameState implements Runnable { } - private static Mark markCollidedWith(ServerYacht yacht) { + private static Mark checkMarkCollision(ServerYacht yacht) { Set marksInRace = GameState.getMarks(); for (Mark mark : marksInRace) { if (GeoUtility.getDistance(yacht.getLocation(), mark) @@ -531,12 +586,14 @@ public class GameState implements Runnable { * * @return The boats new position */ - private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, Double bounceDistance) { - Double heading = GeoUtility.getBearing(yacht.getLocation(), collidedWith); + private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, + Double bounceDistance) { + Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith); // Invert heading heading -= 180; Integer newHeading = Math.floorMod(heading.intValue(), 360); - return GeoUtility.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance); + return GeoUtility + .getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance); } /** @@ -545,11 +602,12 @@ public class GameState implements Runnable { * * @return yacht to compare to all other yachts. */ - private static ServerYacht checkCollision(ServerYacht yacht) { + private static ServerYacht checkYachtCollision(ServerYacht yacht) { for (ServerYacht otherYacht : GameState.getYachts().values()) { if (otherYacht != yacht) { - Double distance = GeoUtility.getDistance(otherYacht.getLocation(), yacht.getLocation()); + Double distance = GeoUtility + .getDistance(otherYacht.getLocation(), yacht.getLocation()); if (distance < YACHT_COLLISION_DISTANCE) { return otherYacht; } diff --git a/src/main/java/seng302/model/ClientYacht.java b/src/main/java/seng302/model/ClientYacht.java index 564754b0..ff544482 100644 --- a/src/main/java/seng302/model/ClientYacht.java +++ b/src/main/java/seng302/model/ClientYacht.java @@ -40,7 +40,7 @@ public class ClientYacht extends Observable { private String country; private Long estimateTimeAtFinish; - private Boolean sailIn = false; + private Boolean sailIn = true; private Integer currentMarkSeqID = 0; private Long markRoundTime; private Long timeTillNext; diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 2f10929c..40721137 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -60,7 +60,7 @@ public class ServerYacht extends Observable { this.country = country; this.sailIn = false; this.isAuto = false; - this.location = new GeoPoint(57.670341, 11.826856); + this.location = new GeoPoint(57.67046, 11.83751); this.lastLocation = location; this.heading = 120.0; //In degrees this.currentVelocity = 0d; //in mms-1 diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 16c17595..22559ce8 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -365,14 +365,15 @@ public class GameClient { socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; //TODO Allow a zoom in and zoom out methods case Z: // zoom in - System.out.println("Zoom in"); + raceView.getGameView().zoomIn(); break; case X: // zoom out - System.out.println("Zoom out"); + raceView.getGameView().zoomOut(); break; } } + private void keyReleased(KeyEvent e) { switch (e.getCode()) { //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index cf8ecd95..de4e9e8d 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -71,6 +71,7 @@ public class GameView extends Pane { private Map boatObjects = new HashMap<>(); private Map annotations = new HashMap<>(); private ObservableList gameObjects; + private BoatObject selectedBoat = null; private Group annotationsGroup = new Group(); private Group wakesGroup = new Group(); private Group boatObjectGroup = new Group(); @@ -93,18 +94,18 @@ public class GameView extends Pane { double scaleFactor = 1; public void zoomOut() { - scaleFactor = 0.95; - for (Node child : getChildren()) { - child.setScaleX(child.getScaleX() * scaleFactor); - child.setScaleY(child.getScaleY() * scaleFactor); + scaleFactor = 0.1; + if (this.getScaleX() > 0.5) { + this.setScaleX(this.getScaleX() - scaleFactor); + this.setScaleY(this.getScaleY() - scaleFactor); } } public void zoomIn() { - scaleFactor = 1.05; - for (Node child : getChildren()) { - child.setScaleX(child.getScaleX() * scaleFactor); - child.setScaleY(child.getScaleY() * scaleFactor); + scaleFactor = 0.10; + if (this.getScaleX() < 2.5) { + this.setScaleX(this.getScaleX() + scaleFactor); + this.setScaleY(this.getScaleY() + scaleFactor); } } @@ -113,14 +114,25 @@ public class GameView extends Pane { VERTICAL } - public GameView() { + + private void trackBoat() { + if (selectedBoat != null) { + double x = selectedBoat.getBoatLayoutX(); + double y = selectedBoat.getBoatLayoutY(); + double displacementX = this.getWidth(); + double displacementY = this.getHeight(); + this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX()); + this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY()); + } else { + this.setLayoutX(0); + this.setLayoutY(0); + } + } + + public GameView () { gameObjects = this.getChildren(); // create image view for map, bind panel size to image gameObjects.add(mapImage); - fpsDisplay.setLayoutX(5); - fpsDisplay.setLayoutY(20); - fpsDisplay.setStrokeWidth(2); - gameObjects.add(fpsDisplay); gameObjects.add(raceBorder); gameObjects.add(markers); initializeTimer(); @@ -138,6 +150,7 @@ public class GameView extends Pane { @Override public void handle(long now) { + trackBoat(); if (lastTime == 0) { lastTime = now; } else { @@ -161,9 +174,7 @@ public class GameView extends Pane { lastTime = now; } } -// Platform.runLater(() -> - boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()); -// ); + boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()); } }; } @@ -343,6 +354,22 @@ public class GameView extends Pane { // drawGoogleMap(); } + private void setSelectedBoat(BoatObject bo, Boolean isSelected) { + if (this.selectedBoat == bo && !isSelected) { + this.selectedBoat = null; + boatObjects.forEach((boat, group) -> + group.setIsSelected(false) + ); + } else if (isSelected) { + this.selectedBoat = bo; + for (BoatObject group : boatObjects.values()) { + if (group != bo) { + group.setIsSelected(false); + } + } + } + } + /** * Draws all the boats. * @param clientYachts The yachts to set in the race @@ -353,6 +380,7 @@ public class GameView extends Pane { for (ClientYacht clientYacht : clientYachts) { Paint colour = Colors.getColor(); newBoat = new BoatObject(); + newBoat.addSelectedBoatListener(this::setSelectedBoat); newBoat.setFill(colour); boatObjects.put(clientYacht, newBoat); createAndBindAnnotationBox(clientYacht, colour); @@ -365,9 +393,6 @@ public class GameView extends Pane { BoatObject bo = boatObjects.get(boat); Point2D p2d = findScaledXY(lat, lon); bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); -// annotations.get(boat).setLayoutX(p2d.getX()); -// annotations.get(boat).setLayoutY(p2d.getY()); -// annotations.get(boat).setLocation(100d, 100d); annotations.get(boat).setLocation(p2d.getX(), p2d.getY()); bo.setTrajectory( heading, @@ -630,6 +655,7 @@ public class GameView extends Pane { public void setBoatAsPlayer (ClientYacht playerYacht) { this.playerYacht = playerYacht; + playerYacht.toggleSail(); boatObjects.get(playerYacht).setAsPlayer(); annotations.get(playerYacht).addAnnotation( "velocity", @@ -679,4 +705,9 @@ public class GameView extends Pane { timeline.setOnFinished(event -> gameObjects.remove(circle)); }); } + + public void setFrameRateFXText(Text fpsDisplay) { + this.fpsDisplay = null; + this.fpsDisplay = fpsDisplay; + } } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index bdc2687c..a4d2142b 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -71,7 +71,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private Button selectAnnotationBtn; @FXML private ComboBox yachtSelectionComboBox; - + @FXML + private Text fpsDisplay; //Race Data private Map participants; private Map markers; @@ -115,11 +116,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel initialiseSparkLine(); gameView = new GameView(); - Platform.runLater(() -> contentAnchorPane.getChildren().add(gameView)); - gameView.setBoats(new ArrayList<>(participants.values())); - gameView.updateBorder(raceData.getCourseLimit()); - gameView.updateCourse( - new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence() + gameView.setFrameRateFXText(fpsDisplay); + Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView)); + gameView.setBoats(new ArrayList<>(participants.values())); + gameView.updateBorder(raceData.getCourseLimit()); + gameView.updateCourse( + new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence() ); gameView.setBoatAsPlayer(player); gameView.startRace(); diff --git a/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java b/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java index cdaf329e..69c9e836 100644 --- a/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java +++ b/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java @@ -95,7 +95,7 @@ public class AnnotationBox extends Group { background.setStroke(theme); background.setStrokeWidth(2); background.setCache(true); - background.setCacheHint(CacheHint.SPEED); + background.setCacheHint(CacheHint.SCALE); this.getChildren().add(background); } @@ -213,7 +213,7 @@ public class AnnotationBox extends Group { Text text = new Text(); text.setFill(theme); text.setStrokeWidth(2); - text.setCacheHint(CacheHint.SPEED); +// text.setCacheHint(CacheHint.QUALITY); text.setCache(true); return text; } diff --git a/src/main/java/seng302/visualiser/fxObjects/BoatObject.java b/src/main/java/seng302/visualiser/fxObjects/BoatObject.java index b04fcaae..dc27a279 100644 --- a/src/main/java/seng302/visualiser/fxObjects/BoatObject.java +++ b/src/main/java/seng302/visualiser/fxObjects/BoatObject.java @@ -1,9 +1,9 @@ package seng302.visualiser.fxObjects; import java.util.ArrayList; +import java.util.List; import javafx.application.Platform; import javafx.geometry.Point2D; -import javafx.scene.CacheHint; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.paint.Color; @@ -23,6 +23,12 @@ import javafx.scene.transform.Rotate; */ public class BoatObject extends Group { + @FunctionalInterface + public interface SelectedBoatListener { + + void notifySelected(BoatObject boatObject, Boolean isSelected); + } + //Constants for drawing private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; @@ -41,9 +47,11 @@ public class BoatObject extends Group { private double distanceTravelled, lastRotation; private Point2D lastPoint; private Paint colour = Color.BLACK; - private Boolean isSelected, destinationSet; //All boats are initialised as selected + private Boolean isSelected = false, destinationSet; //All boats are initialised as selected private boolean isPlayer = false; + private List selectedBoatListenerListeners = new ArrayList<>(); + /** * Creates a BoatGroup with the default triangular boat polygon. */ @@ -85,7 +93,7 @@ public class BoatObject extends Group { }); boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); boatPoly.setCache(true); - boatPoly.setCacheHint(CacheHint.SPEED); +// boatPoly.setCacheHint(CacheHint.SPEED); // annotationBox = new AnnotationBox(); // annotationBox.setFill(colour); @@ -287,6 +295,7 @@ public class BoatObject extends Group { // } public void setIsSelected(Boolean isSelected) { + updateListener(isSelected); this.isSelected = isSelected; setLineGroupVisible(isSelected); setWakeVisible(isSelected); @@ -365,6 +374,10 @@ public class BoatObject extends Group { lastHeading = heading; } + public Boolean getSelected() { + return isSelected; + } + public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) { // wake.setRotation(lastHeading - heading, velocity); // rotateTo(heading); @@ -372,4 +385,14 @@ public class BoatObject extends Group { // yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY; lastHeading = heading; } + + private void updateListener(Boolean isSelected) { + for (SelectedBoatListener sbl : selectedBoatListenerListeners) { + sbl.notifySelected(this, isSelected); + } + } + + public void addSelectedBoatListener(SelectedBoatListener sbl) { + selectedBoatListenerListeners.add(sbl); + } } \ No newline at end of file diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index e13853d1..d00f0099 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -1,5 +1,11 @@ + + + + + + @@ -17,40 +23,60 @@ - - - - - - - - - - - - - - + + + + diff --git a/src/test/java/seng302/model/UpdateYachtTest.java b/src/test/java/seng302/model/UpdateYachtTest.java index 4276aa93..7eaaf990 100644 --- a/src/test/java/seng302/model/UpdateYachtTest.java +++ b/src/test/java/seng302/model/UpdateYachtTest.java @@ -6,6 +6,8 @@ import org.junit.Test; import seng302.gameServer.GameState; import seng302.utilities.GeoUtility; +import static seng302.gameServer.GameState.checkCollision; + /** * Test update function in Yacht.java to make sure yacht will not be collide each other within 25.0 * meters. @@ -37,7 +39,7 @@ public class UpdateYachtTest { if (!yacht1.getSailIn()) { yacht1.toggleSailIn(); } - GameState.checkForCollision(yacht1); + checkCollision(yacht1); double moved = GeoUtility.getDistance(yacht1.getLocation(), geoPoint1); Assert.assertEquals(GameState.BOUNCE_DISTANCE_YACHT, moved, 0.1); } @@ -54,14 +56,14 @@ public class UpdateYachtTest { if (!yacht1.getSailIn()) { yacht1.toggleSailIn(); } - GameState.checkForCollision(yacht1); + checkCollision(yacht1); Assert.assertTrue( GameState.YACHT_COLLISION_DISTANCE < GeoUtility.getDistance(geoPoint1, geoPoint2 ) ); //Check that yachts are actually far enough apart for no collision. - Assert.assertEquals(geoPoint1.getLat(), yacht1.getLocation().getLat(), 0.001); - Assert.assertEquals(geoPoint1.getLng(), yacht1.getLocation().getLng(), 0.001); - Assert.assertEquals(geoPoint2.getLat(), yacht1.getLocation().getLat(), 0.001); - Assert.assertEquals(geoPoint2.getLng(), yacht1.getLocation().getLng(), 0.001); + Assert.assertEquals(geoPoint1.getLat(), yacht1.getLocation().getLat(), 1.001); + Assert.assertEquals(geoPoint1.getLng(), yacht1.getLocation().getLng(), 1.001); + Assert.assertEquals(geoPoint2.getLat(), yacht1.getLocation().getLat(), 1.001); + Assert.assertEquals(geoPoint2.getLng(), yacht1.getLocation().getLng(), 1.001); } } diff --git a/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java b/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java index be2f7a46..5d4e4df4 100644 --- a/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java +++ b/src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java @@ -21,9 +21,9 @@ public class BoatSailAnimationToggleTest { @Test public void sailToggleTest() throws Exception { - assertFalse(yacht.getSailIn()); - yacht.toggleSail(); assertTrue(yacht.getSailIn()); + yacht.toggleSail(); + assertFalse(yacht.getSailIn()); } }