From 34872a822b30c0fb1a7853a89bff3e9c96fc580c Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 8 Apr 2017 17:49:50 +1200 Subject: [PATCH 01/23] Stripped back codebase to make to create basic model for streaming data Removed many classes involved with visualisation such as controllers and multiple fxmls. Now there is just one for debugging Merged in Boat updating pattern from team 27 #story[828] --- .../seng302/controllers/CanvasController.java | 283 ------------------ .../java/seng302/controllers/Controller.java | 180 +++++++++-- .../seng302/controllers/RaceController.java | 80 ----- .../controllers/RaceResultController.java | 37 --- .../controllers/RaceViewController.java | 280 ----------------- src/main/java/seng302/models/Boat.java | 74 ++++- src/main/java/seng302/models/Course.java | 54 ++++ src/main/java/seng302/models/Event.java | 148 --------- src/main/java/seng302/models/Leg.java | 165 +++++----- src/main/java/seng302/models/Race.java | 219 ++++++++------ .../java/seng302/models/TimelineInfo.java | 31 -- src/main/resources/config/course.xml | 4 +- src/main/resources/views/CanvasView.fxml | 7 - src/main/resources/views/FinishView.fxml | 55 ---- src/main/resources/views/MainView.fxml | 48 ++- src/main/resources/views/RaceView.fxml | 59 ---- src/test/java/seng302/BoatTest.java | 35 --- src/test/java/seng302/ColorsTest.java | 45 --- src/test/java/seng302/EventTest.java | 38 --- src/test/java/seng302/LegTest.java | 54 ---- src/test/java/seng302/RaceTest.java | 41 --- src/test/java/seng302/TestRaceTimer.java | 25 -- .../java/seng302/models/mark/MarkTest.java | 44 --- .../models/parsers/ConfigParserTest.java | 42 --- .../models/parsers/CourseParserTest.java | 60 ---- .../models/parsers/TeamsParserTest.java | 35 --- 26 files changed, 518 insertions(+), 1625 deletions(-) delete mode 100644 src/main/java/seng302/controllers/CanvasController.java delete mode 100644 src/main/java/seng302/controllers/RaceController.java delete mode 100644 src/main/java/seng302/controllers/RaceResultController.java delete mode 100644 src/main/java/seng302/controllers/RaceViewController.java create mode 100644 src/main/java/seng302/models/Course.java delete mode 100644 src/main/java/seng302/models/Event.java delete mode 100644 src/main/java/seng302/models/TimelineInfo.java delete mode 100644 src/main/resources/views/CanvasView.fxml delete mode 100644 src/main/resources/views/FinishView.fxml delete mode 100644 src/main/resources/views/RaceView.fxml delete mode 100644 src/test/java/seng302/BoatTest.java delete mode 100644 src/test/java/seng302/ColorsTest.java delete mode 100644 src/test/java/seng302/EventTest.java delete mode 100644 src/test/java/seng302/LegTest.java delete mode 100644 src/test/java/seng302/RaceTest.java delete mode 100644 src/test/java/seng302/TestRaceTimer.java delete mode 100644 src/test/java/seng302/models/mark/MarkTest.java delete mode 100644 src/test/java/seng302/models/parsers/ConfigParserTest.java delete mode 100644 src/test/java/seng302/models/parsers/CourseParserTest.java delete mode 100644 src/test/java/seng302/models/parsers/TeamsParserTest.java diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java deleted file mode 100644 index c0b20e26..00000000 --- a/src/main/java/seng302/controllers/CanvasController.java +++ /dev/null @@ -1,283 +0,0 @@ -package seng302.controllers; - -import javafx.animation.*; -import javafx.fxml.FXML; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.layout.AnchorPane; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import seng302.models.Boat; -import seng302.models.TimelineInfo; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkType; -import seng302.models.mark.SingleMark; - -import java.util.*; - -/** - * Created by ptg19 on 15/03/17. - * Modified by Haoming Yin (hyi25) on 20/3/2017. - */ -public class CanvasController { - - @FXML - private AnchorPane canvasPane; - - private RaceViewController raceViewController; - private ResizableCanvas canvas; - private GraphicsContext gc; - - private final double ORIGIN_LAT = 32.321504; - private final double ORIGIN_LON = -64.857063; - private final int SCALE = 16000; - - public void setup(RaceViewController raceViewController){ - this.raceViewController = raceViewController; - } - - public void initialize() { - canvas = new ResizableCanvas(); - canvasPane.getChildren().add(canvas); - // Bind canvas size to stack pane size. - canvas.widthProperty().bind(canvasPane.widthProperty()); - canvas.heightProperty().bind(canvasPane.heightProperty()); - gc = canvas.getGraphicsContext2D(); - - - // overriding the handle so that it can clean canvas and redraw boats and course marks - AnimationTimer timer = new AnimationTimer() { - private long lastUpdate = 0; - private long lastFpsUpdate = 0; - private int lastFpsCount = 0; - private int fpsCount = 0; - - @Override - public void handle(long now) { - if (true){ //if statement for limiting refresh rate if needed - gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); - gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); - drawCourse(); - drawBoats(); - drawFps(lastFpsCount); - - // If race has started, draw the boats and play the timeline - if (raceViewController.getRace().getRaceTime() > 1){ - raceViewController.playTimelines(); - } - // Race has not started, pause the timelines - else { - raceViewController.pauseTimelines(); - } - lastUpdate = now; - fpsCount ++; - if (now - lastFpsUpdate >= 1000000000){ - lastFpsCount = fpsCount; - fpsCount = 0; - lastFpsUpdate = now; - } - } - } - }; - timer.start(); - } - - class ResizableCanvas extends Canvas { - - public ResizableCanvas() { - // Redraw canvas when size changes. - widthProperty().addListener(evt -> draw()); - heightProperty().addListener(evt -> draw()); - } - - private void draw() { - double width = getWidth(); - double height = getHeight(); - - GraphicsContext gc = getGraphicsContext2D(); - gc.clearRect(0, 0, width, height); - } - - @Override - public boolean isResizable() { - return true; - } - - @Override - public double prefWidth(double height) { - return getWidth(); - } - - @Override - public double prefHeight(double width) { - return getHeight(); - } - } - - private void drawFps(int fps){ - if (raceViewController.isDisplayFps()){ - gc.setFill(Color.BLACK); - gc.setFont(new Font(14)); - gc.setLineWidth(3); - gc.fillText(fps + " FPS", 5, 20); - } - } - - /** - * Draws all the boats. - */ - private void drawBoats() { - Map timelineInfos = raceViewController.getTimelineInfos(); - for (Boat boat : timelineInfos.keySet()) { - TimelineInfo timelineInfo = timelineInfos.get(boat); - - boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); - - drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); - } - } - - /** - * Draw the wake line behind a boat - * @param gc The graphics context used for drawing the wake - * @param x the x position of the boat - * @param y the y position of the boat - * @param speed the speed of the boat - * @param color the color of the wake line - * @param heading the heading of the boat - */ - private void drawWake(GraphicsContext gc, double x, double y, double speed, Color color, double heading){ - double angle = Math.toRadians(heading); - speed = speed * 2; - Point newP = new Point(0, speed); - newP.rotate(angle); - - gc.setStroke(color); - gc.setLineWidth(1.0); - gc.strokeLine(x, y, newP.x + x, newP.y + y); - } - - /** - * Draws a boat with given (x, y) position in the given color - * - * @param lat - * @param lon - * @param color - * @param name - * @param speed - */ - private void drawBoat(double lat, double lon, Color color, String name, double speed, double heading) { - // Latitude - double x = (lon - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - lat) * SCALE; - - gc.setFill(color); - - if (raceViewController.isDisplayAnnotations()) { - // Set boat text - gc.setFont(new Font(14)); - gc.setLineWidth(3); - gc.fillText(name + ", " + speed + " knots", x + 15, y + 15); - } -// double diameter = 9; -// gc.fillOval(x, y, diameter, diameter); - double angle = Math.toRadians(heading); - - Point p1 = new Point(0, -15); // apex point - Point p2 = new Point(7, 4); // base point - Point p3 = new Point(-7, 4); // base point - p1.rotate(angle); - p2.rotate(angle); - p3.rotate(angle); - double[] xx = new double[] {p1.x + x, p2.x + x, x, p3.x + x}; - double[] yy = new double[] {p1.y + y, p2.y + y, y, p3.y + y}; - gc.fillPolygon(xx, yy, 4); - - if (raceViewController.isDisplayAnnotations()){ - drawWake(gc, x, y, speed, color, heading); - } - } - - /** - * Inner class for creating point so that you can rotate it around origin point. - */ - class Point { - - double x, y; - - Point (double x, double y) { - this.x = x; - this.y = y; - } - - void rotate(double angle) { - double oldX = x; - double oldY = y; - this.x = oldX * Math.cos(angle) - oldY * Math.sin(angle); - this.y = oldX * Math.sin(angle) + oldY * Math.cos(angle); - - } - } - - /** - * Draws the course. - */ - private void drawCourse() { - for (Mark mark : raceViewController.getRace().getCourse()) { - if (mark.getMarkType() == MarkType.SINGLE_MARK) { - drawSingleMark((SingleMark) mark, Color.BLACK); - } else if (mark.getMarkType() == MarkType.GATE_MARK) { - drawGateMark((GateMark) mark); - } - } - } - - /** - * Draw a given mark on canvas - * - * @param singleMark - */ - private void drawSingleMark(SingleMark singleMark, Color color) { - double x = (singleMark.getLongitude() - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - singleMark.getLatitude()) * SCALE; - - gc.setFill(color); - gc.fillRect(x,y,5.5,5.5); - } - - /** - * Draw a gate mark which contains two single marks - * - * @param gateMark - */ - private void drawGateMark(GateMark gateMark) { - Color color = Color.BLUE; - - if (gateMark.getName().equals("Start")){ - color = Color.RED; - } - - if (gateMark.getName().equals("Finish")){ - color = Color.GREEN; - } - - drawSingleMark(gateMark.getSingleMark1(), color); - drawSingleMark(gateMark.getSingleMark2(), color); - - GraphicsContext gc = canvas.getGraphicsContext2D(); - - gc.setStroke(color); - - // Convert lat/lon to x,y - double x1 = (gateMark.getSingleMark1().getLongitude()- ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - gateMark.getSingleMark1().getLatitude()) * SCALE; - - double x2 = (gateMark.getSingleMark2().getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - gateMark.getSingleMark2().getLatitude()) * SCALE; - - gc.setLineWidth(1); - gc.strokeLine(x1, y1, x2, y2); - } -} \ No newline at end of file diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index 1892d472..080e06ab 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,38 +1,176 @@ package seng302.controllers; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; +import javafx.scene.Group; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.Button; +import javafx.scene.paint.Color; +import seng302.models.Boat; +import seng302.models.Course; +import seng302.models.Race; +import seng302.models.mark.Mark; +import seng302.models.mark.MarkType; +import seng302.models.parsers.ConfigParser; +import seng302.models.parsers.CourseParser; +import seng302.models.parsers.TeamsParser; -import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.ResourceBundle; /** - * Created by michaelrausch on 21/03/17. + * Controller for main window for RaceVision + * Created by Kusal on 3/22/2017. */ public class Controller implements Initializable { - @FXML - private AnchorPane contentPane; - private void setContentPane(String jfxUrl){ - try{ - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); - } - catch(javafx.fxml.LoadException e){ - System.err.println(e.getCause()); - } - catch(IOException e){ - System.err.println(e); - } - } + private static final int MARKER_WIDTH = 10; + private static final int MARKER_HEIGHT = 10; + private static final double ORIGINAL_LAT = 32.321504; + private static final double ORIGINAL_LON = -64.857063; + + + @FXML + private Canvas courseCanvas; + + @FXML + private Group boatGroup; + + @FXML + private Button playPauseButton; + + + private Course thisCourse; + private ArrayList startingBoats; + private int raceDuration; + private Race race; + private boolean raceRunning = false; + + private CourseParser cp; + private TeamsParser tp; + private ConfigParser cop; @Override public void initialize(URL location, ResourceBundle resources) { - setContentPane("/views/RaceView.fxml"); + cp = new CourseParser("/config/course.xml"); + tp = new TeamsParser("/config/teams.xml"); + cop = new ConfigParser("/config/config.xml"); + + thisCourse = new Course(cp.getCourse()); + startingBoats = tp.getBoats(); + race = new Race(thisCourse.getMarks(), startingBoats, cop.getTimeScale(), this); + init(); + } + + /** + * Initialises a race on the screen after it has been loaded + */ + private void init() { + initMap(); + initBoats(); + playPauseButton.setDisable(false); + } + + /** + * Initialise the map by drawing it onto courseCanvas. + */ + private void initMap() { + //Create the boundary of the course displayed on the map + drawCourse(); + } + + + /** + * Draw the markers and gates onto the courseCanvas. + */ + private void drawCourse() { + GraphicsContext gc = courseCanvas.getGraphicsContext2D(); + gc.save(); + + for(Mark rp : thisCourse.getMarks()) { + if (rp.getMarkType().equals(MarkType.SINGLE_MARK)) { + gc.setFill(Color.GRAY); + gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); + } else if (rp.getMarkType().equals(MarkType.GATE_MARK)) { + gc.setFill(Color.GRAY); + gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); +// gc.fillOval(((OpenGate) rp).getDrawX2(), ((OpenGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT); + + gc.setLineWidth(2); + gc.setFill(Color.GREEN); + gc.setStroke(Color.GREEN); + } + gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); +// gc.fillOval(((ClosedGate) rp).getDrawX2(), ((ClosedGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT); +// gc.strokeLine(convertLongToX(rp.getLongitude()) + 5, convertLatToY(rp.getLatitude()) + 5, ((ClosedGate) rp).getDrawX2() + 5, ((ClosedGate) rp).getDrawY2() + 5); + } + gc.restore(); + } + + /** + * Places boats at starting line + */ + private void initBoats() { + +// int startingX = (convertLongToX(thisCourse.getMarks().get(0).getLongitude())).intValue(); +// int startingY = (convertLatToY(thisCourse.getMarks().get(0).getLongitude())).intValue(); + + int startingX = 50; + int startingY = 100; + + for(Boat boat : startingBoats) { + boat.moveBoatTo(startingX, startingY); + boatGroup.getChildren().add(boat.getBoatObject()); + boat.setCurrentLeg(race.getRaceLegs().get(0)); + boat.setHeading(race.getRaceLegs().get(0).getHeading()); + boat.setLegDistance(0d); + } + } + + + /** + * Starts and stops the race depending on whether or not it is already running + */ + public void playPause() { + if (!raceRunning) { + play(); + } else { + pause(); + } + } + + /** + * Plays the race and updates the play / pause button + */ + private void play() { + race.run(); + raceRunning = true; + playPauseButton.setText("Pause"); + + } + + /** + * Pauses the race and updates the play / pause button + */ + private void pause() { + race.pause(); + raceRunning = false; + playPauseButton.setText("Play"); + } + + + + private Double convertLongToX(Double lon) { + return (lon - ORIGINAL_LON) * thisCourse.getDistanceScaleFactor(); + } + + private Double convertLatToY(Double lat) { + return (ORIGINAL_LAT - lat) * thisCourse.getDistanceScaleFactor(); + } + + public Button getPlayPauseButton() { + return playPauseButton; } } diff --git a/src/main/java/seng302/controllers/RaceController.java b/src/main/java/seng302/controllers/RaceController.java deleted file mode 100644 index b5fa2847..00000000 --- a/src/main/java/seng302/controllers/RaceController.java +++ /dev/null @@ -1,80 +0,0 @@ -package seng302.controllers; - -import seng302.models.Boat; -import seng302.models.Race; -import seng302.models.parsers.ConfigParser; -import seng302.models.parsers.CourseParser; -import seng302.models.parsers.TeamsParser; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Random; - -/** - * Created by zyt10 on 17/03/17. - * run before CanvasController to initialize race events - * the CanvasController then uses the event data to make the animations - */ -public class RaceController { - Race race = null; - - public void initializeRace() { - String raceConfigFile = "/config/config.xml"; - String teamsConfigFile = "/config/teams.xml"; - - try { - race = createRace(raceConfigFile, teamsConfigFile); - } catch (Exception e) { - System.out.println("There was an error creating the race."); - } - - if (race != null) { - race.startRace(); - } else { - System.out.println("There was an error creating the race. Exiting."); - } - } - - public Race createRace(String configFile, String teamsConfigFile) throws Exception { - Race race = new Race(); - - // Read team names from file - TeamsParser tp = new TeamsParser(teamsConfigFile); - - // Read course from file - ConfigParser config = new ConfigParser(configFile); - - ArrayList boatNames = new ArrayList<>(); - ArrayList teams = tp.getBoats(); - - //get race size - int numberOfBoats = teams.size(); - - //get time scale - double timeScale = config.getTimeScale(); - race.setTimeScale(timeScale); - - for (Boat boat : teams) { - boatNames.add(boat.getTeamName()); - race.addBoat(boat); - } - - // Shuffle team names - long seed = System.nanoTime(); - Collections.shuffle(boatNames, new Random(seed)); - - if (numberOfBoats > Array.getLength(boatNames.toArray())) { - return null; - } - - CourseParser course = new CourseParser("/config/course.xml"); - race.addCourse(course.getCourse()); - - return race; - } - - public Race getRace() { - return race; - } -} diff --git a/src/main/java/seng302/controllers/RaceResultController.java b/src/main/java/seng302/controllers/RaceResultController.java deleted file mode 100644 index 7378fa68..00000000 --- a/src/main/java/seng302/controllers/RaceResultController.java +++ /dev/null @@ -1,37 +0,0 @@ -package seng302.controllers; - -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import seng302.models.Race; - -import java.net.URL; -import java.util.ResourceBundle; - -/** - * Created by ptg19 on 20/03/17. - */ -public class RaceResultController implements Initializable{ - @FXML private AnchorPane window; - @FXML private VBox resultsVBox; - private Race race; - - RaceResultController(Race race){ - this.race = race; - } - - @Override - public void initialize(URL location, ResourceBundle resources) { - int boatPosition = this.race.getFinishedBoats().length; - - for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){ - resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getTeamName())); - boatPosition--; - } - - - - } -} diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java deleted file mode 100644 index 965fbc7d..00000000 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ /dev/null @@ -1,280 +0,0 @@ -package seng302.controllers; - -import javafx.animation.Animation; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.CheckBox; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import javafx.util.Duration; -import seng302.models.Boat; -import seng302.models.Event; -import seng302.models.Race; -import seng302.models.TimelineInfo; -import seng302.models.parsers.ConfigParser; - -import java.io.IOException; -import java.util.*; - -/** - * Created by ptg19 on 29/03/17. - */ -public class RaceViewController { - @FXML - private VBox positionVbox; - @FXML - private CheckBox toggleAnnotation, toggleFps; - @FXML - private Text timerLabel; - @FXML - private AnchorPane contentAnchorPane; - @FXML - private Text windArrowText, windDirectionText; - @FXML - private CanvasController includedCanvasController; - - private boolean displayAnnotations; - private boolean displayFps; - private Timeline timerTimeline; - private Map timelineInfos = new HashMap<>(); - private ArrayList boatOrder = new ArrayList<>(); - private Race race; - - public void initialize() { - includedCanvasController.setup(this); - RaceController raceController = new RaceController(); - raceController.initializeRace(); - race = raceController.getRace(); - - initializeTimer(); - initializeSettings(); - try{ - initializeTimelines(); - } - catch (Exception e){ - e.printStackTrace(); - } - - //set wind direction!!!!!!! can't find another place to put my code --haoming - double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); - windDirectionText.setText(String.format("%.1f°", windDirection)); - windArrowText.setRotate(windDirection); - } - - private void initializeSettings(){ - displayAnnotations = true; - displayFps = true; - - toggleAnnotation.selectedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { - displayAnnotations = !displayAnnotations; - } - }); - toggleFps.selectedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { - displayFps = !displayFps; - } - }); - } - - private void initializeTimer(){ - timerTimeline = new Timeline(); - timerTimeline.setCycleCount(Timeline.INDEFINITE); - // Run timer update every second - timerTimeline.getKeyFrames().add( - new KeyFrame(Duration.seconds(1), - event -> { - // Stop timer if race is finished - if (this.race.isRaceFinished()) { - this.timerTimeline.stop(); - } else { - timerLabel.setText(convertTimeToMinutesSeconds(race.getRaceTime())); - this.race.incrementRaceTime(); - } - }) - ); - - // Start the timer - timerTimeline.playFromStart(); - } - - /** - * Generates time line for each boat, and stores time time into timelineInfos hash map - */ - private void initializeTimelines() { - HashMap boat_events = race.getEvents(); - - for (Boat boat : boat_events.keySet()) { - // x, y are the real time coordinates - DoubleProperty x = new SimpleDoubleProperty(); - DoubleProperty y = new SimpleDoubleProperty(); - - List keyFrames = new ArrayList<>(); - List events = boat_events.get(boat); - - // iterates all events and convert each event to keyFrame, then add them into a list - for (Event event : events) { - if (event.getIsFinishingEvent()) { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } else { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished ->{ - handleEvent(event); - boat.setHeading(event.getBoatHeading()); - }, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } - } - timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); - } - setRaceDuration(); - } - - private void setRaceDuration(){ - Double maxDuration = 0.0; - Timeline maxTimeline = null; - - for (TimelineInfo timelineInfo : timelineInfos.values()) { - - Timeline timeline = timelineInfo.getTimeline(); - if (timeline.getTotalDuration().toMillis() >= maxDuration) { - maxDuration = timeline.getTotalDuration().toMillis(); - maxTimeline = timeline; - } - - // Timelines are paused by default - timeline.play(); - timeline.pause(); - } - - maxTimeline.setOnFinished(event -> { - race.setRaceFinished(); - loadRaceResultView(); - }); - } - - /** - * Play each boats timerTimeline - */ - public void playTimelines(){ - for (TimelineInfo timelineInfo : timelineInfos.values()){ - Timeline timeline = timelineInfo.getTimeline(); - - if (timeline.getStatus() == Animation.Status.PAUSED){ - timeline.play(); - } - } - } - - /** - * Pause each boats timerTimeline - */ - public void pauseTimelines(){ - for (TimelineInfo timelineInfo : timelineInfos.values()){ - Timeline timeline = timelineInfo.getTimeline(); - - if (timeline.getStatus() == Animation.Status.RUNNING){ - timeline.pause(); - } - } - } - - /** - * Display the list of boats in the order they finished the race - */ - private void loadRaceResultView() { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); - loader.setController(new RaceResultController(race)); - - try { - contentAnchorPane.getChildren().removeAll(); - contentAnchorPane.getChildren().clear(); - contentAnchorPane.getChildren().addAll((Pane) loader.load()); - - } catch (javafx.fxml.LoadException e) { - System.err.println(e.getCause()); - } catch (IOException e) { - System.err.println(e); - } - } - - public void handleEvent(Event event) { - Boat boat = event.getBoat(); - boatOrder.remove(boat); - boat.setMarkLastPast(event.getMarkPosInRace()); - boatOrder.add(boat); - boatOrder.sort(new Comparator() { - @Override - public int compare(Boat b1, Boat b2) { - return b2.getMarkLastPast() - b1.getMarkLastPast(); - } - }); - showOrder(); - } - - private void showOrder() { - positionVbox.getChildren().clear(); - positionVbox.getChildren().removeAll(); - - for (Boat boat : boatOrder) { - positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots")); - } - } - - /** - * Convert seconds to a string of the format mm:ss - * - * @param time the time in seconds - * @return a formatted string - */ - public String convertTimeToMinutesSeconds(int time) { - if (time < 0) { - return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60); - } - return String.format("%02d:%02d", time / 60, time % 60); - } - - public void stopTimer() { - timerTimeline.stop(); - } - public void startTimer() { - timerTimeline.play(); - } - - public boolean isDisplayFps() { - return displayFps; - } - - public boolean isDisplayAnnotations() { - return displayAnnotations; - } - - public Race getRace() { - return race; - } - - public Map getTimelineInfos() { - return timelineInfos; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index 0973ad38..e081efb9 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -1,28 +1,36 @@ package seng302.models; import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; /** * Represents a boat in the race. */ public class Boat { + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + private static final double VELOCITY_WAKE_RATIO = 1/2d; //Ratio for deciding how long the wake will be wrt velocity + private String teamName; // The name of the team, this is also the name of the boat - private double velocity; // In meters/second - private double lat; // Boats position - private double lon; // - - private double distanceToNextMark; + private Double velocity; // In meters/second + private Double lat; // Boats position + private Double lon; // - + private Double legDistance; private Color color; - private int markLastPast; - private double heading; + private Leg currentLeg; + private Double heading; private String shortName; + private Polygon boatObject = new Polygon(); + public Boat(String teamName) { this.teamName = teamName; - this.velocity = 10; // Default velocity + this.velocity = 10d; // Default velocity this.lat = 0.0; this.lon = 0.0; - this.distanceToNextMark = 0.0; + this.legDistance = 0.0; this.shortName = ""; } @@ -36,9 +44,12 @@ public class Boat { public Boat(String teamName, double boatVelocity, String shortName) { this.teamName = teamName; this.velocity = boatVelocity; - this.distanceToNextMark = 0.0; + this.legDistance = 0.0; this.color = Colors.getColor(); this.shortName = shortName; + this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0, + BOAT_WIDTH, BOAT_HEIGHT, + 0.0, BOAT_HEIGHT); } /** @@ -88,8 +99,12 @@ public class Boat { this.lon = lon; } - public void setDistanceToNextMark(double distance){ - this.distanceToNextMark = distance; + public Double getLegDistance() { + return legDistance; + } + + public void setLegDistance(double legDistance){ + this.legDistance = legDistance; } public double getLatitude(){ @@ -108,12 +123,12 @@ public class Boat { return Math.round((this.velocity * 1.94384) * 100d) / 100d; } - public void setMarkLastPast(int markLastPast) { - this.markLastPast = markLastPast; + public Leg getCurrentLeg() { + return currentLeg; } - public int getMarkLastPast() { - return markLastPast; + public void setCurrentLeg(Leg currentLeg) { + this.currentLeg = currentLeg; } public void setHeading(double heading){ @@ -127,4 +142,33 @@ public class Boat { public String getShortName(){ return this.shortName; } + + + /** + * Moves the boat and its children annotations from its current coordinates by specified amounts. + * @param x The amount to move the X coordinate by + * @param y The amount to move the Y coordinate by + */ + void moveBoatBy(Double x, Double y) { + boatObject.setLayoutX(boatObject.getLayoutX() + x); + boatObject.setLayoutY(boatObject.getLayoutY() + y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + } + + /** + * Moves the boat and its children annotations to coordinates specified + * @param x The X coordinate to move the boat to + * @param y The Y coordinate to move the boat to + */ + public void moveBoatTo(int x, int y) { + boatObject.setLayoutX(x); + boatObject.setLayoutY(y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + } + + public Polygon getBoatObject() { + return boatObject; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Course.java b/src/main/java/seng302/models/Course.java new file mode 100644 index 00000000..d6647a02 --- /dev/null +++ b/src/main/java/seng302/models/Course.java @@ -0,0 +1,54 @@ +package seng302.models; + +/** + * Created by wmu16 on 4/8/17. + */ +import javafx.scene.canvas.Canvas; +import javafx.util.Pair; +import seng302.models.mark.Mark; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Class for describing the course information for the canvas + * Created by wmu16 on 22/03/17. + */ +public class Course { + + private ArrayList Legs; + private ArrayList marks; + public static final Integer SCALE = 160000; + + + public Course(ArrayList marks) { + this.marks = marks; + this.Legs = makeLegs(); + } + + /** + * Makes the race legs out of all the marker marks for the course + * @return ArrayList of Legs + */ + private ArrayList makeLegs() { + ArrayList Legs = new ArrayList<>(); + for (int i = 0; i < marks.size()-1; i++) { + Legs.add(new Leg(marks.get(i), marks.get(i+1))); + } + return Legs; + } + + + public ArrayList getMarks() { + return marks; + } + + public double getDistanceScaleFactor() { + return SCALE; + } + + public ArrayList getLegs() { + return Legs; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/models/Event.java b/src/main/java/seng302/models/Event.java deleted file mode 100644 index e803845d..00000000 --- a/src/main/java/seng302/models/Event.java +++ /dev/null @@ -1,148 +0,0 @@ -package seng302.models; - -import seng302.models.mark.Mark; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** -* Event class containing the time of specific event, related team/boat, and -* event location such as leg. -*/ -public class Event { - private Double time; // Time the event occurs - private Boat boat; - private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race - private Mark mark1; // This mark - private Mark mark2; // Next mark - private int markPosInRace; // the position of the current mark in the race course - - private final double ORIGIN_LAT = 32.320504; - private final double ORIGIN_LON = -64.857063; - private final double SCALE = 16000; - - - - /** - * Event class containing the time of specific event, related team/boat, and - * event location such as leg. - * - * @param eventTime, what time the event happens - * @param eventBoat, the boat that the event belongs to - */ - public Event(Double eventTime, Boat eventBoat, Mark mark1, Mark mark2, int markPosInRace) { - this.time = eventTime; - this.boat = eventBoat; - this.mark1 = mark1; - this.mark2 = mark2; - this.markPosInRace = markPosInRace; - } - - /** - * Event class containing the time of specific event, related team/boat, and - * event location such as leg. - * - * @param eventTime, what time the event happens - * @param eventBoat, the boat that the event belongs to - */ - public Event(Double eventTime, Boat eventBoat, Mark mark1, int markPosInRace) { - this.time = eventTime; - this.boat = eventBoat; - this.mark1 = mark1; - this.markPosInRace = markPosInRace; - this.isFinishingEvent = true; - } - - public double getTime() { - return this.time; - } - - public void setTime(double eventTime) { - this.time = eventTime; - } - - /** - * Gets the time in a formatted string - * - * @return the string of time - */ - public String getTimeString() { - return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time.longValue())); - } - - public Boat getBoat() { - return this.boat; - } - - public void setBoat(Boat eventBoat) { - this.boat = eventBoat; - } - - public boolean getIsFinishingEvent() { - return this.isFinishingEvent; - } - - /** - * Get a string that contains the timestamp and course information for this event - * - * @return A string that details what happened in this event - */ - public String getEventString() { - // This event is a boat finishing the race - if (this.isFinishingEvent) { - return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race"); - } - System.out.println(this.getDistanceBetweenMarks()); - return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°"); - } - - /** - * @return the distance between the two marks - */ - public double getDistanceBetweenMarks() { - double earth_radius = 6378.137; - double dLat = this.mark2.getLatitude() * Math.PI / 180 - this.mark1.getLatitude() * Math.PI / 180; - double dLon = this.mark2.getLongitude() * Math.PI / 180 - this.mark1.getLongitude() * Math.PI / 180; - - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.mark1.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double d = earth_radius * c; - - return d * 1000; - } - - /** - * Calculates current boat heading direction. - * @return the boats heading as degree. vertical upward is 0 degree, and degree goes up clockwise. - */ - public double getBoatHeading() { - if (mark2 == null){ - return 0.0; - } - - double x1 = (mark1.getLongitude() - ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - mark1.getLatitude()) * SCALE; - double x2 = (mark2.getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - mark2.getLatitude()) * SCALE; - - double headingRadians = Math.atan2(y2-y1, x2-x1); - - if (headingRadians < 0){ - headingRadians += 2 * Math.PI; - } - - // Convert back to degrees, and flip 180 degrees -// return ((headingRadians) * 180) / Math.PI; - return (Math.toDegrees(headingRadians) + 90) % 360; - - } - - public Mark getThisMark() { - return this.mark1; - } - - public int getMarkPosInRace() { - return markPosInRace; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/Leg.java b/src/main/java/seng302/models/Leg.java index 8f21a6ea..7e03e195 100644 --- a/src/main/java/seng302/models/Leg.java +++ b/src/main/java/seng302/models/Leg.java @@ -1,116 +1,97 @@ package seng302.models; -import seng302.models.mark.SingleMark; + +import seng302.models.mark.Mark; /** -* Represents the leg of a race. -*/ + * Class for defining the leg of a race between two markers + * Created by cir27 on 3/03/17. + */ public class Leg { - private int heading; - private int distance; - private boolean isFinishingLeg; - private SingleMark startingSingleMark; + + private final double ORIGIN_LAT = 32.320504; + private final double ORIGIN_LON = -64.857063; + private final double SCALE = 16000; + + private Double distance; + private Double heading; + private Mark end; + private Mark start; + + Leg(Mark start, Mark end) { + this.distance = calculateMarkerDistance(start, end); + this.heading = angleFromCoordinate(start, end); + this.start = start; + this.end = end; + } /** - * Create a new leg + * Calculates the euclidian distance between two markers on the canvas using xy coordinates * - * @param heading, the magnetic heading of this leg - * @param distance, the total distance of this leg in meters - * @param singleMark, the singleMark this leg starts on + * @param geoPointOne first geographical point + * @param geoPointTwo second geographical point + * @return the distance between two points in meters */ - public Leg(int heading, int distance, SingleMark singleMark) { - this.heading = heading; - this.distance = distance; - this.startingSingleMark = singleMark; - this.isFinishingLeg = false; + private Double calculateMarkerDistance(Mark geoPointOne, Mark geoPointTwo) { + + double earth_radius = 6378.137; + double dLat = geoPointTwo.getLatitude() * Math.PI / 180 - geoPointOne.getLatitude() * Math.PI / 180; + double dLon = geoPointTwo.getLongitude() * Math.PI / 180 - geoPointOne.getLongitude() * Math.PI / 180; + + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(geoPointOne.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = earth_radius * c; + + return d * 1000; } /** - * Create a new leg + * Calculates the angle between to angular co-ordinates on a sphere. * - * @param heading, the magnetic heading of this leg - * @param distance, the total distance of this leg in meters - * @param markerName, the name of the marker this leg starts on + * @param geoPointOne first geographical location + * @param geoPointTwo second geographical location + * @return the angle from point one to point two */ - public Leg(int heading, int distance, String markerName) { - this.heading = heading; - this.distance = distance; - this.startingSingleMark = new SingleMark(markerName); - this.isFinishingLeg = false; + private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) { + + if (geoPointTwo == null){ + return 0.0; + } + + double x1 = (geoPointOne.getLongitude() - ORIGIN_LON) * SCALE; + double y1 = (ORIGIN_LAT - geoPointOne.getLatitude()) * SCALE; + double x2 = (geoPointTwo.getLongitude() - ORIGIN_LON) * SCALE; + double y2 = (ORIGIN_LAT - geoPointTwo.getLatitude()) * SCALE; + + double headingRadians = Math.atan2(y2-y1, x2-x1); + + if (headingRadians < 0){ + headingRadians += 2 * Math.PI; + } + + // Convert back to degrees, and flip 180 degrees +// return ((headingRadians) * 180) / Math.PI; + return (Math.toDegrees(headingRadians) + 90) % 360; + } - /** - * Get the heading of this leg - * @return int - */ - public int getHeading() { - return this.heading; - } - - /** - * Set the heading for this leg - * @param heading - */ - public void setHeading(int heading) { - this.heading = heading; - } - - /** - * Get the total distance of this leg in meters - * @return int - */ - public int getDistance() { + Double getDistance() + { return this.distance; } - /** - * Set the distance of this leg in meters - * @param distance - */ - public void setDistance(int distance) { - this.distance = distance; + Mark getEnd() + { + return this.end; } - /** - * Returns the marker this leg started on - * @return SingleMark - */ - public SingleMark getMarker() { - return this.startingSingleMark; + public Mark getStart() { + return start; } - /** - * Set the singleMark this leg starts on - * @param singleMark - */ - public void setMarker(SingleMark singleMark) { - this.startingSingleMark = singleMark; + public Double getHeading() + { + return this.heading; } - - /** - * Returns the name of the marker this leg started on - * @return String - */ - public String getMarkerLabel() { - return this.startingSingleMark.getName(); - } - - - - /** - * Specify whether or not the race finishes on this leg - * - * @param isFinishingLeg whether or not the race finishes on this leg - */ - public void setFinishingLeg(boolean isFinishingLeg) { - this.isFinishingLeg = isFinishingLeg; - } - - /** - * Returns whether or not the race finishes after this leg - * @return true if this the race finishes after this leg - */ - public boolean getIsFinishingLeg() { - return this.isFinishingLeg; - } -} \ No newline at end of file +} diff --git a/src/main/java/seng302/models/Race.java b/src/main/java/seng302/models/Race.java index 165d9468..a396b508 100644 --- a/src/main/java/seng302/models/Race.java +++ b/src/main/java/seng302/models/Race.java @@ -1,5 +1,7 @@ package seng302.models; +import javafx.animation.AnimationTimer; +import seng302.controllers.Controller; import seng302.models.mark.Mark; import java.util.*; @@ -8,47 +10,137 @@ import java.util.*; * Race class containing the boats and legs in the race * Created by mra106 on 8/3/2017. */ -public class Race { +public class Race extends Thread { + + private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps + + private ArrayList boats; // The boats in the race private ArrayList finishingOrder; // The order in which the boats finish the race - private HashMap events = new HashMap<>(); // The events that occur in the race - private List course; // Marks in the race - private long startTime = 0; - private double timeScale = 1; + private List markers; // Marks in the race + private List raceLegs; private boolean raceFinished = false; // Race is finished private int raceTime = -2; // Current time in the race + private Double timeScale = null; + private boolean raceStarted = false; + private Controller controller; /** * Race class containing the boats and legs in the race */ - public Race() { - this.boats = new ArrayList<>(); + public Race(List markers, ArrayList boats, Double timescale, Controller controller) { + this.boats = boats; + this.markers = markers; + this.raceLegs = makeRaceLegs(); + this.controller = controller; + + this.timeScale = timescale; this.finishingOrder = new ArrayList<>(); - this.course = new ArrayList<>(); + } + + + /** + * Makes the race legs out of all the marker points for the course + * @return ArrayList of raceLegs + */ + private ArrayList makeRaceLegs() { + ArrayList raceLegs = new ArrayList<>(); + for (int i=0; i < markers.size()-1; i++) { + raceLegs.add(new Leg(markers.get(i), markers.get(i+1))); + } + return raceLegs; + } + + + /** + * A timer that is called every frame to call the action of moving the boats + */ + private AnimationTimer at = new AnimationTimer() { + @Override + public void handle(long now) { + + //Updating Boat positions + if(finishingOrder.size() != boats.size()) { + // update the time + boats.stream().filter(boat -> !finishingOrder.contains(boat)).forEach(boat -> { + updateBoatPosition(boat); + }); + } else { + at.stop(); + } + } + + }; + + + /** + * Begins the race simulation + */ + @Override + public void run() { + if (!raceStarted) { +// controller.getPlayPauseButton().setDisable(true); + at.start(); + } + } + + + /** + * Moves the coordinates of the boats. + * @param boat The boat to update the position of + */ + private void updateBoatPosition(Boat boat) { + Double thisHeading = boat.getHeading(); + // TODO: 4/8/17 wmu16 - Add a distance scale factor from lat long distance in Metres to xy equivalent + Double hypMove = boat.getVelocity() * UPDATE_TIME * timeScale; //* distanceScaleFactor + boat.setLegDistance(boat.getLegDistance() + hypMove); + moveBoat(boat, thisHeading, hypMove); } /** - * Add a boat to the race - * - * @param boat, the boat to add + * Moves a boat along coordinates by breaking down the distance moved along the hypotenuse into x and y components + * @param boat The Boat to move + * @param heading The heading the boat is moving at + * @param hypMove The distance moved along the hypotenuse (absolute distance) */ - public void addBoat(Boat boat) { - boats.add(boat); + private void moveBoat(Boat boat, Double heading, Double hypMove) { + //If the boat has not yet passed the marker at the end of the leg increase its (x,y) as per normal + // TODO: 4/8/17 wmu16 - Initialising boat positions and legs and headings etc. + if(boat.getLegDistance() <= boat.getCurrentLeg().getDistance()) { + Double xMove = hypMove * Math.sin(Math.toRadians(heading)); + Double yMove = - hypMove * Math.cos(Math.toRadians(heading)); + boat.moveBoatBy(xMove, yMove); + + //If the boat has finished... + } else if (getNextRaceLeg(boat.getCurrentLeg()).equals(boat.getCurrentLeg())) { + finishingOrder.add(boat); + //Otherwise apply the overflow distance of the leg to the next leg + } else { + Double overflowDistance = boat.getLegDistance() - boat.getCurrentLeg().getDistance(); + boat.setCurrentLeg(getNextRaceLeg(boat.getCurrentLeg())); + boat.setHeading(boat.getCurrentLeg().getHeading()); + boat.setLegDistance(overflowDistance); + moveBoat(boat, boat.getHeading(), overflowDistance); + } + } /** - * Returns a list of boats in a random order - * - * @return a list of boats + * Returns the next leg in the race + * @param currentRaceLeg The leg that you are currently on + * @return The next race leg or the same race leg if it has reached the end */ - public Boat[] getShuffledBoats() { - // Shuffle the list of boats - long seed = System.nanoTime(); - Collections.shuffle(this.boats, new Random(seed)); - - return boats.toArray(new Boat[boats.size()]); + private Leg getNextRaceLeg(Leg currentRaceLeg) { + Leg nextRaceLeg = currentRaceLeg; + for(int i = 0; i< raceLegs.size() - 1; i++) { + if (raceLegs.get(i).equals(currentRaceLeg)) { + nextRaceLeg = raceLegs.get(i + 1); + } + } + return nextRaceLeg; } + /** * Returns a list of boats in the order that they * finished the race (position 0 is first place) @@ -69,85 +161,28 @@ public class Race { return boats.toArray(new Boat[boats.size()]); } - /** - * Sets time scale - * - * @param timeScale - */ - public void setTimeScale(double timeScale) { - this.timeScale = timeScale; - } - - /** - * Generate all events that will happen during the race. - */ - private void generateEvents() { - - for (Boat boat : this.boats) { - double totalDistance = 0; - int numberOfMarks = this.course.size(); - - for (int i = 0; i < numberOfMarks; i++) { - Double time = (totalDistance / boat.getVelocity() / timeScale); - - // If there are singleMarks after this event - if (i < numberOfMarks - 1) { - Event event = new Event(time, boat, course.get(i), course.get(i + 1), i); - - try { - events.get(boat).add(event); - - } catch (NullPointerException e) { - events.put(boat, new ArrayList<>(Arrays.asList(event))); - } - totalDistance += event.getDistanceBetweenMarks(); - System.out.println(totalDistance); - System.out.println(boat.getVelocity()); - } - - // There are no more marks after this event - - else{ - Event event = new Event(time, boat, course.get(i), i); - events.get(boat).add(event); - } - } - } - } - - /** * Starts a race and generates all events for the race. */ public void startRace() { // record start time. - this.startTime = System.currentTimeMillis(); - generateEvents(); + + + + } + + public void pause() { + at.stop(); } /** - * Set the race course - * @param course a list of marks in the course - */ - public void addCourse(List course) { - this.course = course; - } - - /** - * Get a list of marks in the course + * Get a list of marks in the markers * @return */ - public List getCourse() { - return course; + public List getMarkers() { + return markers; } - /** - * Get a map of the events in the race - * @return - */ - public HashMap getEvents() { - return events; - } /** * Set a boat as finished @@ -191,7 +226,11 @@ public class Race { /** * Increment the race time by one second */ - public void incrementRaceTime(){ + public void incrementRaceTime() { this.raceTime += this.timeScale; } + + public List getRaceLegs() { + return raceLegs; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/TimelineInfo.java b/src/main/java/seng302/models/TimelineInfo.java deleted file mode 100644 index 867ce67b..00000000 --- a/src/main/java/seng302/models/TimelineInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package seng302.models; - -import javafx.animation.Timeline; -import javafx.beans.property.DoubleProperty; - - -/** - * Created by zyt10 on 17/03/17. - * this class is literally just to associate a timeline with a DoubleProperty x and y - */ -public class TimelineInfo { - private Timeline timeline; - private DoubleProperty x; - private DoubleProperty y; - - public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) { - this.timeline = timeline; - this.x = x; - this.y = y; - } - - public Timeline getTimeline() { - return timeline; - } - public DoubleProperty getX() { - return x; - } - public DoubleProperty getY() { - return y; - } -} diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index f8bfa00e..2886ff77 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -1,6 +1,6 @@ - + Start @@ -68,4 +68,4 @@ Leeward Gate Finish - + diff --git a/src/main/resources/views/CanvasView.fxml b/src/main/resources/views/CanvasView.fxml deleted file mode 100644 index bc16ad7e..00000000 --- a/src/main/resources/views/CanvasView.fxml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/main/resources/views/FinishView.fxml b/src/main/resources/views/FinishView.fxml deleted file mode 100644 index debdea26..00000000 --- a/src/main/resources/views/FinishView.fxml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/views/MainView.fxml b/src/main/resources/views/MainView.fxml index ac0b944e..a89478b6 100644 --- a/src/main/resources/views/MainView.fxml +++ b/src/main/resources/views/MainView.fxml @@ -1,11 +1,47 @@ - + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml deleted file mode 100644 index f7fcbfeb..00000000 --- a/src/main/resources/views/RaceView.fxml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/java/seng302/BoatTest.java b/src/test/java/seng302/BoatTest.java deleted file mode 100644 index 0160bb8d..00000000 --- a/src/test/java/seng302/BoatTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Boat; - -import static org.junit.Assert.assertEquals; - -/** - * Unit test for the Team class. - */ -public class BoatTest { - - @Test - public void testBoatCreation() { - Boat boat1 = new Boat("Team 1"); - assertEquals(boat1.getTeamName(), "Team 1"); - assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15); - } - - @Test - public void testChangeTeamName() { - Boat boat1 = new Boat("Team 1"); - boat1.setTeamName("Team 2"); - assertEquals(boat1.getTeamName(), "Team 2"); - } - - @Test - public void testSetVelocity() { - Boat boat1 = new Boat("Team 1", 29.0, ""); - assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15); - - boat1.setVelocity(12.0); - assertEquals(boat1.getVelocity(), (double)12.0, 1e-15); - } -} diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java deleted file mode 100644 index a6681b26..00000000 --- a/src/test/java/seng302/ColorsTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package seng302; - -import javafx.scene.paint.Color; -import org.junit.Test; -import seng302.models.Boat; -import seng302.models.Colors; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -/** - * Created by ryan_ on 16/03/2017. - */ -public class ColorsTest { - //@Test - public void testNextColor() { - List boats = new ArrayList<>(); - boats.add(new Boat("Team 1")); - boats.add(new Boat("Team 2")); - boats.add(new Boat("Team 3")); - boats.add(new Boat("Team 4")); - boats.add(new Boat("Team 5")); - boats.add(new Boat("Team 6")); - - int count = 0; - List enumColors = new ArrayList<>(); - while (count < 6) { - Color color = Colors.getColor(); - enumColors.add(color); - count++; - } - - List boatColors = new ArrayList<>(); - for (Boat boat : boats) { - Color color = boat.getColor(); - boatColors.add(color); - } - - assertEquals(enumColors, boatColors); - } -} diff --git a/src/test/java/seng302/EventTest.java b/src/test/java/seng302/EventTest.java deleted file mode 100644 index 0be0fc98..00000000 --- a/src/test/java/seng302/EventTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Boat; -import seng302.models.Event; -import seng302.models.mark.SingleMark; - -import static org.junit.Assert.assertEquals; - -/** - * Test for Event class - * Created by Haoming on 7/03/17. - */ -public class EventTest { - - @Test - public void getTimeString() throws Exception { - Boat boat = new Boat("testBoat"); - Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0); - assertEquals("20:31:242", event.getTimeString()); - } - - @Test - public void testBoatHeading() throws Exception { - Boat boat = new Boat("testBoat"); - Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1), new SingleMark("mark2", 121.9,99.2), 0); - - assertEquals(event.getBoatHeading(), 228.0266137055349, 1e-15); - } - - @Test - public void testDistanceBetweenMarks() throws Exception { - Boat boat = new Boat("testBoat"); - Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1), new SingleMark("mark2", 121.9,99.2), 0); - - assertEquals(event.getDistanceBetweenMarks(), 339059.653830461, 1e-15); - } -} \ No newline at end of file diff --git a/src/test/java/seng302/LegTest.java b/src/test/java/seng302/LegTest.java deleted file mode 100644 index 9bb64b6c..00000000 --- a/src/test/java/seng302/LegTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Leg; -import seng302.models.mark.SingleMark; - -import static org.junit.Assert.assertEquals; - -/** - * Unit test for the Leg class. - */ -public class LegTest { - - /** - * Test creation of the leg by specifying a string - * for the marker label - */ - @Test - public void testLegCreationUsingMarkerLabel() { - Leg leg = new Leg(010, 100, "SingleMark"); - - assertEquals(leg.getHeading(), 010); - assertEquals(leg.getDistance(), 100); - assertEquals(leg.getMarkerLabel(), "SingleMark"); - assertEquals(leg.getIsFinishingLeg(), false); - } - - /** - * Test creation of the leg by providing a - * SingleMark object - */ - @Test - public void testLegCreation() { - Leg leg = new Leg(010, 100, new SingleMark("SingleMark")); - - assertEquals(leg.getHeading(), 010); - assertEquals(leg.getDistance(), 100); - assertEquals(leg.getMarkerLabel(), "SingleMark"); - assertEquals(leg.getIsFinishingLeg(), false); - } - - /** - * Test changing whether or not a - * leg is the finishing leg - */ - @Test - public void testSetFinishLeg() { - Leg leg = new Leg(010, 100, "SingleMark"); - - leg.setFinishingLeg(true); - assertEquals(leg.getIsFinishingLeg(), true); - } - -} diff --git a/src/test/java/seng302/RaceTest.java b/src/test/java/seng302/RaceTest.java deleted file mode 100644 index ab318331..00000000 --- a/src/test/java/seng302/RaceTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Boat; -import seng302.models.Race; - -import java.lang.reflect.Array; - -import static org.junit.Assert.assertEquals; - -/** - * Unit test for the Race class. - */ -public class RaceTest { - /** - * Test that all boats were added to the race - */ - @Test - public void testAddingBoatsToRace() { - Boat boat1 = new Boat("Team 1"); - Boat boat2 = new Boat("Team 2"); - - Race race = new Race(); - race.addBoat(boat1); - race.addBoat(boat2); - - assertEquals(Array.getLength(race.getBoats()), 2); - } - - @Test - public void testGetShuffledBoats(){ - Boat boat1 = new Boat("Team 1"); - Boat boat2 = new Boat("Team 2"); - - Race race = new Race(); - race.addBoat(boat1); - race.addBoat(boat2); - - assertEquals(Array.getLength(race.getShuffledBoats()), 2); - } -} diff --git a/src/test/java/seng302/TestRaceTimer.java b/src/test/java/seng302/TestRaceTimer.java deleted file mode 100644 index 542405b1..00000000 --- a/src/test/java/seng302/TestRaceTimer.java +++ /dev/null @@ -1,25 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.controllers.RaceViewController; - -import static org.junit.Assert.assertTrue; - - -public class TestRaceTimer { - @Test - public void testPositiveTimeString(){ - RaceViewController controller = new RaceViewController(); - String result = controller.convertTimeToMinutesSeconds(61); - - assertTrue(result.equals("01:01")); - } - - @Test - public void testNegativeTimeString(){ - RaceViewController controller = new RaceViewController(); - String result = controller.convertTimeToMinutesSeconds(-61); - - assertTrue(result.equals("-01:01")); - } -} diff --git a/src/test/java/seng302/models/mark/MarkTest.java b/src/test/java/seng302/models/mark/MarkTest.java deleted file mode 100644 index f1943c64..00000000 --- a/src/test/java/seng302/models/mark/MarkTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package seng302.models.mark; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Created by Haoming on 17/3/17. - */ -public class MarkTest { - - private SingleMark singleMark1; - private SingleMark singleMark2; - private GateMark gateMark; - - @Before - public void setUp() throws Exception { - this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342); - this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352); - this.gateMark = new GateMark("testMark_GM", singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); - } - - @Test - public void getName() throws Exception { - assertEquals("testMark_SM1", this.singleMark1.getName()); - assertEquals("testMark_GM", this.gateMark.getName()); - } - - @Test - public void getMarkType() throws Exception { - assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK); - assertTrue(this.gateMark.getMarkType() == MarkType.GATE_MARK); - } - - @Test - public void getMarkContent() throws Exception { - assertEquals(12.23234, this.singleMark1.getLatitude(), 1e-10); - assertEquals(-34.342, this.singleMark1.getLongitude(), 1e-10); - assertEquals("testMark_SM1", this.gateMark.getSingleMark1().getName()); - assertEquals(-34.352, this.gateMark.getSingleMark2().getLongitude(), 1e-10); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/models/parsers/ConfigParserTest.java b/src/test/java/seng302/models/parsers/ConfigParserTest.java deleted file mode 100644 index 8a0c0c8c..00000000 --- a/src/test/java/seng302/models/parsers/ConfigParserTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package seng302.models.parsers; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Created by Haoming on 23/03/17. - */ -public class ConfigParserTest { - - private ConfigParser cp; - - @Before - public void initializeParser() throws Exception { - cp = new ConfigParser("/config/config.xml"); - } - - @Test - public void getWindDirection() throws Exception { - assertEquals(135, cp.getWindDirection(), 1e-10); - } - - @Test - public void getTimeScale() throws Exception { - assertEquals(10.0, cp.getTimeScale(), 1e-10); - } - - @Test - public void getDoubleByTagName() throws Exception { - assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10); - assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10); - } - - @Test - public void getStringByTagName() throws Exception { - assertEquals("AC35", cp.getStringByTagName("race-name", "11")); - assertEquals("oops", cp.getStringByTagName("noTag", "oops")); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/models/parsers/CourseParserTest.java b/src/test/java/seng302/models/parsers/CourseParserTest.java deleted file mode 100644 index 865caec6..00000000 --- a/src/test/java/seng302/models/parsers/CourseParserTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package seng302.models.parsers; - -import org.junit.Before; -import org.junit.Test; -import seng302.models.mark.*; - -import java.util.ArrayList; - -import static org.junit.Assert.*; - -/** - * To test if course parser works as expected. - * Created by Haoming on 17/03/17. - */ -public class CourseParserTest { - - private CourseParser cp; - - @Before - public void initializeParser() throws Exception { - cp = new CourseParser("/config/course.xml"); - } - - @Test - public void getGates() throws Exception { - ArrayList course = cp.getCourse(); - - assertTrue(MarkType.GATE_MARK == course.get(0).getMarkType()); - - GateMark gateMark1 = (GateMark) course.get(0); - assertEquals(32.293771, gateMark1.getSingleMark2().getLatitude(), 0.00000001); - assertEquals(-64.855242, gateMark1.getSingleMark2().getLongitude(), 0.00000001); - - GateMark gateMark2 = (GateMark) course.get(5); - - assertEquals("Finish1", gateMark2.getSingleMark1().getName()); - assertEquals("Finish2", gateMark2.getSingleMark2().getName()); - assertEquals(32.317257, gateMark2.getSingleMark2().getLatitude(), 0.00000001); - assertEquals(-64.83626, gateMark2.getSingleMark2().getLongitude(), 0.00000001); - } - - @Test - public void getMarks() throws Exception { - ArrayList course = cp.getCourse(); - assertEquals("Mid Mark", course.get(1).getName()); - } - - @Test - public void getOrder() { - ArrayList course = cp.getCourse(); - assertEquals(6, course.size()); - assertEquals("Start", course.get(0).getName()); - assertEquals("Mid Mark", course.get(1).getName()); - assertEquals("Leeward Gate", course.get(2).getName()); - assertEquals("Windward Gate", course.get(3).getName()); - assertEquals("Leeward Gate", course.get(4).getName()); - assertEquals("Finish", course.get(5).getName()); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/models/parsers/TeamsParserTest.java b/src/test/java/seng302/models/parsers/TeamsParserTest.java deleted file mode 100644 index 3c31b519..00000000 --- a/src/test/java/seng302/models/parsers/TeamsParserTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package seng302.models.parsers; - -import org.junit.Before; -import org.junit.Test; -import seng302.models.Boat; - -import java.util.ArrayList; - -import static org.junit.Assert.*; - -/** - * Created by Haoming on 18/03/17. - */ -public class TeamsParserTest { - - private TeamsParser tp; - @Before - public void readFile() { - tp = new TeamsParser("/config/teams.xml"); - } - - @Test - public void getBoats() throws Exception { - ArrayList boats = tp.getBoats(); - - assertEquals(6, boats.size(), 1e-10); - - assertEquals("Oracle Team USA", boats.get(0).getTeamName()); - //assertEquals(30.9, boats.get(0).getVelocity(), 1e-10); - - assertEquals("Groupama Team France", boats.get(5).getTeamName()); - //assertEquals(45.6, boats.get(5).getVelocity(), 1e-10); - } - -} \ No newline at end of file From edc306da22ace3ea459085af0603f280467d3706 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Wed, 19 Apr 2017 19:05:19 +1200 Subject: [PATCH 02/23] Created AC35 Streaming server - Sends heartbeat messages every 5 seconds - Sends XML at beginning Tags: #story[29] --- src/main/java/seng302/App.java | 2 + .../java/seng302/server/ServerThread.java | 94 +++++++ .../seng302/server/StreamingServerSocket.java | 53 ++++ .../java/seng302/server/messages/Header.java | 71 ++++++ .../seng302/server/messages/Heartbeat.java | 51 ++++ .../java/seng302/server/messages/Message.java | 31 +++ .../seng302/server/messages/MessageType.java | 34 +++ .../seng302/server/messages/XMLMessage.java | 96 +++++++ .../server/messages/XMLMessageSubType.java | 20 ++ src/main/resources/server_config/boats.xml | 234 ++++++++++++++++++ src/main/resources/server_config/race.xml | 85 +++++++ src/main/resources/server_config/regatta.xml | 12 + 12 files changed, 783 insertions(+) create mode 100644 src/main/java/seng302/server/ServerThread.java create mode 100644 src/main/java/seng302/server/StreamingServerSocket.java create mode 100644 src/main/java/seng302/server/messages/Header.java create mode 100644 src/main/java/seng302/server/messages/Heartbeat.java create mode 100644 src/main/java/seng302/server/messages/Message.java create mode 100644 src/main/java/seng302/server/messages/MessageType.java create mode 100644 src/main/java/seng302/server/messages/XMLMessage.java create mode 100644 src/main/java/seng302/server/messages/XMLMessageSubType.java create mode 100644 src/main/resources/server_config/boats.xml create mode 100644 src/main/resources/server_config/race.xml create mode 100644 src/main/resources/server_config/regatta.xml diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 0637d2cc..1a869e78 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; +import seng302.server.ServerThread; public class App extends Application { @@ -18,6 +19,7 @@ public class App extends Application } public static void main(String[] args) { + new ServerThread("Racevision Test Server"); launch(args); } } diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java new file mode 100644 index 00000000..cd4c091f --- /dev/null +++ b/src/main/java/seng302/server/ServerThread.java @@ -0,0 +1,94 @@ +package seng302.server; + +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; +import seng302.server.messages.XMLMessage; +import seng302.server.messages.XMLMessageSubType; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Timer; +import java.util.TimerTask; + +public class ServerThread implements Runnable{ + private Thread runner; + private StreamingServerSocket server; + private final int HEARTBEAT_PERIOD = 5000; + private final int PORT_NUMBER = 8085; + + public ServerThread(String threadName){ + runner = new Thread(this, threadName); + System.out.println("Spawning Server Thread"); + runner.start(); + } + + /** + * Creates and returns an XML Message from the file specified + * @param fileName The source XML file + * @param type The XML Message type + * @return The XML Message + */ + public Message getXmlMessage(String fileName, XMLMessageSubType type){ + String fileContents = null; + + try { + fileContents = new String(Files.readAllBytes(Paths.get(this.getClass().getResource(fileName).getPath().substring(1)))); + } catch (IOException e) { + e.printStackTrace(); + } + + if (fileContents != null){ + return new XMLMessage(fileContents, type, server.getSequenceNumber()); + } + + return null; + } + + public void run() { + try{ + server = new StreamingServerSocket(PORT_NUMBER); + } + catch (IOException e){ + System.err.println("Failed to bind socket: " + e.getMessage()); + } + + server.start(); + + try { + // Load and send race XML data + Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); + Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); + Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); + + if (raceData != null){ + server.send(raceData); + } + + if (boatData != null){ + server.send(boatData); + } + + if (regatta != null){ + server.send(regatta); + } + + // Timer to send the heartbeat + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + Message hb = new Heartbeat(server.getSequenceNumber()); + try { + server.send(hb); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 0, HEARTBEAT_PERIOD); + + } catch (IOException e) { + System.err.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java new file mode 100644 index 00000000..d34eae8a --- /dev/null +++ b/src/main/java/seng302/server/StreamingServerSocket.java @@ -0,0 +1,53 @@ +package seng302.server; + +import seng302.server.messages.Message; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +class StreamingServerSocket { + private java.net.ServerSocket socket; + private Socket client; + private List clients; + private short seqNum; + + StreamingServerSocket(int port) throws IOException{ + socket = new java.net.ServerSocket(port); + clients = new ArrayList<>(); + socket.setSoTimeout(10000); + seqNum = 0; + } + + void start(){ + System.out.println("Listening For Connections"); + try { + client = socket.accept(); + } catch (IOException e) { + e.getMessage(); + } + if (client == null){ + start(); + } + else{ + System.out.println("client connected from " + client.getInetAddress()); + } + } + + void send(Message message) throws IOException{ + if (client == null){ + return; + } + + DataOutputStream outputStream = new DataOutputStream(client.getOutputStream()); + message.send(outputStream); + + seqNum++; + } + + public short getSequenceNumber(){ + return seqNum; + } +} diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/server/messages/Header.java new file mode 100644 index 00000000..9f1a81e0 --- /dev/null +++ b/src/main/java/seng302/server/messages/Header.java @@ -0,0 +1,71 @@ +package seng302.server.messages; + +import java.nio.ByteBuffer; + +public class Header { + // From API spec + private final int syncByte1 = 0x47; + private final int syncByte2 = 0x83; + + private MessageType messageType; + private int timeStamp; + private int sourceId; + private short messageLength; + private static final int MESSAGE_LEN = 15; + + /** + * Message Header from section 3.2 of the AC35 Streaming + * Data spec + * @param messageType The type of the message following this header + * @param sourceId The message source (as defined in the spec) + * @param messageLength The length of the message following this header + */ + public Header(MessageType messageType, int sourceId, Short messageLength){ + this.messageType = messageType; + this.sourceId = sourceId; + this.messageLength = messageLength; + timeStamp = (int) (System.currentTimeMillis() / 1000L); + } + + /** + * @return a ByteBuffer containing the message header + */ + public ByteBuffer getByteBuffer(){ + ByteBuffer buff = ByteBuffer.allocate(15); + + // Sync Byte 1, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)syncByte1).array()); + buff.position(1); + + // Sync Byte 2, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)syncByte2).array()); + buff.position(2); + + // Message Type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array()); + buff.position(3); + + // Timestamp, 6 bytes + int x = ((int) Integer.toUnsignedLong(6)); + buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); + buff.position(9); + + // Source ID, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt(sourceId).array()); + buff.position(13); + + // Message Length, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(messageLength).array()); + buff.position(15); + + return buff; + } + + /** + * Returns the size of this message + * @return the size of the message + */ + public static Integer getSize(){ + return MESSAGE_LEN; + } +} diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java new file mode 100644 index 00000000..a6470240 --- /dev/null +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -0,0 +1,51 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; + +public class Heartbeat extends Message { + private final int MESSAGE_SIZE = 4; + private int seqNo; + + /** + * Heartbeat from the AC35 Streaming data spec + * @param seqNo Increment every time a message is sent + */ + public Heartbeat(int seqNo){ + this.seqNo = seqNo; + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); + + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + getSize()); + + // Write header + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Write seq num + buff.put(ByteBuffer.allocate(4).putInt(seqNo).array()); + buff.position(Header.getSize()+4); + + // Write CRC + CRC32 crc = new CRC32(); + crc.update(buff.array()); + + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java new file mode 100644 index 00000000..8e5c48b4 --- /dev/null +++ b/src/main/java/seng302/server/messages/Message.java @@ -0,0 +1,31 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; + +public abstract class Message { + Header header; + + /** + * @param header Set the header for this message + */ + public void setHeader(Header header){ + this.header = header; + } + + /** + * @return the header specified for this message + */ + public Header getHeader(){ + return header; + } + + /** + * @return the size of the message + */ + public abstract int getSize(); + + /** + * Send the message as through the outputStream + */ + public abstract void send(DataOutputStream outputStream); +} diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/server/messages/MessageType.java new file mode 100644 index 00000000..be856dac --- /dev/null +++ b/src/main/java/seng302/server/messages/MessageType.java @@ -0,0 +1,34 @@ +package seng302.server.messages; + +/** + * Enum containing the types of messages + * sent by the server + */ +public enum MessageType { + HEARTBEAT(1), + RACE_STATUS(12), + DISPLAY_TEXT_MESSAGE(20), + XML_MESSAGE(26), + RACE_START_STATUS(27), + YACHT_EVENT_CODE(29), + YACHT_ACTION_CODE(31), + CHATTER_TEXT(36), + BOAT_LOCATION(37), + MARK_ROUNDING(38), + COURSE_WIND(44), + AVERAGE_WIND(47); + + private int code; + + MessageType(int code){ + this.code = code; + } + + /** + * Get the message code (From the API Spec) + * @return the message code + */ + int getCode(){ + return this.code; + } +} diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java new file mode 100644 index 00000000..2d7e0431 --- /dev/null +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -0,0 +1,96 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; + +public class XMLMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; + private final int MESSAGE_VERSION = 1; //Always set to 1 + private final int MESSAGE_SIZE = 14; + + // Message fields + private int timeStamp; + private short ack = 0x00; //Unused + private XMLMessageSubType xmlMessageSubType; + private Short length; + private Short sequence; + private String content; + private CRC32 crc; + + /** + * XML Message from the AC35 Streaming data spec + * @param content The XML content + * @param type The XML Message Sub Type + */ + public XMLMessage(String content, XMLMessageSubType type, short sequenceNum){ + this.content = content; + this.xmlMessageSubType = type; + crc = new CRC32(); + timeStamp = (int) (System.currentTimeMillis() / 1000L); + ack = 0; + length = (short) this.content.length(); + sequence = sequenceNum; + + setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); + } + + /** + * @return The length of this message + */ + public int getSize(){ + return MESSAGE_SIZE + content.length(); + } + + /** + * Send this message as a stream of bytes + * @param outputStream The output stream to send the message + */ + public void send(DataOutputStream outputStream) { + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4); + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Version Number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); + buff.position(Header.getSize() + 1); + + // Ack, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(ack).array()); + buff.position(Header.getSize() + 3); + + // Timestamp, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); + buff.position(Header.getSize() + 9); + + // XML message sub type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)xmlMessageSubType.getType()).array()); + buff.position(Header.getSize() + 10); + + // Seq num, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(sequence).array()); + buff.position(Header.getSize() + 12); + + // Message length, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(length).array()); + buff.position(Header.getSize() + 14); + + // XML Content + buff.put(this.content.getBytes()); + buff.position(Header.getSize() + 14 + this.content.getBytes().length); + + // calculate CRC + crc.update(buff.array()); + + // Add CRC to message + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + // Send + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/XMLMessageSubType.java b/src/main/java/seng302/server/messages/XMLMessageSubType.java new file mode 100644 index 00000000..2e146c5a --- /dev/null +++ b/src/main/java/seng302/server/messages/XMLMessageSubType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the types of XML messages + */ +public enum XMLMessageSubType { + REGATTA(5), + RACE(6), + BOAT(7); + + private int type; + + XMLMessageSubType(int type){ + this.type = type; + } + + public int getType(){ + return this.type; + } +} diff --git a/src/main/resources/server_config/boats.xml b/src/main/resources/server_config/boats.xml new file mode 100644 index 00000000..df5ddca4 --- /dev/null +++ b/src/main/resources/server_config/boats.xml @@ -0,0 +1,234 @@ + + + 2015-08-28T17:32:59+0100 + 12 + 219 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml new file mode 100644 index 00000000..845f2044 --- /dev/null +++ b/src/main/resources/server_config/race.xml @@ -0,0 +1,85 @@ + + + 2015-08-29T13:12:40+02:00 + + 15082901 + Fleet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/regatta.xml b/src/main/resources/server_config/regatta.xml new file mode 100644 index 00000000..6abcf2da --- /dev/null +++ b/src/main/resources/server_config/regatta.xml @@ -0,0 +1,12 @@ + + + 24 + Gothenburg World Series 2015 + Gothenburg + 57.6679590 + 11.8503233 + 6.95 + 2 + 3.2 + gothenburg_shoreline + \ No newline at end of file From 6874f288eeea5d6c3702942aec748300400c0e14 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Mon, 24 Apr 2017 21:53:42 +1200 Subject: [PATCH 03/23] Added Race Status messages to the mock streaming data interface Tags: #story[29] --- .../java/seng302/server/ServerThread.java | 46 ++++++- .../seng302/server/messages/BoatStatus.java | 25 ++++ .../server/messages/BoatSubMessage.java | 91 ++++++++++++ .../seng302/server/messages/RaceStatus.java | 26 ++++ .../server/messages/RaceStatusMessage.java | 130 ++++++++++++++++++ .../seng302/server/messages/RaceType.java | 20 +++ .../server/messages/WindDirection.java | 20 +++ 7 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 src/main/java/seng302/server/messages/BoatStatus.java create mode 100644 src/main/java/seng302/server/messages/BoatSubMessage.java create mode 100644 src/main/java/seng302/server/messages/RaceStatus.java create mode 100644 src/main/java/seng302/server/messages/RaceStatusMessage.java create mode 100644 src/main/java/seng302/server/messages/RaceType.java create mode 100644 src/main/java/seng302/server/messages/WindDirection.java diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index cd4c091f..f84887c2 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -1,13 +1,12 @@ package seng302.server; -import seng302.server.messages.Heartbeat; -import seng302.server.messages.Message; -import seng302.server.messages.XMLMessage; -import seng302.server.messages.XMLMessageSubType; +import seng302.server.messages.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.Timer; import java.util.TimerTask; @@ -15,6 +14,7 @@ public class ServerThread implements Runnable{ private Thread runner; private StreamingServerSocket server; private final int HEARTBEAT_PERIOD = 5000; + private final int RACE_STATUS_PERIOD = 1000; private final int PORT_NUMBER = 8085; public ServerThread(String threadName){ @@ -45,6 +45,29 @@ public class ServerThread implements Runnable{ return null; } + /** + * @return A sample race status message + */ + public Message getTestRaceStatusMessage(){ + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + + List boats = new ArrayList(); + boats.add(boat1); + boats.add(boat2); + boats.add(boat3); + + return new RaceStatusMessage(1, RaceStatus.PRESTART, 1000, WindDirection.EAST, + 100, 3, RaceType.MATCH_RACE, 1, boats); + } + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -87,6 +110,21 @@ public class ServerThread implements Runnable{ } }, 0, HEARTBEAT_PERIOD); + // Timer to send the race status messages + Timer t1 = new Timer(); + t1.schedule(new TimerTask() { + @Override + public void run() { + Message statusMessage = getTestRaceStatusMessage(); + + try { + server.send(statusMessage); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 100, RACE_STATUS_PERIOD); + } catch (IOException e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/seng302/server/messages/BoatStatus.java b/src/main/java/seng302/server/messages/BoatStatus.java new file mode 100644 index 00000000..94418000 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatStatus.java @@ -0,0 +1,25 @@ +package seng302.server.messages; + +/** + * The current status of a boat + */ +public enum BoatStatus { + UNDEFINED(0), + PRESTART(1), + RACING(2), + FINISHED(3), + DNS(4), + DNF(5), + DSQ(6), + CS(7); + + private long code; + + BoatStatus(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/server/messages/BoatSubMessage.java new file mode 100644 index 00000000..d403d85b --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatSubMessage.java @@ -0,0 +1,91 @@ +package seng302.server.messages; + +import java.nio.ByteBuffer; + +/** + * The status of each boat, sent within a race status message + */ +public class BoatSubMessage { + private final int MESSAGE_SIZE = 20; + + private long sourceId; + private BoatStatus boatStatus; + private long legNumber; + private long numberPenaltiesAwarded; + private long numberPenaltiesServed; + private long estimatedTimeAtNextMark; + private long estimatedTimeAtFinish; + + /** + * Boat Sub message from section 4.2 of the AC35 streaming data interface spec + * @param sourceId The source ID of the boat + * @param boatStatus The boats status + * @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc) + * @param numberPenaltiesAwarded The number of penalties awarded to the boat + * @param numberPenaltiesServed The number of penalties served to the boat + * @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race + * @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark + */ + public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed, + long estimatedTimeAtFinish, long estimatedTimeAtNextMark){ + this.sourceId = sourceId; + this.boatStatus = boatStatus; + this.legNumber = legNumber; + this.numberPenaltiesAwarded = numberPenaltiesAwarded; + this.numberPenaltiesServed = numberPenaltiesServed; + this.estimatedTimeAtFinish = estimatedTimeAtFinish; + this.estimatedTimeAtNextMark = estimatedTimeAtNextMark; + } + + /** + * @return The size of this message in bytes + */ + public int getSize(){ + return MESSAGE_SIZE; + } + + /** + * @return a ByteBuffer containing this boat status message + */ + public ByteBuffer getByteBuffer(){ + ByteBuffer buff = ByteBuffer.allocate(getSize()); + int buffPos = 0; + + // Source ID, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt((int) sourceId).array()); + buffPos += 4; + buff.position(buffPos); + + // Boat Status, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array()); + buffPos += 1; + buff.position(buffPos); + + // Leg number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array()); + buffPos += 1; + buff.position(buffPos); + + // Number of penalties awarded, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array()); + buffPos += 1; + buff.position(buffPos); + + // Number of penalties served, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array()); + buffPos += 1; + buff.position(buffPos); + + // Estimated time at next mark, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtNextMark).array()); + buffPos += 6; + buff.position(buffPos); + + // Estimated time at finish, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtFinish).array()); + buffPos += 6; + buff.position(buffPos); + + return buff; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatus.java b/src/main/java/seng302/server/messages/RaceStatus.java new file mode 100644 index 00000000..7f123c2d --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStatus.java @@ -0,0 +1,26 @@ +package seng302.server.messages; + +/** + * The current status of the race + */ +public enum RaceStatus { + NOTACTIVE(0), + WARNING(1), // Between 3:00 and 1:00 before start + PREPARATORY(2), // Less than 1:00 before start + STARTED(3), + ABANDONED(6), + POSTPONED(7), + TERMINATED(8), + RACE_START_TIME_NOT_SET(9), + PRESTART(10); // More than 3:00 before start + + private int code; + + RaceStatus(int code){ + this.code = code; + } + + public int getCode(){ + return this.code; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java new file mode 100644 index 00000000..17ab82fc --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -0,0 +1,130 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.zip.CRC32; + +public class RaceStatusMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS; + private final int MESSAGE_VERSION = 2; //Always set to 1 + private final int MESSAGE_BASE_SIZE = 24; + + // fields + private long currentTime; + private long raceId; + private RaceStatus raceStatus; + private long expectedStartTime; + private WindDirection raceWindDirection; + private long windSpeed; + private long numBoatsInRace; + private RaceType raceType; + private List boats; + private CRC32 crc; + + /** + * A message containing the current status of the race + * @param raceId The ID of the current race + * @param raceStatus The status of the race + * @param expectedStartTime The expected start time + * @param raceWindDirection The wind direction (north, east, south) + * @param windSpeed The wind speed in mm/sec + * @param numBoatsInRace The number of boats in the race + * @param raceType The race type (Match/fleet) + * @param sourceId The source of this message + * @param boats A list of boat status sub messages + */ + public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection, + long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List boats){ + currentTime = System.currentTimeMillis() / 1000L; + this.raceId = raceId; + this.raceStatus = raceStatus; + this.expectedStartTime = expectedStartTime; + this.raceWindDirection = raceWindDirection; + this.windSpeed = windSpeed; + this.numBoatsInRace = numBoatsInRace; + this.raceType = raceType; + this.boats = boats; + crc = new CRC32(); + + setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); + } + + /** + * @return the size of this message in bytes + */ + @Override + public int getSize() { + return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace); + } + + /** + * Send this message as a stream of bytes + * @param outputStream The output stream to send the message + */ + @Override + public void send(DataOutputStream outputStream) { + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/); + + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Version Number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); + buff.position(Header.getSize() + 1); + + // Current time, 2 bytes + buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array()); + buff.position(Header.getSize() + 7); + + // Race id, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array()); + buff.position(Header.getSize() + 11); + + // Race status, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array()); + buff.position(Header.getSize() + 12); + + // Expected start time, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array()); + buff.position(Header.getSize() + 18); + + // Wind direction, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array()); + buff.position(Header.getSize() + 20); + + // Wind Speed, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array()); + buff.position(Header.getSize() + 22); + + // Number of boats, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array()); + buff.position(Header.getSize() + 23); + + // Race Type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array()); + buff.position(Header.getSize() + 24); + + int buffPosition = Header.getSize() + 24; + + for (BoatSubMessage boatSubMessage : boats){ + buff.put(boatSubMessage.getByteBuffer()); + buffPosition += boatSubMessage.getSize(); + buff.position(buffPosition); + } + + // calculate CRC + crc.update(buff.array()); + + // Add CRC to message + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + // Send + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/RaceType.java b/src/main/java/seng302/server/messages/RaceType.java new file mode 100644 index 00000000..182b5dfd --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the types of races + * sent by the server + */ +public enum RaceType { + MATCH_RACE(1), + FLEET_RACE(2); + + private long code; + + RaceType(long code){ + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/WindDirection.java b/src/main/java/seng302/server/messages/WindDirection.java new file mode 100644 index 00000000..c0b8d767 --- /dev/null +++ b/src/main/java/seng302/server/messages/WindDirection.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the supported wind directions + */ +public enum WindDirection { + NORTH(0x0000L), + EAST(0x4000L), + SOUTH(0x8000L); + + private long code; + + WindDirection(long code) { + this.code = code; + } + + public long getCode() { + return code; + } +} From 1f8f1f0f863d6eb557d7c4730b767979f6bbe227 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Tue, 25 Apr 2017 21:49:51 +1200 Subject: [PATCH 04/23] Added boat location, and race start messages to the mock data interface - Added proper support for signed and unsigned types. This includes automatic conversion to the correct data type (long to int, short, or byte). - Moved code related to adding values into the byte buffer into the abstract Message class Tags: #story[29] --- .../java/seng302/server/ServerThread.java | 34 ++++- .../server/messages/BoatLocationMessage.java | 109 ++++++++++++++ .../server/messages/BoatSubMessage.java | 11 +- .../seng302/server/messages/DeviceType.java | 16 +++ .../seng302/server/messages/Heartbeat.java | 19 +-- .../java/seng302/server/messages/Message.java | 132 ++++++++++++++++- .../messages/RaceStartNotificationType.java | 21 +++ .../messages/RaceStartStatusMessage.java | 59 ++++++++ .../server/messages/RaceStatusMessage.java | 67 ++------- .../seng302/server/messages/XMLMessage.java | 64 +++------ src/test/java/seng302/server/TestHeader.java | 26 ++++ src/test/java/seng302/server/TestMessage.java | 136 ++++++++++++++++++ 12 files changed, 573 insertions(+), 121 deletions(-) create mode 100644 src/main/java/seng302/server/messages/BoatLocationMessage.java create mode 100644 src/main/java/seng302/server/messages/DeviceType.java create mode 100644 src/main/java/seng302/server/messages/RaceStartNotificationType.java create mode 100644 src/main/java/seng302/server/messages/RaceStartStatusMessage.java create mode 100644 src/test/java/seng302/server/TestHeader.java create mode 100644 src/test/java/seng302/server/TestMessage.java diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index f84887c2..68015574 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -15,6 +15,7 @@ public class ServerThread implements Runnable{ private StreamingServerSocket server; private final int HEARTBEAT_PERIOD = 5000; private final int RACE_STATUS_PERIOD = 1000; + private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; public ServerThread(String threadName){ @@ -68,6 +69,19 @@ public class ServerThread implements Runnable{ 100, 3, RaceType.MATCH_RACE, 1, boats); } + /** + * @return A list of sample boat location messages + */ + public List getTestBoatLocationMessages(){ + List messages = new ArrayList<>(); + + messages.add(new BoatLocationMessage(1, 1, 100, 200, 231, 23)); + messages.add(new BoatLocationMessage(2, 2, 400, 300, 211, 13)); + + return messages; + } + + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -105,7 +119,7 @@ public class ServerThread implements Runnable{ try { server.send(hb); } catch (IOException e) { - e.printStackTrace(); + System.out.print(""); } } }, 0, HEARTBEAT_PERIOD); @@ -120,11 +134,27 @@ public class ServerThread implements Runnable{ try { server.send(statusMessage); } catch (IOException e) { - e.printStackTrace(); + System.out.print(""); } } }, 100, RACE_STATUS_PERIOD); + t1.schedule(new TimerTask() { + @Override + public void run() { + List messages = getTestBoatLocationMessages(); + + for (Message m : messages){ + try { + server.send(m); + } catch (IOException e) { + System.out.print(""); + } + } + + } + }, 100, BOAT_LOCATION_PERIOD); + } catch (IOException e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java new file mode 100644 index 00000000..a475128b --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -0,0 +1,109 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class BoatLocationMessage extends Message { + private final int MESSAGE_SIZE = 56; + + private long messageVersionNumber; + private long time; + private long sourceId; + private long sequenceNum; + private DeviceType deviceType; + private long latitude; + private long longitude; + private long altitude; + private long heading; + private long pitch; + private long roll; + private long boatSpeed; + private long COG; + private long SOG; + private long apparentWindSpeed; + private long apparentWindAngle; + private long trueWindSpeed; + private long trueWindDirection; + private long trueWindAngle; + private long currentDrift; + private long currentSet; + private long rudderAngle; + + /** + * Describes the location, altitude and sensor data from the boat. + * @param sourceId ID of the boat + * @param sequenceNum Sequence number of the message + * @param latitude The boats latitude + * @param longitude The boats longitude + * @param heading The boats heading + * @param boatSpeed The boats speed + */ + public BoatLocationMessage(int sourceId, int sequenceNum, long latitude, long longitude, long heading, long boatSpeed){ + messageVersionNumber = 1; + time = System.currentTimeMillis() / 1000L; + this.sourceId = sourceId; + this.sequenceNum = sequenceNum; + this.deviceType = DeviceType.RACING_YACHT; + this.latitude = -latitude; + this.longitude = longitude; + this.altitude = 0; + this.heading = heading; + this.pitch = 0; + this.roll = 0; + this.boatSpeed = boatSpeed; + this.COG = 0; + this.SOG = 0; + this.apparentWindSpeed = 0; + this.apparentWindAngle = 0; + this.trueWindSpeed = 0; + this.trueWindDirection = 0; + this.trueWindAngle = 0; + this.currentDrift = 0; + this.currentSet = 0; + this.rudderAngle = 0; + + setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); + } + + @Override + public int getSize() { + return 56; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) messageVersionNumber); + putInt((int) time, 6); + putInt((int) sourceId, 4); + putUnsignedInt((int) sequenceNum, 4); + putByte((byte) deviceType.getCode()); + putInt((int) latitude, 4); + putInt((int) longitude, 4); + putInt((int) altitude, 4); + putUnsignedInt((int) heading, 2); + putInt((int) pitch, 2); + putInt((int) roll, 2); + putUnsignedInt((int) boatSpeed, 2); + putUnsignedInt((int) COG, 2); + putUnsignedInt((int) SOG, 2); + putUnsignedInt((int) apparentWindSpeed, 2); + putInt((int) apparentWindAngle, 2); + putUnsignedInt((int) trueWindSpeed, 2); + putUnsignedInt((int) trueWindDirection, 2); + putInt((int) trueWindAngle, 2); + putUnsignedInt((int) currentDrift, 2); + putUnsignedInt((int) currentSet, 2); + putInt((int) rudderAngle, 2); + + + writeCRC(); + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + System.out.print(""); + } + } +} diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/server/messages/BoatSubMessage.java index d403d85b..ebe0f6a2 100644 --- a/src/main/java/seng302/server/messages/BoatSubMessage.java +++ b/src/main/java/seng302/server/messages/BoatSubMessage.java @@ -1,11 +1,12 @@ package seng302.server.messages; +import java.io.DataOutputStream; import java.nio.ByteBuffer; /** * The status of each boat, sent within a race status message */ -public class BoatSubMessage { +public class BoatSubMessage{ private final int MESSAGE_SIZE = 20; private long sourceId; @@ -57,22 +58,22 @@ public class BoatSubMessage { buff.position(buffPos); // Boat Status, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Leg number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Number of penalties awarded, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Number of penalties served, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array()); buffPos += 1; buff.position(buffPos); diff --git a/src/main/java/seng302/server/messages/DeviceType.java b/src/main/java/seng302/server/messages/DeviceType.java new file mode 100644 index 00000000..d245c2b1 --- /dev/null +++ b/src/main/java/seng302/server/messages/DeviceType.java @@ -0,0 +1,16 @@ +package seng302.server.messages; + +public enum DeviceType { + UNKNOWN(0), + RACING_YACHT(1); + + private long code; + + DeviceType(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index a6470240..e5815039 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -26,24 +26,15 @@ public class Heartbeat extends Message { public void send(DataOutputStream outputStream) { setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + getSize()); + allocateBuffer(); + writeHeaderToBuffer(); - // Write header - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); + putUnsignedInt(seqNo, 4); - // Write seq num - buff.put(ByteBuffer.allocate(4).putInt(seqNo).array()); - buff.position(Header.getSize()+4); - - // Write CRC - CRC32 crc = new CRC32(); - crc.update(buff.array()); - - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 8e5c48b4..889a37fa 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,21 +1,27 @@ package seng302.server.messages; import java.io.DataOutputStream; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; public abstract class Message { - Header header; + private final int CRC_SIZE = 4; + private Header header; + private ByteBuffer buffer; + private int bufferPosition; + private CRC32 crc; /** * @param header Set the header for this message */ - public void setHeader(Header header){ + void setHeader(Header header){ this.header = header; } /** * @return the header specified for this message */ - public Header getHeader(){ + Header getHeader(){ return header; } @@ -28,4 +34,124 @@ public abstract class Message { * Send the message as through the outputStream */ public abstract void send(DataOutputStream outputStream); + + /** + * Allocate byte buffer to correct size + */ + void allocateBuffer(){ + buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); + bufferPosition = 0; + } + + /** + * Write the set header to the byte buffer + */ + void writeHeaderToBuffer(){ + buffer.put(getHeader().getByteBuffer()); + bufferPosition += Header.getSize(); + buffer.position(bufferPosition); + } + + /** + * Move the buffer position by n bytes + * @param size Number of bytes to move the buffer by + */ + private void moveBufferPositionBy(int size){ + bufferPosition += size; + buffer.position(bufferPosition); + } + + /** + * Put an unsigned byte in the buffer + */ + void putUnsignedByte(byte b){ + buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array()); + moveBufferPositionBy(1); + } + + /** + * Put an signed byte in the buffer + */ + void putByte(byte b){ + buffer.put(ByteBuffer.allocate(1).put(b).array()); + moveBufferPositionBy(1); + } + + /** + * Place an unsigned integer of the specified length in the buffer + * @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers) + * @param size The size of the int to be added to the buffer + */ + void putUnsignedInt(long val, int size){ + if (size <= 1){ + putUnsignedByte((byte) val); + + } + else if (size < 4){ + // Use short + buffer.put(ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array()); + moveBufferPositionBy(size); + } + else{ + // Use int + buffer.put(ByteBuffer.allocate(size).putInt((int) (val & 0xffffffffL)).array()); + moveBufferPositionBy(size); + } + } + + /** + * Put a signed int of a specified length in the buffer + * @param val The integer value to add + * @param size The size of the integer to be added to the buffer + */ + void putInt(int val, int size){ + if (size < 4){ + buffer.put(ByteBuffer.allocate(size).putShort((short) val).array()); + } + else{ + buffer.put(ByteBuffer.allocate(size).putInt((short) val).array()); + } + moveBufferPositionBy(size); + } + + /** + * Write an array of bytes to the buffer + * @param bytes to write + */ + void putBytes(byte[] bytes){ + buffer.put(bytes); + moveBufferPositionBy(bytes.length); + } + + /** + * Write a ByteBuffer of bytes to the buffer + * @param bytes to write + * @param size number of bytes + */ + void putBytes(ByteBuffer bytes, int size){ + buffer.put(bytes); + moveBufferPositionBy(size); + } + + + /** + * Calculate the CRC of the buffer and append it to the end of the buffer + */ + void writeCRC(){ + crc = new CRC32(); + + buffer.position(0); + crc.update(buffer.array()); + buffer.position(bufferPosition); + + putInt((int) crc.getValue(), CRC_SIZE); + } + + /** + * @return The current buffer + */ + public ByteBuffer getBuffer(){ + return buffer; + } + } diff --git a/src/main/java/seng302/server/messages/RaceStartNotificationType.java b/src/main/java/seng302/server/messages/RaceStartNotificationType.java new file mode 100644 index 00000000..29db3f1e --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStartNotificationType.java @@ -0,0 +1,21 @@ +package seng302.server.messages; + +/** + * The types of race start status messages + */ +public enum RaceStartNotificationType { + SET_RACE_START_TIME(1), + RACE_POSTPONED(2), + RACE_ABANDONED(3), + RACE_TERMINATED(4); + + private final long type; + + RaceStartNotificationType(long type) { + this.type = type; + } + + long getType(){ + return type; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java new file mode 100644 index 00000000..d2878bc2 --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -0,0 +1,59 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class RaceStartStatusMessage extends Message { + private final int MESSAGE_SIZE = 20; + + private long version; + private long timeStamp; + private long ackNumber; + private long raceStartTime; + private long raceId; + private RaceStartNotificationType notificationType; + + /** + * Message sent to clients with the expected start time of the race + * @param ackNumber Sequence number of message. + * @param raceStartTime Expected race start time + * @param raceId Race ID# + * @param notificationType Type of this notification + */ + public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){ + this.version = 1; + this.timeStamp = System.currentTimeMillis() / 1000L; + this.ackNumber = ackNumber; + this.raceStartTime = raceStartTime; + this.notificationType = notificationType; + this.raceId = raceId; + + setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putUnsignedByte((byte) version); + putInt((int) timeStamp, 6); + putInt((int) ackNumber, 2); + putInt((int) raceStartTime, 6); + putInt((int) raceId, 4); + putUnsignedByte((byte) notificationType.getType()); + + writeCRC(); + + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 17ab82fc..77c0c67a 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -11,7 +11,6 @@ public class RaceStatusMessage extends Message{ private final int MESSAGE_VERSION = 2; //Always set to 1 private final int MESSAGE_BASE_SIZE = 24; - // fields private long currentTime; private long raceId; private RaceStatus raceStatus; @@ -56,7 +55,7 @@ public class RaceStatusMessage extends Message{ */ @Override public int getSize() { - return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace); + return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); } /** @@ -65,64 +64,28 @@ public class RaceStatusMessage extends Message{ */ @Override public void send(DataOutputStream outputStream) { - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/); + allocateBuffer(); + writeHeaderToBuffer(); - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); - - // Version Number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); - buff.position(Header.getSize() + 1); - - // Current time, 2 bytes - buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array()); - buff.position(Header.getSize() + 7); - - // Race id, 4 bytes - buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array()); - buff.position(Header.getSize() + 11); - - // Race status, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array()); - buff.position(Header.getSize() + 12); - - // Expected start time, 6 bytes - buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array()); - buff.position(Header.getSize() + 18); - - // Wind direction, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array()); - buff.position(Header.getSize() + 20); - - // Wind Speed, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array()); - buff.position(Header.getSize() + 22); - - // Number of boats, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array()); - buff.position(Header.getSize() + 23); - - // Race Type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array()); - buff.position(Header.getSize() + 24); - - int buffPosition = Header.getSize() + 24; + putByte((byte) MESSAGE_VERSION); + putInt((int) currentTime, 6); + putInt((int) raceId, 4); + putByte((byte) raceStatus.getCode()); + putInt((int) expectedStartTime, 6); + putInt((int) raceWindDirection.getCode(), 2); + putInt((int) windSpeed, 2); + putByte((byte) numBoatsInRace); + putByte((byte) raceType.getCode()); for (BoatSubMessage boatSubMessage : boats){ - buff.put(boatSubMessage.getByteBuffer()); - buffPosition += boatSubMessage.getSize(); - buff.position(buffPosition); + putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize()); } - // calculate CRC - crc.update(buff.array()); - - // Add CRC to message - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); // Send try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index 2d7e0431..cc862d2f 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -11,26 +11,24 @@ public class XMLMessage extends Message{ private final int MESSAGE_SIZE = 14; // Message fields - private int timeStamp; - private short ack = 0x00; //Unused + private long timeStamp; + private long ack = 0x00; //Unused private XMLMessageSubType xmlMessageSubType; - private Short length; - private Short sequence; + private long length; + private long sequence; private String content; - private CRC32 crc; /** * XML Message from the AC35 Streaming data spec * @param content The XML content * @param type The XML Message Sub Type */ - public XMLMessage(String content, XMLMessageSubType type, short sequenceNum){ + public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){ this.content = content; this.xmlMessageSubType = type; - crc = new CRC32(); - timeStamp = (int) (System.currentTimeMillis() / 1000L); + timeStamp = System.currentTimeMillis() / 1000L; ack = 0; - length = (short) this.content.length(); + length = this.content.length(); sequence = sequenceNum; setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); @@ -48,47 +46,23 @@ public class XMLMessage extends Message{ * @param outputStream The output stream to send the message */ public void send(DataOutputStream outputStream) { - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4); - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); + allocateBuffer(); + writeHeaderToBuffer(); - // Version Number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); - buff.position(Header.getSize() + 1); + // Write message fields + putUnsignedByte((byte) MESSAGE_VERSION); + putInt((int) ack, 2); + putInt((int) timeStamp, 6); + putByte((byte)xmlMessageSubType.getType()); + putInt((int) sequence, 2); + putInt((int) length, 2); + putBytes(content.getBytes()); - // Ack, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(ack).array()); - buff.position(Header.getSize() + 3); - - // Timestamp, 6 bytes - buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); - buff.position(Header.getSize() + 9); - - // XML message sub type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)xmlMessageSubType.getType()).array()); - buff.position(Header.getSize() + 10); - - // Seq num, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(sequence).array()); - buff.position(Header.getSize() + 12); - - // Message length, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(length).array()); - buff.position(Header.getSize() + 14); - - // XML Content - buff.put(this.content.getBytes()); - buff.position(Header.getSize() + 14 + this.content.getBytes().length); - - // calculate CRC - crc.update(buff.array()); - - // Add CRC to message - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); // Send try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/test/java/seng302/server/TestHeader.java b/src/test/java/seng302/server/TestHeader.java new file mode 100644 index 00000000..4fa2bc3d --- /dev/null +++ b/src/test/java/seng302/server/TestHeader.java @@ -0,0 +1,26 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.Header; +import seng302.server.messages.MessageType; + +import static junit.framework.TestCase.assertTrue; + +/** + * Tests message header + */ +public class TestHeader { + + @Test + public void testHeaderSizeEqualsActualSize(){ + Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); + assertTrue(h.getSize() == h.getByteBuffer().array().length); + + } + + @Test + public void headerSizeIsSameAsSpec(){ + Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); + assertTrue(h.getSize() == 15); // Spec specifies 15 bytes + } +} diff --git a/src/test/java/seng302/server/TestMessage.java b/src/test/java/seng302/server/TestMessage.java new file mode 100644 index 00000000..71521014 --- /dev/null +++ b/src/test/java/seng302/server/TestMessage.java @@ -0,0 +1,136 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.*; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; + +public class TestMessage { + private static int XML_MESSAGE_LEN = 14; + private static int RACE_STATUS_BASE_LEN = 24; + private static int BOAT_SUB_MESSAGE_LEN = 20; + private static int CRC_LEN = 4; + + /** + * Test generated output is the same as the expected output + */ + @Test + public void testHeatBetBufferOutputLength(){ + Message m = new Heartbeat(1); + List output = new ArrayList<>(); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == (m.getSize() + CRC_LEN + Header.getSize())); + } + + /** + * Test output expected is the same as the spec + */ + @Test + public void testXmlMessageSize(){ + Message m = new XMLMessage("12345", XMLMessageSubType.BOAT, 1); + assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length())); + } + + /** + * Ensure that when no boats are in the race, that only the base message is sent + */ + @Test + public void testRaceStatusMessageBufferLenNoBoats(){ + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 0,RaceType.MATCH_RACE,1, new ArrayList()); + + List output = new ArrayList<>(); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == RACE_STATUS_BASE_LEN + Header.getSize() + CRC_LEN); + } + + /** + * Test that each boat status is added to the message + */ + @Test + public void testRaceStatusMessageBufferLenWithBoats(){ + List boatMessages = new ArrayList<>(); + List output = new ArrayList<>(); + + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + boatMessages.add(boat1); + boatMessages.add(boat2); + boatMessages.add(boat3); + + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 3,RaceType.MATCH_RACE,1, boatMessages); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == (RACE_STATUS_BASE_LEN + (BOAT_SUB_MESSAGE_LEN * 3) + CRC_LEN + Header.getSize())); + } + + /** + * IllegalArgumentException should be thrown when numBoatsInRace is smaller + * than the number of boats actually in the race + */ + @Test(expected = IllegalArgumentException.class) + public void testRaceStatusTooManyBoats(){ + List boatMessages = new ArrayList<>(); + List output = new ArrayList<>(); + + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + boatMessages.add(boat1); + boatMessages.add(boat2); + boatMessages.add(boat3); + + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 1,RaceType.MATCH_RACE,1, boatMessages); + + m.send(new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + System.out.print(""); + } + })); + } +} From bc31987f96f7d629ccf9b81f3a04d6b0c067436a Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Wed, 26 Apr 2017 22:38:39 +1200 Subject: [PATCH 05/23] Added Boat location messages to the mock streaming data interface - Added static methods to convert between binary packed lat/longs and floating point numbers Tags: #story[829] --- .../server/messages/BoatLocationMessage.java | 57 ++++++++++++++++- .../server/messages/MarkRoundingMessage.java | 62 +++++++++++++++++++ .../seng302/server/messages/MarkType.java | 20 ++++++ .../server/messages/RoundingBoatStatus.java | 21 +++++++ .../seng302/server/messages/RoundingSide.java | 20 ++++++ .../java/seng302/server/TestConversions.java | 35 +++++++++++ 6 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/main/java/seng302/server/messages/MarkRoundingMessage.java create mode 100644 src/main/java/seng302/server/messages/MarkType.java create mode 100644 src/main/java/seng302/server/messages/RoundingBoatStatus.java create mode 100644 src/main/java/seng302/server/messages/RoundingSide.java create mode 100644 src/test/java/seng302/server/TestConversions.java diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index a475128b..4b914699 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -65,11 +65,66 @@ public class BoatLocationMessage extends Message { setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); } + /** + * Convert binary latitude or longitude to floating point number + * @param binaryPackedLatLon Binary packed lat OR lon + * @return Floating point lat/lon + */ + public static double binaryPackedToLatLon(long binaryPackedLatLon){ + return (double)binaryPackedLatLon * 180.0 / 2147483648.0; + } + + /** + * Convert binary packed heading to floating point number + * @param binaryPackedHeading Binary packed heading + * @return heading as a decimal + */ + public static double binaryPackedHeadingToDouble(long binaryPackedHeading){ + return (double)binaryPackedHeading * 360.0 / 65536.0; + } + + /** + * Convert binary packed wind angle to floating point number + * @param binaryPackedWindAngle Binary packed wind angle + * @return wind angle as a decimal + */ + public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){ + return (double)binaryPackedWindAngle*180.0/32768.0; + } + + /** + * Convert a latitude or longitude to a binary packed long + * @param latLon A floating point latitude/longitude + * @return A binary packed lat/lon + */ + public static long latLonToBinaryPackedLong(double latLon){ + return (long)((536870912 * latLon) / 45); + } + + /** + * Convert a heading to a binary packed long + * @param heading A floating point heading + * @return A binary packed heading + */ + public static long headingToBinaryPackedLong(double heading){ + return (long)((8192*heading)/45); + } + + /** + * Convert a wind angle to a binary packed long + * @param windAngle Floating point wind angle + * @return A binary packed wind angle + */ + public static long windAngleToBinaryPackedLong(double windAngle){ + return (long)((8192*windAngle)/45); + } + @Override public int getSize() { - return 56; + return MESSAGE_SIZE; } + @Override public void send(DataOutputStream outputStream) { allocateBuffer(); diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/server/messages/MarkRoundingMessage.java new file mode 100644 index 00000000..405c46ff --- /dev/null +++ b/src/main/java/seng302/server/messages/MarkRoundingMessage.java @@ -0,0 +1,62 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class MarkRoundingMessage extends Message{ + private final long MESSAGE_VERSION_NUMBER = 1; + private final int MESSAGE_SIZE = 21; + + private long time; + private long ackNumber; + private long raceId; + private long sourceId; + private RoundingBoatStatus boatStatus; + private RoundingSide roundingSide; + private long markId; + + /** + * This message is sent when a boat passes a mark, start line, or finish line + * The purpose of this is to record the time when yachts cross marks + */ + public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus, + RoundingSide roundingSide, int markId){ + this.time = System.currentTimeMillis() / 1000L; + this.ackNumber = ackNumber; + this.raceId = raceId; + this.sourceId = sourceId; + this.boatStatus = roundingBoatStatus; + this.roundingSide = roundingSide; + this.markId = markId; + + setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize())); + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) MESSAGE_VERSION_NUMBER); + putInt((int) time, 6); + putInt((int) ackNumber, 2); + putInt((int) raceId, 4); + putInt((int) sourceId, 4); + putByte((byte) boatStatus.getCode()); + putByte((byte) roundingSide.getCode()); + putByte((byte) markId); + + writeCRC(); + + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/MarkType.java b/src/main/java/seng302/server/messages/MarkType.java new file mode 100644 index 00000000..abbacc6f --- /dev/null +++ b/src/main/java/seng302/server/messages/MarkType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Types of marks boats can round + */ +public enum MarkType { + UNKNOWN(0), + ROUNDING_MARK(1), + GATE(2); + + private long code; + + MarkType(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/RoundingBoatStatus.java b/src/main/java/seng302/server/messages/RoundingBoatStatus.java new file mode 100644 index 00000000..32eb2447 --- /dev/null +++ b/src/main/java/seng302/server/messages/RoundingBoatStatus.java @@ -0,0 +1,21 @@ +package seng302.server.messages; + +/** + * The status of a boat rounding a mark + */ +public enum RoundingBoatStatus { + UNKNOWN(0), + RACING(1), + DSQ(2), + WITHDRAWN(3); + + private long code; + + RoundingBoatStatus(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/RoundingSide.java b/src/main/java/seng302/server/messages/RoundingSide.java new file mode 100644 index 00000000..5cc4097c --- /dev/null +++ b/src/main/java/seng302/server/messages/RoundingSide.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * The side the boat rounded the mark + */ +public enum RoundingSide { + UNKNOWN(0), + PORT(1), + STARBOARD(2); + + private long code; + + RoundingSide(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/test/java/seng302/server/TestConversions.java b/src/test/java/seng302/server/TestConversions.java new file mode 100644 index 00000000..91bf44b3 --- /dev/null +++ b/src/test/java/seng302/server/TestConversions.java @@ -0,0 +1,35 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.BoatLocationMessage; + +import static junit.framework.TestCase.assertEquals; + +/** + * Test conversions used by the boat location messages + */ +public class TestConversions { + @Test + public void testLatLonConversion(){ + long binaryPacked = BoatLocationMessage.latLonToBinaryPackedLong(3232.323); + double original = BoatLocationMessage.binaryPackedToLatLon(binaryPacked); + + assertEquals(3232.323, original, 0.01); + } + + @Test + public void testWindAngleConversion(){ + long binaryPacked = BoatLocationMessage.windAngleToBinaryPackedLong(3232.323); + double original = BoatLocationMessage.binaryPackedWindAngleToDouble(binaryPacked); + + assertEquals(3232.323, original, 0.01); + } + + @Test + public void testHeadingConversion(){ + long binaryPacked = BoatLocationMessage.headingToBinaryPackedLong(3232.323); + double original = BoatLocationMessage.binaryPackedHeadingToDouble(binaryPacked); + + assertEquals(3232.323, original, 0.01); + } +} From 3bdc6ce5cc70c6255539b2125e5ac9c620d901f1 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 00:56:44 +1200 Subject: [PATCH 06/23] Created a new course parser to parse race xml file specified in AC35 spec. #story[828] --- .../java/seng302/models/mark/MarkType.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/seng302/models/mark/MarkType.java b/src/main/java/seng302/models/mark/MarkType.java index 3de5cba3..43366b1d 100644 --- a/src/main/java/seng302/models/mark/MarkType.java +++ b/src/main/java/seng302/models/mark/MarkType.java @@ -4,6 +4,33 @@ package seng302.models.mark; * To represent two types of mark * Created by Haoming Yin (hyi25) on 17/3/17. */ + + public enum MarkType { - SINGLE_MARK, GATE_MARK + + UNKNOWN(0), + ROUNDING_MARK(1), + GATE_MARK(2), + // above mark types are from AC35 spec. + + // more specific types for gate mark + WINDWARD(201), + LEEWARD(202), + START(203), + FINISH(204), + + // single_mark is from old team-13 code base, for compatibility, it has not + // been removed yet + SINGLE_MARK(5); + + private int type; + + MarkType(int markType) { + this.type = markType; + } + + public int getType() { + return this.type; + } + } From f6b7a3042f6f270eb8ecc76068738a715712ad02 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 01:03:37 +1200 Subject: [PATCH 07/23] Rewrote all kind of marks to fit marks specified in AC35 spec. - added compound mark - added corner - rewrote mark as a single mark - added rounding type enum #story[828] --- .../server/simulator/mark/CompoundMark.java | 57 +++++++++++++++++++ .../seng302/server/simulator/mark/Corner.java | 49 ++++++++++++++++ .../seng302/server/simulator/mark/Mark.java | 54 ++++++++++++++++++ .../server/simulator/mark/RoundingType.java | 28 +++++++++ 4 files changed, 188 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/mark/CompoundMark.java create mode 100644 src/main/java/seng302/server/simulator/mark/Corner.java create mode 100644 src/main/java/seng302/server/simulator/mark/Mark.java create mode 100644 src/main/java/seng302/server/simulator/mark/RoundingType.java diff --git a/src/main/java/seng302/server/simulator/mark/CompoundMark.java b/src/main/java/seng302/server/simulator/mark/CompoundMark.java new file mode 100644 index 00000000..59dc1f62 --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/CompoundMark.java @@ -0,0 +1,57 @@ +package seng302.server.simulator.mark; + +public class CompoundMark { + + private int markID; + private String name; + + private Mark mark1; + private Mark mark2; + + public CompoundMark(int markID, String name) { + this.markID = markID; + this.name = name; + } + + public void addMark(int seqId, Mark mark) { + if (seqId == 1) { + setMark1(mark); + } else if (seqId == 2) { + setMark2(mark); + } + } + + public int getMarkID() { + return markID; + } + + public void setMarkID(int markID) { + this.markID = markID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Mark getMark1() { + return mark1; + } + + public void setMark1(Mark mark1) { + this.mark1 = mark1; + mark1.setSeqID(1); + } + + public Mark getMark2() { + return mark2; + } + + public void setMark2(Mark mark2) { + this.mark2 = mark2; + mark2.setSeqID(2); + } +} diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/server/simulator/mark/Corner.java new file mode 100644 index 00000000..98b4828d --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/Corner.java @@ -0,0 +1,49 @@ +package seng302.server.simulator.mark; + +public class Corner { + + private int seqID; + private CompoundMark compoundMark; + //private int CompoundMarkID; + private RoundingType roundingType; + private int zoneSize; // size of the zone around a mark in boat-lengths. + + public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) { + this.seqID = seqID; + this.compoundMark = compoundMark; + this.roundingType = roundingType; + this.zoneSize = zoneSize; + } + + public int getSeqID() { + return seqID; + } + + public void setSeqID(int seqID) { + this.seqID = seqID; + } + + public CompoundMark getCompoundMark() { + return compoundMark; + } + + public void setCompoundMark(CompoundMark compoundMark) { + this.compoundMark = compoundMark; + } + + public RoundingType getRoundingType() { + return roundingType; + } + + public void setRoundingType(RoundingType roundingType) { + this.roundingType = roundingType; + } + + public int getZoneSize() { + return zoneSize; + } + + public void setZoneSize(int zoneSize) { + this.zoneSize = zoneSize; + } +} diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java new file mode 100644 index 00000000..edcac047 --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -0,0 +1,54 @@ +package seng302.server.simulator.mark; + +/** + * An abstract class to represent general marks + * Created by Haoming Yin (hyi25) on 17/3/17. + */ +public class Mark { + + private int seqID; + private String name; + private double lat; + private double lng; + //private int sourceID; + + public Mark(String name, double lat, double lng) { + this.name = name; + this.lat = lat; + this.lng = lng; + } + + public int getSeqID() { + return seqID; + } + + public void setSeqID(int seqID) { + this.seqID = seqID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } +} + + diff --git a/src/main/java/seng302/server/simulator/mark/RoundingType.java b/src/main/java/seng302/server/simulator/mark/RoundingType.java new file mode 100644 index 00000000..63fae504 --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/RoundingType.java @@ -0,0 +1,28 @@ +package seng302.server.simulator.mark; + +public enum RoundingType{ + + // the mark should be rounded to port (boat's left) + PORT("PS"), + + // the mark should be rounded to starboard (boat's right) + STARBOARD("Stbd"), + + // the boat within the compound mark with the SeqID of 1 should be rounded + // to starboard and the boat within the compound mark with the SeqID of 2 + // should be rounded to port. + SP("SP"), + + // the opposite of SP + PS("PS"); + + private String type; + + RoundingType(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } +} From 7f38191d034bbe9bfeac498a4daf708f05a6816a Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 01:04:30 +1200 Subject: [PATCH 08/23] Rewrote course parser to parse race xml file specified in AC35 spec. #story[828] --- .../simulator/parsers/CourseParser.java | 115 ++++++++++++++++++ .../server/simulator/parsers/FileParser.java | 35 ++++++ 2 files changed, 150 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/parsers/CourseParser.java create mode 100644 src/main/java/seng302/server/simulator/parsers/FileParser.java diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/server/simulator/parsers/CourseParser.java new file mode 100644 index 00000000..831626e4 --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/CourseParser.java @@ -0,0 +1,115 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import seng302.server.simulator.mark.CompoundMark; +import seng302.server.simulator.mark.Corner; +import seng302.server.simulator.mark.Mark; +import seng302.server.simulator.mark.RoundingType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * parse a course xml file + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public class CourseParser extends FileParser { + + private Document doc; + private Map compoundMarksMap; + + public CourseParser(String path) { + super(path); + this.doc = this.parseFile(); + } + + public List getCourse() { + compoundMarksMap = getCompoundMarks(doc); + List corners = new ArrayList<>(); + NodeList cMarksSequence = doc.getElementsByTagName("CompoundMarkSequence"); + + for (int i = 0; i < cMarksSequence.getLength(); i++) { + corners.add(getCorner(cMarksSequence.item(i))); + } + return corners; + } + + + private Corner getCorner(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + + Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); + Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID")); + CompoundMark cMark = compoundMarksMap.get(cMarkId); + RoundingType roundingType = RoundingType.valueOf(e.getAttribute("Rounding")); + Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize")); + + return new Corner(seqId, cMark, roundingType, zoneSize); + } + return null; + } + + private Map getCompoundMarks(Node node) { + Map compoundMarksMap = new HashMap<>(); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + NodeList course = element.getElementsByTagName("Course"); + + // loop through all compound marks who are the children of course node + for (int i = 0; i < course.getLength(); i++) { + CompoundMark cMark = getCompoundMark(course.item(i)); + if (cMark != null) + compoundMarksMap.put(cMark.getMarkID(), cMark); + } + + return compoundMarksMap; + } + return null; + } + + + private CompoundMark getCompoundMark(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + Integer markID = Integer.valueOf(e.getAttribute("CompoundmarkID")); + String name = e.getAttribute("Name"); + CompoundMark cMark = new CompoundMark(markID, name); + + NodeList marks = e.getElementsByTagName("Mark"); + for (int i = 0; i < marks.getLength(); i++) { + Mark mark = getMark(marks.item(i)); + if (mark != null) + cMark.addMark(mark.getSeqID(), mark); + } + return cMark; + } + System.out.println("Failed to create compound mark."); + return null; + } + + + private Mark getMark(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); + String name = e.getAttribute("Name"); + Double lat = Double.valueOf(e.getAttribute("TargetLat")); + Double lng = Double.valueOf(e.getAttribute("TargetLng")); + + Mark mark = new Mark(name, lat, lng); + mark.setSeqID(seqId); + + return mark; + } + System.out.println("Failed to create mark."); + return null; + } + +} diff --git a/src/main/java/seng302/server/simulator/parsers/FileParser.java b/src/main/java/seng302/server/simulator/parsers/FileParser.java new file mode 100644 index 00000000..b82378f5 --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/FileParser.java @@ -0,0 +1,35 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.InputStream; + +/** + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public abstract class FileParser { + + private String filePath; + + public FileParser(String path) { + this.filePath = path; + } + + protected Document parseFile() { + try { + InputStream is = getClass().getResourceAsStream(this.filePath); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(is); + // optional, in order to recover info from broken line. + doc.getDocumentElement().normalize(); + return doc; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + } +} From b2ea8196d5885b9d881d92ec8fb8ac8c30893eaa Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 11:36:27 +1200 Subject: [PATCH 09/23] Fixed a bug of getCourse method as it didn't parse xml correctly. - a typo 'CompoundmarkID'(should be 'CompoundMarkID') which caused parser failed to parse file. - add typeOf method in RoundingType to convert strings to types #story[828] --- .../server/simulator/mark/CompoundMark.java | 13 +++++++++++++ .../seng302/server/simulator/mark/Corner.java | 10 ++++++++++ .../seng302/server/simulator/mark/Mark.java | 9 +++++++++ .../server/simulator/mark/RoundingType.java | 19 +++++++++++++++++-- .../simulator/parsers/CourseParser.java | 18 ++++++++++-------- 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/main/java/seng302/server/simulator/mark/CompoundMark.java b/src/main/java/seng302/server/simulator/mark/CompoundMark.java index 59dc1f62..489a4a12 100644 --- a/src/main/java/seng302/server/simulator/mark/CompoundMark.java +++ b/src/main/java/seng302/server/simulator/mark/CompoundMark.java @@ -21,6 +21,19 @@ public class CompoundMark { } } + /** + * Prints out compoundMark's info and its marks, good for testing + * @return a string showing its details + */ + @Override + public String toString(){ + if (mark2 == null) + return String.format("CompoundMark: %d (%s), [%s]", + markID, name, mark1.toString()); + return String.format("CompoundMark: %d (%s), [%s; %s]", + markID, name, mark1.toString(), mark2.toString()); + } + public int getMarkID() { return markID; } diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/server/simulator/mark/Corner.java index 98b4828d..70f231b5 100644 --- a/src/main/java/seng302/server/simulator/mark/Corner.java +++ b/src/main/java/seng302/server/simulator/mark/Corner.java @@ -15,6 +15,16 @@ public class Corner { this.zoneSize = zoneSize; } + /** + * Prints out corner's info and its compound mark, good for testing + * @return a string showing its details + */ + @Override + public String toString() { + return String.format("Corner: %d - %s - %d, %s\n", + seqID, roundingType.getType(), zoneSize, compoundMark.toString()); + } + public int getSeqID() { return seqID; } diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java index edcac047..0dd2d761 100644 --- a/src/main/java/seng302/server/simulator/mark/Mark.java +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -18,6 +18,15 @@ public class Mark { this.lng = lng; } + /** + * Prints out mark's info and its geo location, good for testing + * @return a string showing its details + */ + @Override + public String toString() { + return String.format("Mark: %d (%s), lat: %f, lng: %f", seqID, name, lat, lng); + } + public int getSeqID() { return seqID; } diff --git a/src/main/java/seng302/server/simulator/mark/RoundingType.java b/src/main/java/seng302/server/simulator/mark/RoundingType.java index 63fae504..de6f6133 100644 --- a/src/main/java/seng302/server/simulator/mark/RoundingType.java +++ b/src/main/java/seng302/server/simulator/mark/RoundingType.java @@ -1,9 +1,9 @@ package seng302.server.simulator.mark; -public enum RoundingType{ +public enum RoundingType { // the mark should be rounded to port (boat's left) - PORT("PS"), + PORT("Port"), // the mark should be rounded to starboard (boat's right) STARBOARD("Stbd"), @@ -25,4 +25,19 @@ public enum RoundingType{ public String getType() { return this.type; } + + public static RoundingType typeOf(String type) { + switch (type) { + case "Port": + return PORT; + case "Stbd": + return STARBOARD; + case "SP": + return SP; + case "PS": + return PS; + default: + return null; + } + } } diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/server/simulator/parsers/CourseParser.java index 831626e4..d055f88e 100644 --- a/src/main/java/seng302/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/server/simulator/parsers/CourseParser.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Map; /** - * parse a course xml file + * Parses the race xml file to get course details * Created by Haoming Yin (hyi25) on 16/3/2017 */ public class CourseParser extends FileParser { @@ -28,10 +28,11 @@ public class CourseParser extends FileParser { this.doc = this.parseFile(); } + // TODO: should handle error / invalid file gracefully public List getCourse() { - compoundMarksMap = getCompoundMarks(doc); + compoundMarksMap = getCompoundMarks(doc.getDocumentElement()); List corners = new ArrayList<>(); - NodeList cMarksSequence = doc.getElementsByTagName("CompoundMarkSequence"); + NodeList cMarksSequence = doc.getElementsByTagName("Corner"); for (int i = 0; i < cMarksSequence.getLength(); i++) { corners.add(getCorner(cMarksSequence.item(i))); @@ -47,7 +48,7 @@ public class CourseParser extends FileParser { Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID")); CompoundMark cMark = compoundMarksMap.get(cMarkId); - RoundingType roundingType = RoundingType.valueOf(e.getAttribute("Rounding")); + RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding")); Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize")); return new Corner(seqId, cMark, roundingType, zoneSize); @@ -60,11 +61,11 @@ public class CourseParser extends FileParser { if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; - NodeList course = element.getElementsByTagName("Course"); + NodeList cMarks = element.getElementsByTagName("CompoundMark"); // loop through all compound marks who are the children of course node - for (int i = 0; i < course.getLength(); i++) { - CompoundMark cMark = getCompoundMark(course.item(i)); + for (int i = 0; i < cMarks.getLength(); i++) { + CompoundMark cMark = getCompoundMark(cMarks.item(i)); if (cMark != null) compoundMarksMap.put(cMark.getMarkID(), cMark); } @@ -78,7 +79,8 @@ public class CourseParser extends FileParser { private CompoundMark getCompoundMark(Node node) { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; - Integer markID = Integer.valueOf(e.getAttribute("CompoundmarkID")); + Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID")); + String name = e.getAttribute("Name"); CompoundMark cMark = new CompoundMark(markID, name); From 2a67f04d15bc7137d8dac509b929ba84fd2b99e5 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 20:56:58 +1200 Subject: [PATCH 10/23] Create GeoUtility to process all geo calculations. - calculate distance between two geo positions - calculate the bearing from one geo position to another - calculate the new geo position by passing original position, bearing and distance #story[828] --- .../seng302/server/simulator/GeoUtility.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/GeoUtility.java diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/server/simulator/GeoUtility.java new file mode 100644 index 00000000..a93be4b3 --- /dev/null +++ b/src/main/java/seng302/server/simulator/GeoUtility.java @@ -0,0 +1,76 @@ +package seng302.server.simulator; + +import javafx.geometry.Pos; +import seng302.server.simulator.mark.Mark; +import seng302.server.simulator.mark.Position; + +public class GeoUtility { + + private static double EARTH_RADIUS = 6378.137; + /** + * Calculates the euclidean distance between two markers on the canvas using xy coordinates + * + * @param p1 first geographical position + * @param p2 second geographical position + * @return the distance in meter between two points in meters + */ + public static Double calculateMarkerDistance(Position p1, Position p2) { + + double dLat = Math.toRadians(p2.getLat() - p1.getLat()); + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + + double a = Math.pow(Math.sin(dLat / 2), 2.0) + + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) + * Math.pow(Math.sin(dLon / 2), 2.0); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = EARTH_RADIUS * c; + + return d * 1000; // distance from km to meter + } + + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the bearing in degree from p1 to p2, value range (0 ~ 360 deg.). + * vertical up is 0 deg. horizontal right is 90 deg. + */ + public static Double getBearing(Position p1, Position p2) { + + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + + double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); + double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) + - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon); + + double bearing = Math.toDegrees(Math.atan2(y, x)); + + return (bearing + 360.0) % 360.0; + } + + /** + * Given an existing point in lat/lng, distance in (in meter) and bearing + * (in degrees), calculates the new lat/lng. + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position + * @param distance the distance in meter, from original position to the new position + * @return the new position + */ + public static Position getGeoCoordinate(Position origin, Double bearing, Double distance) { + double b = Math.toRadians(bearing); // bearing to radians + double d = distance / 1000.0; // distance to km + + double originLat = Math.toRadians(origin.getLat()); + double originLng = Math.toRadians(origin.getLng()); + + double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) + + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); + double endLng = originLng + + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), + Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); + + return new Position(Math.toDegrees(endLat), Math.toDegrees(endLng)); + } +} From 7bf2d4c40ec8f552160418290edb91709a63e1e7 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 22:46:04 +1200 Subject: [PATCH 11/23] Added Position class to better use GeoUtility. - mark now inherit from Position #story[828] --- .../seng302/server/simulator/GeoUtility.java | 12 +++---- .../seng302/server/simulator/mark/Corner.java | 30 ++++++++++++++++++ .../seng302/server/simulator/mark/Mark.java | 30 ++++++------------ .../server/simulator/mark/Position.java | 31 +++++++++++++++++++ .../simulator/parsers/CourseParser.java | 5 +-- 5 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 src/main/java/seng302/server/simulator/mark/Position.java diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/server/simulator/GeoUtility.java index a93be4b3..288e0ee1 100644 --- a/src/main/java/seng302/server/simulator/GeoUtility.java +++ b/src/main/java/seng302/server/simulator/GeoUtility.java @@ -1,12 +1,11 @@ package seng302.server.simulator; -import javafx.geometry.Pos; -import seng302.server.simulator.mark.Mark; import seng302.server.simulator.mark.Position; public class GeoUtility { private static double EARTH_RADIUS = 6378.137; + /** * Calculates the euclidean distance between two markers on the canvas using xy coordinates * @@ -14,7 +13,7 @@ public class GeoUtility { * @param p2 second geographical position * @return the distance in meter between two points in meters */ - public static Double calculateMarkerDistance(Position p1, Position p2) { + public static Double getDistance(Position p1, Position p2) { double dLat = Math.toRadians(p2.getLat() - p1.getLat()); double dLon = Math.toRadians(p2.getLng() - p1.getLng()); @@ -35,7 +34,7 @@ public class GeoUtility { * @param p1 the first geographical position, start point * @param p2 the second geographical position, end point * @return the bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. + * vertical up is 0 deg. horizontal right is 90 deg. */ public static Double getBearing(Position p1, Position p2) { @@ -53,8 +52,9 @@ public class GeoUtility { /** * Given an existing point in lat/lng, distance in (in meter) and bearing * (in degrees), calculates the new lat/lng. - * @param origin the original position within lat / lng - * @param bearing the bearing in degree, from original position to the new position + * + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position * @param distance the distance in meter, from original position to the new position * @return the new position */ diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/server/simulator/mark/Corner.java index 70f231b5..136212f2 100644 --- a/src/main/java/seng302/server/simulator/mark/Corner.java +++ b/src/main/java/seng302/server/simulator/mark/Corner.java @@ -8,6 +8,10 @@ public class Corner { private RoundingType roundingType; private int zoneSize; // size of the zone around a mark in boat-lengths. + // TODO: this shouldn't be used in the future!!!! + private double bearingToNextCorner, distanceToNextCorner; + private Corner nextCorner; + public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) { this.seqID = seqID; this.compoundMark = compoundMark; @@ -56,4 +60,30 @@ public class Corner { public void setZoneSize(int zoneSize) { this.zoneSize = zoneSize; } + + + // TODO: next six setters & getters shouldn't be used in the future. + public double getBearingToNextCorner() { + return bearingToNextCorner; + } + + public void setBearingToNextCorner(double bearingToNextCorner) { + this.bearingToNextCorner = bearingToNextCorner; + } + + public double getDistanceToNextCorner() { + return distanceToNextCorner; + } + + public void setDistanceToNextCorner(double distanceToNextCorner) { + this.distanceToNextCorner = distanceToNextCorner; + } + + public Corner getNextCorner() { + return nextCorner; + } + + public void setNextCorner(Corner nextCorner) { + this.nextCorner = nextCorner; + } } diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java index 0dd2d761..41f00bb6 100644 --- a/src/main/java/seng302/server/simulator/mark/Mark.java +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -4,18 +4,16 @@ package seng302.server.simulator.mark; * An abstract class to represent general marks * Created by Haoming Yin (hyi25) on 17/3/17. */ -public class Mark { +public class Mark extends Position { private int seqID; private String name; - private double lat; - private double lng; - //private int sourceID; + private int sourceID; - public Mark(String name, double lat, double lng) { + public Mark(String name, double lat, double lng, int sourceID) { + super(lat, lng); this.name = name; - this.lat = lat; - this.lng = lng; + this.sourceID = sourceID; } /** @@ -24,7 +22,7 @@ public class Mark { */ @Override public String toString() { - return String.format("Mark: %d (%s), lat: %f, lng: %f", seqID, name, lat, lng); + return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, lng); } public int getSeqID() { @@ -43,20 +41,12 @@ public class Mark { this.name = name; } - public double getLat() { - return lat; + public int getSourceID() { + return sourceID; } - public void setLat(double lat) { - this.lat = lat; - } - - public double getLng() { - return lng; - } - - public void setLng(double lng) { - this.lng = lng; + public void setSourceID(int sourceID) { + this.sourceID = sourceID; } } diff --git a/src/main/java/seng302/server/simulator/mark/Position.java b/src/main/java/seng302/server/simulator/mark/Position.java new file mode 100644 index 00000000..74200e9d --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/Position.java @@ -0,0 +1,31 @@ +package seng302.server.simulator.mark; + +public class Position { + + double lat, lng; + + public Position(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + + public String toString() { + return String.format("Position at lat:%f lng:%f.", lat, lng); + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } +} diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/server/simulator/parsers/CourseParser.java index d055f88e..f7be46cd 100644 --- a/src/main/java/seng302/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/server/simulator/parsers/CourseParser.java @@ -29,7 +29,7 @@ public class CourseParser extends FileParser { } // TODO: should handle error / invalid file gracefully - public List getCourse() { + protected List getCourse() { compoundMarksMap = getCompoundMarks(doc.getDocumentElement()); List corners = new ArrayList<>(); NodeList cMarksSequence = doc.getElementsByTagName("Corner"); @@ -104,8 +104,9 @@ public class CourseParser extends FileParser { String name = e.getAttribute("Name"); Double lat = Double.valueOf(e.getAttribute("TargetLat")); Double lng = Double.valueOf(e.getAttribute("TargetLng")); + Integer sourceId = Integer.valueOf(e.getAttribute("SourceID")); - Mark mark = new Mark(name, lat, lng); + Mark mark = new Mark(name, lat, lng, sourceId); mark.setSeqID(seqId); return mark; From 8b8422de3a78af4c7b23831f4337bee8ab77bbfd Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 22:47:39 +1200 Subject: [PATCH 12/23] Renamed course parser to race parser - because in AC35 spec. race xml file contain course set up and all other general race settings #story[828] --- .../java/seng302/server/simulator/Boat.java | 110 ++++++++++++++++++ .../server/simulator/parsers/BoatsParser.java | 20 ++++ .../server/simulator/parsers/RaceParser.java | 53 +++++++++ 3 files changed, 183 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/Boat.java create mode 100644 src/main/java/seng302/server/simulator/parsers/BoatsParser.java create mode 100644 src/main/java/seng302/server/simulator/parsers/RaceParser.java diff --git a/src/main/java/seng302/server/simulator/Boat.java b/src/main/java/seng302/server/simulator/Boat.java new file mode 100644 index 00000000..3fd6eb87 --- /dev/null +++ b/src/main/java/seng302/server/simulator/Boat.java @@ -0,0 +1,110 @@ +package seng302.server.simulator; + +import seng302.server.simulator.mark.Corner; +import seng302.server.simulator.mark.Position; + +public class Boat { + + private int sourceID; + private double lat; + private double lng; + private double speed; // in mm/sec + private String boatName, shortName, shorterName; + + // haven't been used so far + private Corner lastPassedCorner, headingCorner; + + public Boat(int sourceID, String boatName) { + this.sourceID = sourceID; + this.boatName = boatName; + } + + /** + * Moves boat to the heading direction for a given time duration + * @param heading moving direction in degree. + * @param duration moving duration in millisecond. + */ + public void move(double heading, double duration) { + Double distance = speed * duration / 1000000; // convert mm to meter + Position originPos = new Position(lat, lng); + Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance); + this.lat = newPos.getLat(); + this.lng = newPos.getLng(); + } + + public String toString() { + return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng); + } + + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } + + public double getSpeed() { + return speed; + } + + public void setSpeed(double speed) { + this.speed = speed; + } + + public String getBoatName() { + return boatName; + } + + public void setBoatName(String boatName) { + this.boatName = boatName; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public String getShorterName() { + return shorterName; + } + + public void setShorterName(String shorterName) { + this.shorterName = shorterName; + } + + public Corner getLastPassedCorner() { + return lastPassedCorner; + } + + public void setLastPassedCorner(Corner lastPassedCorner) { + this.lastPassedCorner = lastPassedCorner; + } + + public Corner getHeadingCorner() { + return headingCorner; + } + + public void setHeadingCorner(Corner headingCorner) { + this.headingCorner = headingCorner; + } +} diff --git a/src/main/java/seng302/server/simulator/parsers/BoatsParser.java b/src/main/java/seng302/server/simulator/parsers/BoatsParser.java new file mode 100644 index 00000000..5d552a00 --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/BoatsParser.java @@ -0,0 +1,20 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + + +/** + * Parses the race xml file to get course details + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public class BoatsParser extends FileParser { + + private Document doc; + + public BoatsParser(String path) { + super(path); + this.doc = this.parseFile(); + } + +} diff --git a/src/main/java/seng302/server/simulator/parsers/RaceParser.java b/src/main/java/seng302/server/simulator/parsers/RaceParser.java new file mode 100644 index 00000000..363da64f --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/RaceParser.java @@ -0,0 +1,53 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import seng302.server.simulator.Boat; +import seng302.server.simulator.mark.Corner; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Parses the race xml file to get course details + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public class RaceParser extends FileParser { + + private Document doc; + private String path; + + public RaceParser(String path) { + super(path); + this.path = path; + this.doc = this.parseFile(); + } + + public List getCourse() { + CourseParser cp = new CourseParser(path); + return cp.getCourse(); + } + + public List getBoats() { + NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht"); + List boats = new ArrayList<>(); + + for (int i = 0; i < yachts.getLength(); i++) { + boats.add(getBoat(yachts.item(i))); + } + return boats; + } + + private Boat getBoat(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + + Integer sourceId = Integer.valueOf(e.getAttribute("SourceID")); + return new Boat(sourceId, "Test Boat"); + } + return null; + } +} From 8c8f2532338c8c45c40a832931d496f982277cde Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 22:58:13 +1200 Subject: [PATCH 13/23] Created simulator to generate mock data. - simulator runs as a background thread and sleep for a given time lapse. - simulator extends observable, so it can notify all its observers when boats positions have been updated #story[828] --- src/main/java/seng302/App.java | 2 + .../seng302/server/simulator/Simulator.java | 119 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/Simulator.java diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 1a869e78..6efce9e7 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -6,6 +6,7 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import seng302.server.ServerThread; +import seng302.server.simulator.Simulator; public class App extends Application { @@ -20,6 +21,7 @@ public class App extends Application public static void main(String[] args) { new ServerThread("Racevision Test Server"); + new Thread(new Simulator(1000)).run(); launch(args); } } diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java new file mode 100644 index 00000000..f76d0eec --- /dev/null +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -0,0 +1,119 @@ +package seng302.server.simulator; + +import seng302.server.simulator.mark.Corner; +import seng302.server.simulator.mark.Mark; +import seng302.server.simulator.mark.Position; +import seng302.server.simulator.parsers.RaceParser; + +import java.util.List; +import java.util.Observable; + +public class Simulator extends Observable implements Runnable { + + private List course; + private List boats; + private long lapse; + + /** + * Creates a simulator instance with given time lapse. + * @param lapse time duration in millisecond. + */ + public Simulator(long lapse) { + RaceParser rp = new RaceParser("/server_config/race.xml"); + course = rp.getCourse(); + boats = rp.getBoats(); + this.lapse = lapse; + + setLegs(); + + // set start line's coordinate to boats + Double startLat = course.get(0).getCompoundMark().getMark1().getLat(); + Double startLng = course.get(0).getCompoundMark().getMark1().getLng(); + for (Boat boat : boats) { + boat.setLat(startLat); + boat.setLng(startLng); + boat.setLastPassedCorner(course.get(0)); + boat.setHeadingCorner(course.get(1)); + boat.setSpeed(50000); + } + } + + @Override + public void run() { + + int numOfFinishedBoats = 0; + + while (numOfFinishedBoats < boats.size()) { + for (Boat boat : boats) { + numOfFinishedBoats += moveBoat(boat, lapse); + } + System.out.println(boats.get(0)); + + setChanged(); + notifyObservers(boats); + + // sleep for 1 second. + try { + Thread.sleep(lapse); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Moves a boat with give time duration. + * + * @param boat the boat to be moved + * @param duration the moving duration in millisecond + * @return 1 if boat reached final line, otherwise 0 + */ + private int moveBoat(Boat boat, double duration) { + if (boat.getHeadingCorner() != null) { + + boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); + + Position boatPos = new Position(boat.getLat(), boat.getLng()); + Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1(); + + double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); + // if a boat passes its heading mark + while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) { + double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner(); + boat.setLastPassedCorner(boat.getHeadingCorner()); + boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner()); + + // heading corner == null means boat has reached the final mark + if (boat.getHeadingCorner() == null) return 1; + + // move compensate distance for the mark just passed + Position pos = GeoUtility.getGeoCoordinate( + boat.getLastPassedCorner().getCompoundMark().getMark1(), + boat.getLastPassedCorner().getBearingToNextCorner(), + compensateDistance); + boat.setLat(pos.getLat()); + boat.setLng(pos.getLng()); + distanceFromLastMark = GeoUtility.getDistance(new Position(boat.getLat(), boat.getLng()), + boat.getLastPassedCorner().getCompoundMark().getMark1()); + } + } + return 0; + } + + private void setLegs() { + // get the bearing from one mark to the next heading mark + for (int i = 0; i < course.size() - 1; i++) { + + Mark mark1 = course.get(i).getCompoundMark().getMark1(); + Mark mark2 = course.get(i + 1).getCompoundMark().getMark1(); + course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2)); + + course.get(i).setNextCorner(course.get(i + 1)); + + course.get(i).setBearingToNextCorner( + GeoUtility.getBearing(course.get(i).getCompoundMark().getMark1(), + course.get(i + 1).getCompoundMark().getMark1())); + } + } + +} From 705a0a2eaf938c664d85df4c708b8f088869a631 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Fri, 28 Apr 2017 14:34:24 +1200 Subject: [PATCH 14/23] Added document and unit tests for GeoUtility class. - three methods in GeoUtility have been tested and passed. #story[828] --- src/main/java/seng302/App.java | 4 +- .../seng302/server/simulator/GeoUtility.java | 8 +- .../server/simulator/GeoUtilityTest.java | 75 +++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 src/test/java/seng302/server/simulator/GeoUtilityTest.java diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 6efce9e7..caa4313e 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -24,6 +24,4 @@ public class App extends Application new Thread(new Simulator(1000)).run(); launch(args); } -} - - +} \ No newline at end of file diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/server/simulator/GeoUtility.java index 288e0ee1..dff67e50 100644 --- a/src/main/java/seng302/server/simulator/GeoUtility.java +++ b/src/main/java/seng302/server/simulator/GeoUtility.java @@ -33,8 +33,14 @@ public class GeoUtility { * * @param p1 the first geographical position, start point * @param p2 the second geographical position, end point - * @return the bearing in degree from p1 to p2, value range (0 ~ 360 deg.). + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). * vertical up is 0 deg. horizontal right is 90 deg. + * + * NOTE: + * The final bearing will differ from the initial bearing by varying degrees + * according to distance and latitude (if you were to go from say 35°N,45°E + * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° + * and end up on a heading of 120° */ public static Double getBearing(Position p1, Position p2) { diff --git a/src/test/java/seng302/server/simulator/GeoUtilityTest.java b/src/test/java/seng302/server/simulator/GeoUtilityTest.java new file mode 100644 index 00000000..4cdf01df --- /dev/null +++ b/src/test/java/seng302/server/simulator/GeoUtilityTest.java @@ -0,0 +1,75 @@ +package seng302.server.simulator; + +import org.junit.Test; +import seng302.server.simulator.mark.Position; + +import static org.junit.Assert.*; + +/** + * To test methods in GeoUtility. + * Created by Haoming on 28/04/17. + */ +public class GeoUtilityTest { + + private Position p1 = new Position(57.670333, 11.827833); + private Position p2 = new Position(57.671524, 11.844495); + private Position p3 = new Position(57.670822, 11.843392); + private Position p4 = new Position(25.694829, 98.392049); + + private double toleranceRate = 0.01; + + @Test + public void getDistance() throws Exception { + double expected, actual; + + actual = GeoUtility.getDistance(p1, p2); + expected = 1000; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p1, p3); + expected = 927; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p2, p4); + expected = 7430180; + assertEquals(expected, actual, expected * toleranceRate); + } + + @Test + public void getBearing() throws Exception { + double expected, actual; + + actual = GeoUtility.getBearing(p1, p2); + expected = 82; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p1, p3); + expected = 86; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p2, p4); + expected = 78; + assertEquals(expected, actual, expected * toleranceRate); + } + + @Test + public void getGeoCoordinate() throws Exception { + Position expected, actual; + + actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); + expected = p2; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0); + expected = p3; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0); + expected = p4; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + } + +} \ No newline at end of file From 8a04a0e5b741f0907aaa2337f2e0ffe620e3508c Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Fri, 28 Apr 2017 14:53:26 +1200 Subject: [PATCH 15/23] Added documents for Boat, RaceParser and Simulator classes. #story[828] --- src/main/java/seng302/server/simulator/Boat.java | 1 - .../java/seng302/server/simulator/Simulator.java | 11 ++++++++--- .../server/simulator/parsers/RaceParser.java | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/seng302/server/simulator/Boat.java b/src/main/java/seng302/server/simulator/Boat.java index 3fd6eb87..c5b821bf 100644 --- a/src/main/java/seng302/server/simulator/Boat.java +++ b/src/main/java/seng302/server/simulator/Boat.java @@ -11,7 +11,6 @@ public class Boat { private double speed; // in mm/sec private String boatName, shortName, shorterName; - // haven't been used so far private Corner lastPassedCorner, headingCorner; public Boat(int sourceID, String boatName) { diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index f76d0eec..1d285884 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -62,11 +62,11 @@ public class Simulator extends Observable implements Runnable { } /** - * Moves a boat with give time duration. + * Moves a boat with given time duration. * * @param boat the boat to be moved - * @param duration the moving duration in millisecond - * @return 1 if boat reached final line, otherwise 0 + * @param duration the moving duration in milliseconds + * @return 1 if the boat has reached the final line, otherwise return 0 */ private int moveBoat(Boat boat, double duration) { if (boat.getHeadingCorner() != null) { @@ -100,6 +100,11 @@ public class Simulator extends Observable implements Runnable { return 0; } + /** + * Link all the corners in the course list so that every corner knows its next + * corner, as well as the distance and bearing to its next corner. However, + * the last corner's heading is null, which means it is the final line. + */ private void setLegs() { // get the bearing from one mark to the next heading mark for (int i = 0; i < course.size() - 1; i++) { diff --git a/src/main/java/seng302/server/simulator/parsers/RaceParser.java b/src/main/java/seng302/server/simulator/parsers/RaceParser.java index 363da64f..14bf7bb8 100644 --- a/src/main/java/seng302/server/simulator/parsers/RaceParser.java +++ b/src/main/java/seng302/server/simulator/parsers/RaceParser.java @@ -9,7 +9,6 @@ import seng302.server.simulator.mark.Corner; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * Parses the race xml file to get course details @@ -26,11 +25,20 @@ public class RaceParser extends FileParser { this.doc = this.parseFile(); } + /** + * Parses race.xml file and returns a list of corner which is the race course. + * @return a list of ordered corner to represent the course. + */ public List getCourse() { CourseParser cp = new CourseParser(path); return cp.getCourse(); } + /** + * Parses race.xml file and return a list of boats which will compete in the + * race. + * @return a list of boats that are going to compete in the race. + */ public List getBoats() { NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht"); List boats = new ArrayList<>(); @@ -41,6 +49,11 @@ public class RaceParser extends FileParser { return boats; } + /** + * Parses a single boat from the given node + * @param node a node within a boat tag + * @return a boat instance parsed from the given node + */ private Boat getBoat(Node node) { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; From 3e97f016d5b84745a2ce7d6b7b254c4bf54a75b9 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Sat, 29 Apr 2017 19:38:21 +1200 Subject: [PATCH 16/23] Getting boat locations from race simulator & bug fixes - Boat locations that are generated by the simulator are sent to the client as they happen - Fixed heading and lat/lon encoding - Fixed a bug where the header wasn't included in the sent byte stream - Fixed the format of data as it's sent to the client. - Data is now sent using a channel - Removed tests that don't work with channels Tags: #story[829] --- src/main/java/seng302/App.java | 2 +- .../java/seng302/server/ServerThread.java | 262 +++++++++++------- .../seng302/server/StreamingServerSocket.java | 32 ++- .../server/messages/BoatLocationMessage.java | 36 +-- .../java/seng302/server/messages/Header.java | 43 +-- .../seng302/server/messages/Heartbeat.java | 11 +- .../server/messages/MarkRoundingMessage.java | 11 +- .../java/seng302/server/messages/Message.java | 70 ++++- .../seng302/server/messages/MessageType.java | 2 +- .../messages/RaceStartStatusMessage.java | 11 +- .../server/messages/RaceStatusMessage.java | 12 +- .../seng302/server/messages/XMLMessage.java | 12 +- .../seng302/server/simulator/Simulator.java | 6 +- src/main/resources/server_config/boats.xml | 65 +---- src/test/java/seng302/server/TestHeader.java | 4 +- src/test/java/seng302/server/TestMessage.java | 105 ------- 16 files changed, 330 insertions(+), 354 deletions(-) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index caa4313e..5b308813 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -21,7 +21,7 @@ public class App extends Application public static void main(String[] args) { new ServerThread("Racevision Test Server"); - new Thread(new Simulator(1000)).run(); + //new Thread(new Simulator(1000)).run(); launch(args); } } \ No newline at end of file diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 68015574..8059827b 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -1,29 +1,44 @@ package seng302.server; import seng302.server.messages.*; +import seng302.server.simulator.Boat; +import seng302.server.simulator.Simulator; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; -public class ServerThread implements Runnable{ +public class ServerThread implements Runnable, Observer { private Thread runner; private StreamingServerSocket server; + private long startTime; + boolean raceStarted = false; + private List boats; + private Simulator raceSimulator; + private final int HEARTBEAT_PERIOD = 5000; - private final int RACE_STATUS_PERIOD = 1000; + private final int RACE_STATUS_PERIOD = 1000/2; + private final int RACE_START_STATUS_PERIOD = 1000/2; private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; + private final int TIME_TILL_RACE_START = 10; + private static final int LOG_LEVEL = 1; public ServerThread(String threadName){ runner = new Thread(this, threadName); - System.out.println("Spawning Server Thread"); + serverLog("Spawning Server", 0); + raceSimulator = new Simulator(BOAT_LOCATION_PERIOD); + boats = raceSimulator.getBoats(); runner.start(); } + public static void serverLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[SERVER] " + message); + } + } + /** * Creates and returns an XML Message from the file specified * @param fileName The source XML file @@ -38,6 +53,9 @@ public class ServerThread implements Runnable{ } catch (IOException e) { e.printStackTrace(); } + catch (NullPointerException e){ + return null; + } if (fileContents != null){ return new XMLMessage(fileContents, type, server.getSequenceNumber()); @@ -47,56 +65,113 @@ public class ServerThread implements Runnable{ } /** - * @return A sample race status message + * @return Get a race status message for the current race */ - public Message getTestRaceStatusMessage(){ - BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); + public Message getRaceStatusMessage(){ + List boatSubMessages = new ArrayList(); + BoatStatus boatStatus; + RaceStatus raceStatus; - BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); + if (raceStarted){ + boatStatus = BoatStatus.RACING; + raceStatus = RaceStatus.STARTED; + } + else{ + boatStatus = BoatStatus.PRESTART; + raceStatus = RaceStatus.PRESTART; + } - BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); + for (Boat b : boats){ + BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, 0, 0); + boatSubMessages.add(m); + } - - List boats = new ArrayList(); - boats.add(boat1); - boats.add(boat2); - boats.add(boat3); - - return new RaceStatusMessage(1, RaceStatus.PRESTART, 1000, WindDirection.EAST, - 100, 3, RaceType.MATCH_RACE, 1, boats); + return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.EAST, + 100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages); } /** - * @return A list of sample boat location messages + * Starts an instance of the race simulator */ - public List getTestBoatLocationMessages(){ - List messages = new ArrayList<>(); - - messages.add(new BoatLocationMessage(1, 1, 100, 200, 231, 23)); - messages.add(new BoatLocationMessage(2, 2, 400, 300, 211, 13)); - - return messages; + private void startRaceSim(){ + serverLog("Starting Race Simulator", 0); + raceSimulator.addObserver(this); + new Thread(raceSimulator).start(); } + /** + * Starts sending heartbeat messages to the client + */ + private void startSendingHeartbeats(){ + serverLog("Sending Heartbeats", 0); + Timer t = new Timer(); - public void run() { + t.schedule(new TimerTask() { + @Override + public void run() { + Message heartbeat = new Heartbeat(server.getSequenceNumber()); + + try { + server.send(heartbeat); + } catch (IOException e) { + System.out.print(""); + } + } + }, 0, HEARTBEAT_PERIOD); + } + + /** + * Start sending race start status messages until race starts + */ + private void startSendingRaceStartStatusMessages(){ + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1, + RaceStartNotificationType.SET_RACE_START_TIME); + try { + if (startTime < System.currentTimeMillis()/1000 && !raceStarted){ + startRaceSim(); + raceStarted = true; + serverLog("Race Started", 0); + } + else{ + server.send(raceStartStatusMessage); + } + + } catch (IOException e) { + System.out.print(""); + } + } + }, 0, RACE_START_STATUS_PERIOD); + } + + private void startSendingRaceStatusMessages(){ + serverLog("Sending Race Status Messages", 0); + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + Message statusMessage = getRaceStatusMessage(); + + try { + server.send(statusMessage); + } catch (IOException e) { + System.out.print(""); + } + } + }, 100, RACE_STATUS_PERIOD); + } + + /** + * Sends the race, boat, and regatta XML files to the client + */ + private void sendXml(){ try{ - server = new StreamingServerSocket(PORT_NUMBER); - } - catch (IOException e){ - System.err.println("Failed to bind socket: " + e.getMessage()); - } - - server.start(); - - try { - // Load and send race XML data - Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); - Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); - Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); + Message raceData = getXmlMessage("server_config/race.xml", XMLMessageSubType.RACE); + Message boatData = getXmlMessage("server_config/boats.xml", XMLMessageSubType.BOAT); + Message regatta = getXmlMessage("server_config/regatta.xml", XMLMessageSubType.REGATTA); if (raceData != null){ server.send(raceData); @@ -106,57 +181,56 @@ public class ServerThread implements Runnable{ server.send(boatData); } - if (regatta != null){ - server.send(regatta); + if (regatta != null){ + server.send(regatta); } - - // Timer to send the heartbeat - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - Message hb = new Heartbeat(server.getSequenceNumber()); - try { - server.send(hb); - } catch (IOException e) { - System.out.print(""); - } - } - }, 0, HEARTBEAT_PERIOD); - - // Timer to send the race status messages - Timer t1 = new Timer(); - t1.schedule(new TimerTask() { - @Override - public void run() { - Message statusMessage = getTestRaceStatusMessage(); - - try { - server.send(statusMessage); - } catch (IOException e) { - System.out.print(""); - } - } - }, 100, RACE_STATUS_PERIOD); - - t1.schedule(new TimerTask() { - @Override - public void run() { - List messages = getTestBoatLocationMessages(); - - for (Message m : messages){ - try { - server.send(m); - } catch (IOException e) { - System.out.print(""); - } - } - - } - }, 100, BOAT_LOCATION_PERIOD); - } catch (IOException e) { - System.err.println(e.getMessage()); + serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); + } + } + + public void run() { + try{ + server = new StreamingServerSocket(PORT_NUMBER); + } + catch (IOException e){ + serverLog("Failed to bind socket: " + e.getMessage(), 0); + } + + startTime = (System.currentTimeMillis()/1000) + TIME_TILL_RACE_START; + + // Wait for client to connect + server.start(); + + startSendingHeartbeats(); + sendXml(); + startSendingRaceStartStatusMessages(); + startSendingRaceStatusMessages(); + + } + + /** + * Send a boat location message when they are updated by the simulator + * @param o . + * @param arg . + */ + @Override + public void update(Observable o, Object arg) { + // Only send if server started + if(!server.isStarted()){ + return; + } + + for (Boat b : ((Simulator) o).getBoats()){ + try { + Message m = new BoatLocationMessage(b.getSourceID(), 1, b.getLat(), + b.getLng(), b.getHeadingCorner().getBearingToNextCorner(), + ((long) b.getSpeed())); + + server.send(m); + } catch (IOException e) { + serverLog("Couldn't send a boat status message", 1); + } } } } diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java index d34eae8a..0818bd9f 100644 --- a/src/main/java/seng302/server/StreamingServerSocket.java +++ b/src/main/java/seng302/server/StreamingServerSocket.java @@ -4,35 +4,44 @@ import seng302.server.messages.Message; import java.io.DataOutputStream; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.Socket; +import java.nio.channels.Channels; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.List; class StreamingServerSocket { - private java.net.ServerSocket socket; - private Socket client; + private ServerSocketChannel socket; + private SocketChannel client; private List clients; private short seqNum; + private boolean isServerStarted; StreamingServerSocket(int port) throws IOException{ - socket = new java.net.ServerSocket(port); + socket = ServerSocketChannel.open(); + socket.socket().bind(new InetSocketAddress("localhost", port)); clients = new ArrayList<>(); - socket.setSoTimeout(10000); + //socket.setSoTimeout(10000); seqNum = 0; + isServerStarted = false; } void start(){ - System.out.println("Listening For Connections"); + ServerThread.serverLog("Listening For Connections",0); try { client = socket.accept(); } catch (IOException e) { e.getMessage(); } - if (client == null){ + if (client.socket() == null){ start(); } else{ - System.out.println("client connected from " + client.getInetAddress()); + isServerStarted = true; + ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0); } } @@ -41,8 +50,9 @@ class StreamingServerSocket { return; } - DataOutputStream outputStream = new DataOutputStream(client.getOutputStream()); - message.send(outputStream); + //DataOutputStream outputStream = new DataOutputStream(client.getOutputStream()); + //System.out.println(client); + message.send(client); seqNum++; } @@ -50,4 +60,8 @@ class StreamingServerSocket { public short getSequenceNumber(){ return seqNum; } + + public boolean isStarted(){ + return isServerStarted; + } } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index 4b914699..45dd3ad5 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -2,6 +2,9 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; public class BoatLocationMessage extends Message { private final int MESSAGE_SIZE = 56; @@ -11,10 +14,10 @@ public class BoatLocationMessage extends Message { private long sourceId; private long sequenceNum; private DeviceType deviceType; - private long latitude; - private long longitude; + private double latitude; + private double longitude; private long altitude; - private long heading; + private Double heading; private long pitch; private long roll; private long boatSpeed; @@ -38,13 +41,13 @@ public class BoatLocationMessage extends Message { * @param heading The boats heading * @param boatSpeed The boats speed */ - public BoatLocationMessage(int sourceId, int sequenceNum, long latitude, long longitude, long heading, long boatSpeed){ + public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ messageVersionNumber = 1; time = System.currentTimeMillis() / 1000L; this.sourceId = sourceId; this.sequenceNum = sequenceNum; this.deviceType = DeviceType.RACING_YACHT; - this.latitude = -latitude; + this.latitude = latitude; this.longitude = longitude; this.altitude = 0; this.heading = heading; @@ -126,19 +129,23 @@ public class BoatLocationMessage extends Message { @Override - public void send(DataOutputStream outputStream) { + public void send(SocketChannel outputStream) throws IOException{ allocateBuffer(); writeHeaderToBuffer(); + heading = (heading + 180.0) % 360.0; + + long headingToSend = (long)((heading/360.0)*65535.0); + putByte((byte) messageVersionNumber); - putInt((int) time, 6); + putInt(time, 6); putInt((int) sourceId, 4); putUnsignedInt((int) sequenceNum, 4); putByte((byte) deviceType.getCode()); - putInt((int) latitude, 4); - putInt((int) longitude, 4); + putInt((int) latLonToBinaryPackedLong(latitude), 4); + putInt((int) latLonToBinaryPackedLong(longitude), 4); putInt((int) altitude, 4); - putUnsignedInt((int) heading, 2); + putInt(headingToSend, 2); putInt((int) pitch, 2); putInt((int) roll, 2); putUnsignedInt((int) boatSpeed, 2); @@ -153,12 +160,9 @@ public class BoatLocationMessage extends Message { putUnsignedInt((int) currentSet, 2); putInt((int) rudderAngle, 2); - writeCRC(); - try { - outputStream.write(getBuffer().array()); - } catch (IOException e) { - System.out.print(""); - } + rewind(); + + outputStream.write(getBuffer()); } } diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/server/messages/Header.java index 9f1a81e0..c4dc6251 100644 --- a/src/main/java/seng302/server/messages/Header.java +++ b/src/main/java/seng302/server/messages/Header.java @@ -1,6 +1,9 @@ package seng302.server.messages; +import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Collections; public class Header { // From API spec @@ -12,6 +15,8 @@ public class Header { private int sourceId; private short messageLength; private static final int MESSAGE_LEN = 15; + private ByteBuffer buff; + private int buffPos; /** * Message Header from section 3.2 of the AC35 Streaming @@ -25,38 +30,34 @@ public class Header { this.sourceId = sourceId; this.messageLength = messageLength; timeStamp = (int) (System.currentTimeMillis() / 1000L); + buff = ByteBuffer.allocate(MESSAGE_LEN); + buffPos = 0; + } + + private void putInBuffer(byte[] bytes, long val){ + byte[] tmp = bytes.clone(); + Message.reverse(tmp); + + buff.put(tmp); + buffPos += tmp.length; + buff.position(buffPos); } /** * @return a ByteBuffer containing the message header */ public ByteBuffer getByteBuffer(){ - ByteBuffer buff = ByteBuffer.allocate(15); + putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1); - // Sync Byte 1, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)syncByte1).array()); - buff.position(1); + putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2); - // Sync Byte 2, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)syncByte2).array()); - buff.position(2); + putInBuffer(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array(), messageType.getCode()); - // Message Type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array()); - buff.position(3); + putInBuffer(Message.intToByteArray(timeStamp, 6), timeStamp); - // Timestamp, 6 bytes - int x = ((int) Integer.toUnsignedLong(6)); - buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); - buff.position(9); + putInBuffer(Message.intToByteArray(sourceId, 4), sourceId); - // Source ID, 4 bytes - buff.put(ByteBuffer.allocate(4).putInt(sourceId).array()); - buff.position(13); - - // Message Length, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(messageLength).array()); - buff.position(15); + putInBuffer(Message.intToByteArray(messageLength, 2), messageLength); return buff; } diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index e5815039..3660af23 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -3,6 +3,9 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; import java.util.zip.CRC32; public class Heartbeat extends Message { @@ -23,7 +26,7 @@ public class Heartbeat extends Message { } @Override - public void send(DataOutputStream outputStream) { + public void send(SocketChannel outputStream) throws IOException { setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); allocateBuffer(); @@ -33,10 +36,6 @@ public class Heartbeat extends Message { writeCRC(); - try { - outputStream.write(getBuffer().array()); - } catch (IOException e) { - e.printStackTrace(); - } + outputStream.write(getBuffer()); } } \ No newline at end of file diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/server/messages/MarkRoundingMessage.java index 405c46ff..82ef685d 100644 --- a/src/main/java/seng302/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/server/messages/MarkRoundingMessage.java @@ -2,6 +2,9 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; public class MarkRoundingMessage extends Message{ private final long MESSAGE_VERSION_NUMBER = 1; @@ -38,7 +41,7 @@ public class MarkRoundingMessage extends Message{ } @Override - public void send(DataOutputStream outputStream) { + public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -53,10 +56,6 @@ public class MarkRoundingMessage extends Message{ writeCRC(); - try { - outputStream.write(getBuffer().array()); - } catch (IOException e) { - e.printStackTrace(); - } + outputStream.write(getBuffer()); } } diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 889a37fa..2ef71afe 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,7 +1,13 @@ package seng302.server.messages; import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Arrays; import java.util.zip.CRC32; public abstract class Message { @@ -33,13 +39,14 @@ public abstract class Message { /** * Send the message as through the outputStream */ - public abstract void send(DataOutputStream outputStream); + public abstract void send(SocketChannel outputStream) throws IOException; /** * Allocate byte buffer to correct size */ void allocateBuffer(){ buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); + buffer.order(ByteOrder.LITTLE_ENDIAN); bufferPosition = 0; } @@ -47,7 +54,7 @@ public abstract class Message { * Write the set header to the byte buffer */ void writeHeaderToBuffer(){ - buffer.put(getHeader().getByteBuffer()); + buffer.put(getHeader().getByteBuffer().array()); bufferPosition += Header.getSize(); buffer.position(bufferPosition); } @@ -89,12 +96,15 @@ public abstract class Message { } else if (size < 4){ // Use short - buffer.put(ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array()); + byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array(); + reverse(tmp); + buffer.put(tmp); moveBufferPositionBy(size); } else{ // Use int - buffer.put(ByteBuffer.allocate(size).putInt((int) (val & 0xffffffffL)).array()); + byte[] tmp = Message.intToByteArray(val, size); + reverse(tmp); moveBufferPositionBy(size); } } @@ -104,12 +114,16 @@ public abstract class Message { * @param val The integer value to add * @param size The size of the integer to be added to the buffer */ - void putInt(int val, int size){ + void putInt(long val, int size){ if (size < 4){ - buffer.put(ByteBuffer.allocate(size).putShort((short) val).array()); + byte[] tmp = Message.intToByteArray(val, size); + reverse(tmp); + buffer.put(tmp); } else{ - buffer.put(ByteBuffer.allocate(size).putInt((short) val).array()); + byte[] tmp = Message.intToByteArray(val, size); + reverse(tmp); + buffer.put(tmp); } moveBufferPositionBy(size); } @@ -141,7 +155,9 @@ public abstract class Message { crc = new CRC32(); buffer.position(0); - crc.update(buffer.array()); + + byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE); + crc.update(data); buffer.position(bufferPosition); putInt((int) crc.getValue(), CRC_SIZE); @@ -154,4 +170,42 @@ public abstract class Message { return buffer; } + + /** + * Rewind the buffer to the beginning + */ + void rewind(){ + buffer.flip(); + } + + /** + * Convert an integer to an array of bytes + * @param val The value to add + * @param len The width of the integer in the buffer + * @return + */ + public static byte[] intToByteArray(long val, int len){ + int index = 0; + byte[] data = new byte[len]; + + for (int i = 0; i < len; i++){ + data[len - index - 1] = (byte) ((val >>> (8 * index))); + index++; + } + + return data; + } + + /** + * Reverse an array of bytes + * @param data The byte[] to reverse + */ + public static void reverse(byte[] data) { + for (int left = 0, right = data.length - 1; left < right; left++, right--) { + byte temp = data[left]; + data[left] = data[right]; + data[right] = temp; + } + } + } diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/server/messages/MessageType.java index be856dac..01bad14b 100644 --- a/src/main/java/seng302/server/messages/MessageType.java +++ b/src/main/java/seng302/server/messages/MessageType.java @@ -9,7 +9,7 @@ public enum MessageType { RACE_STATUS(12), DISPLAY_TEXT_MESSAGE(20), XML_MESSAGE(26), - RACE_START_STATUS(27), + RACE_START_STATUS(20), YACHT_EVENT_CODE(29), YACHT_ACTION_CODE(31), CHATTER_TEXT(36), diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java index d2878bc2..a89471b8 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -2,6 +2,9 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; public class RaceStartStatusMessage extends Message { private final int MESSAGE_SIZE = 20; @@ -37,7 +40,7 @@ public class RaceStartStatusMessage extends Message { } @Override - public void send(DataOutputStream outputStream) { + public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -50,10 +53,6 @@ public class RaceStartStatusMessage extends Message { writeCRC(); - try { - outputStream.write(getBuffer().array()); - } catch (IOException e) { - e.printStackTrace(); - } + outputStream.write(getBuffer()); } } diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 77c0c67a..37aa3fe7 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -3,6 +3,9 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.zip.CRC32; @@ -63,7 +66,7 @@ public class RaceStatusMessage extends Message{ * @param outputStream The output stream to send the message */ @Override - public void send(DataOutputStream outputStream) { + public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -83,11 +86,6 @@ public class RaceStatusMessage extends Message{ writeCRC(); - // Send - try { - outputStream.write(getBuffer().array()); - } catch (IOException e) { - e.printStackTrace(); - } + outputStream.write(getBuffer()); } } diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index cc862d2f..a0044ea7 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -3,6 +3,9 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; import java.util.zip.CRC32; public class XMLMessage extends Message{ @@ -45,7 +48,7 @@ public class XMLMessage extends Message{ * Send this message as a stream of bytes * @param outputStream The output stream to send the message */ - public void send(DataOutputStream outputStream) { + public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -60,11 +63,6 @@ public class XMLMessage extends Message{ writeCRC(); - // Send - try { - outputStream.write(getBuffer().array()); - } catch (IOException e) { - e.printStackTrace(); - } + outputStream.write(getBuffer()); } } diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index 1d285884..0bdd9716 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -47,7 +47,7 @@ public class Simulator extends Observable implements Runnable { for (Boat boat : boats) { numOfFinishedBoats += moveBoat(boat, lapse); } - System.out.println(boats.get(0)); + //System.out.println(boats.get(0)); setChanged(); notifyObservers(boats); @@ -121,4 +121,8 @@ public class Simulator extends Observable implements Runnable { } } + public List getBoats(){ + return boats; + } + } diff --git a/src/main/resources/server_config/boats.xml b/src/main/resources/server_config/boats.xml index df5ddca4..f5e1e1fb 100644 --- a/src/main/resources/server_config/boats.xml +++ b/src/main/resources/server_config/boats.xml @@ -137,50 +137,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -211,24 +167,5 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/test/java/seng302/server/TestHeader.java b/src/test/java/seng302/server/TestHeader.java index 4fa2bc3d..3655bc03 100644 --- a/src/test/java/seng302/server/TestHeader.java +++ b/src/test/java/seng302/server/TestHeader.java @@ -1,8 +1,7 @@ package seng302.server; import org.junit.Test; -import seng302.server.messages.Header; -import seng302.server.messages.MessageType; +import seng302.server.messages.*; import static junit.framework.TestCase.assertTrue; @@ -23,4 +22,5 @@ public class TestHeader { Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); assertTrue(h.getSize() == 15); // Spec specifies 15 bytes } + } diff --git a/src/test/java/seng302/server/TestMessage.java b/src/test/java/seng302/server/TestMessage.java index 71521014..8d8dfa46 100644 --- a/src/test/java/seng302/server/TestMessage.java +++ b/src/test/java/seng302/server/TestMessage.java @@ -17,24 +17,6 @@ public class TestMessage { private static int BOAT_SUB_MESSAGE_LEN = 20; private static int CRC_LEN = 4; - /** - * Test generated output is the same as the expected output - */ - @Test - public void testHeatBetBufferOutputLength(){ - Message m = new Heartbeat(1); - List output = new ArrayList<>(); - - DataOutputStream ds = new DataOutputStream(new OutputStream() { - @Override - public void write(int b) throws IOException { - output.add(b); - } - }); - - m.send(ds); - assertTrue(output.size() == (m.getSize() + CRC_LEN + Header.getSize())); - } /** * Test output expected is the same as the spec @@ -45,92 +27,5 @@ public class TestMessage { assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length())); } - /** - * Ensure that when no boats are in the race, that only the base message is sent - */ - @Test - public void testRaceStatusMessageBufferLenNoBoats(){ - Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, - 0,RaceType.MATCH_RACE,1, new ArrayList()); - List output = new ArrayList<>(); - - DataOutputStream ds = new DataOutputStream(new OutputStream() { - @Override - public void write(int b) throws IOException { - output.add(b); - } - }); - - m.send(ds); - assertTrue(output.size() == RACE_STATUS_BASE_LEN + Header.getSize() + CRC_LEN); - } - - /** - * Test that each boat status is added to the message - */ - @Test - public void testRaceStatusMessageBufferLenWithBoats(){ - List boatMessages = new ArrayList<>(); - List output = new ArrayList<>(); - - BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); - - BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); - - BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); - - boatMessages.add(boat1); - boatMessages.add(boat2); - boatMessages.add(boat3); - - Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, - 3,RaceType.MATCH_RACE,1, boatMessages); - - DataOutputStream ds = new DataOutputStream(new OutputStream() { - @Override - public void write(int b) throws IOException { - output.add(b); - } - }); - - m.send(ds); - assertTrue(output.size() == (RACE_STATUS_BASE_LEN + (BOAT_SUB_MESSAGE_LEN * 3) + CRC_LEN + Header.getSize())); - } - - /** - * IllegalArgumentException should be thrown when numBoatsInRace is smaller - * than the number of boats actually in the race - */ - @Test(expected = IllegalArgumentException.class) - public void testRaceStatusTooManyBoats(){ - List boatMessages = new ArrayList<>(); - List output = new ArrayList<>(); - - BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); - - BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); - - BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, - 10000, 10000); - - boatMessages.add(boat1); - boatMessages.add(boat2); - boatMessages.add(boat3); - - Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, - 1,RaceType.MATCH_RACE,1, boatMessages); - - m.send(new DataOutputStream(new OutputStream() { - @Override - public void write(int b) throws IOException { - System.out.print(""); - } - })); - } } From 9a995ddcc11ec691483da9ee3484810ece1c5b5c Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Sun, 30 Apr 2017 01:55:49 +1200 Subject: [PATCH 17/23] Boat status changes to finished when a boat finishes the race Tags: #story[829] --- .../java/seng302/server/ServerThread.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 8059827b..d648d243 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -14,6 +14,8 @@ public class ServerThread implements Runnable, Observer { private StreamingServerSocket server; private long startTime; boolean raceStarted = false; + boolean raceFinished = false; + Map boatsFinished = new HashMap<>(); private List boats; private Simulator raceSimulator; @@ -30,6 +32,11 @@ public class ServerThread implements Runnable, Observer { serverLog("Spawning Server", 0); raceSimulator = new Simulator(BOAT_LOCATION_PERIOD); boats = raceSimulator.getBoats(); + + for (Boat b : boats){ + boatsFinished.put(b.getSourceID(), false); + } + runner.start(); } @@ -71,21 +78,38 @@ public class ServerThread implements Runnable, Observer { List boatSubMessages = new ArrayList(); BoatStatus boatStatus; RaceStatus raceStatus; + boolean thereAreBoatsNotFinished = false; - if (raceStarted){ - boatStatus = BoatStatus.RACING; - raceStatus = RaceStatus.STARTED; - } - else{ - boatStatus = BoatStatus.PRESTART; - raceStatus = RaceStatus.PRESTART; - } for (Boat b : boats){ + if (raceStarted){ + boatStatus = BoatStatus.RACING; + thereAreBoatsNotFinished = true; + } + else if(boatsFinished.get(b.getSourceID())){ + boatStatus = BoatStatus.FINISHED; + } + else{ + boatStatus = BoatStatus.PRESTART; + thereAreBoatsNotFinished = true; + } + BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, 0, 0); boatSubMessages.add(m); } + if (thereAreBoatsNotFinished){ + if (raceStarted){ + raceStatus = RaceStatus.STARTED; + } + else{ + raceStatus = RaceStatus.PRESTART; + } + } + else{ + raceStatus = RaceStatus.NOTACTIVE; + } + return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.EAST, 100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages); } @@ -223,14 +247,19 @@ public class ServerThread implements Runnable, Observer { for (Boat b : ((Simulator) o).getBoats()){ try { + Message m = new BoatLocationMessage(b.getSourceID(), 1, b.getLat(), b.getLng(), b.getHeadingCorner().getBearingToNextCorner(), ((long) b.getSpeed())); - server.send(m); } catch (IOException e) { serverLog("Couldn't send a boat status message", 1); } + catch (NullPointerException e){ + //raceFinished = true; + serverLog("Boat " + b.getSourceID() + " finished the race", 1); + boatsFinished.put(b.getSourceID(), true); + } } } } From 1cf55f3e965fde16c45b36c4a3d98e2e9871c97a Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Sun, 30 Apr 2017 16:16:44 +1200 Subject: [PATCH 18/23] Fixed an issue where buffers aren't being sent properly Tags #Story[829] --- src/main/java/seng302/App.java | 2 +- .../java/seng302/server/ServerThread.java | 35 +++++++++++++------ .../seng302/server/StreamingServerSocket.java | 1 + .../server/messages/BoatLocationMessage.java | 2 +- .../seng302/server/messages/Heartbeat.java | 1 + .../server/messages/MarkRoundingMessage.java | 1 + .../seng302/server/messages/MessageType.java | 2 +- .../messages/RaceStartStatusMessage.java | 1 + .../server/messages/RaceStatusMessage.java | 2 ++ .../seng302/server/messages/XMLMessage.java | 1 + 10 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 5b308813..b1e68e98 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -22,6 +22,6 @@ public class App extends Application public static void main(String[] args) { new ServerThread("Racevision Test Server"); //new Thread(new Simulator(1000)).run(); - launch(args); + //launch(args); } } \ No newline at end of file diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index d648d243..2bb36072 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -24,7 +24,7 @@ public class ServerThread implements Runnable, Observer { private final int RACE_START_STATUS_PERIOD = 1000/2; private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; - private final int TIME_TILL_RACE_START = 10; + private final int TIME_TILL_RACE_START = 20; private static final int LOG_LEVEL = 1; public ServerThread(String threadName){ @@ -103,11 +103,22 @@ public class ServerThread implements Runnable, Observer { raceStatus = RaceStatus.STARTED; } else{ - raceStatus = RaceStatus.PRESTART; + long currentTime = System.currentTimeMillis()/1000; + long timeDifference = startTime - currentTime; + + if (timeDifference > 60*3){ + raceStatus = RaceStatus.PRESTART; + } + else if (timeDifference > 60){ + raceStatus = RaceStatus.WARNING; + } + else{ + raceStatus = RaceStatus.PREPARATORY; + } } } else{ - raceStatus = RaceStatus.NOTACTIVE; + raceStatus = RaceStatus.TERMINATED; } return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.EAST, @@ -171,21 +182,23 @@ public class ServerThread implements Runnable, Observer { }, 0, RACE_START_STATUS_PERIOD); } + /** + * Start sending race start status messages until race starts + */ private void startSendingRaceStatusMessages(){ - serverLog("Sending Race Status Messages", 0); Timer t = new Timer(); t.schedule(new TimerTask() { @Override public void run() { - Message statusMessage = getRaceStatusMessage(); - + Message raceStatusMessage = getRaceStatusMessage(); try { - server.send(statusMessage); + server.send(raceStatusMessage); + } catch (IOException e) { System.out.print(""); } } - }, 100, RACE_STATUS_PERIOD); + }, 0, RACE_STATUS_PERIOD); } /** @@ -221,16 +234,18 @@ public class ServerThread implements Runnable, Observer { serverLog("Failed to bind socket: " + e.getMessage(), 0); } - startTime = (System.currentTimeMillis()/1000) + TIME_TILL_RACE_START; - // Wait for client to connect server.start(); + startTime = (System.currentTimeMillis()/1000) + TIME_TILL_RACE_START; + startSendingHeartbeats(); sendXml(); startSendingRaceStartStatusMessages(); startSendingRaceStatusMessages(); + //serverLog("Sending Race Status Messages", 0); + } /** diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java index 0818bd9f..d86e441d 100644 --- a/src/main/java/seng302/server/StreamingServerSocket.java +++ b/src/main/java/seng302/server/StreamingServerSocket.java @@ -54,6 +54,7 @@ class StreamingServerSocket { //System.out.println(client); message.send(client); + seqNum++; } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index 45dd3ad5..9638c0bd 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -135,7 +135,7 @@ public class BoatLocationMessage extends Message { heading = (heading + 180.0) % 360.0; - long headingToSend = (long)((heading/360.0)*65535.0); + long headingToSend = (long)((heading/360.0)*49152.0); putByte((byte) messageVersionNumber); putInt(time, 6); diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index 3660af23..8e619107 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -35,6 +35,7 @@ public class Heartbeat extends Message { putUnsignedInt(seqNo, 4); writeCRC(); + rewind(); outputStream.write(getBuffer()); } diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/server/messages/MarkRoundingMessage.java index 82ef685d..750efb22 100644 --- a/src/main/java/seng302/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/server/messages/MarkRoundingMessage.java @@ -55,6 +55,7 @@ public class MarkRoundingMessage extends Message{ putByte((byte) markId); writeCRC(); + rewind(); outputStream.write(getBuffer()); } diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/server/messages/MessageType.java index 01bad14b..be856dac 100644 --- a/src/main/java/seng302/server/messages/MessageType.java +++ b/src/main/java/seng302/server/messages/MessageType.java @@ -9,7 +9,7 @@ public enum MessageType { RACE_STATUS(12), DISPLAY_TEXT_MESSAGE(20), XML_MESSAGE(26), - RACE_START_STATUS(20), + RACE_START_STATUS(27), YACHT_EVENT_CODE(29), YACHT_ACTION_CODE(31), CHATTER_TEXT(36), diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java index a89471b8..368a18fd 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -52,6 +52,7 @@ public class RaceStartStatusMessage extends Message { putUnsignedByte((byte) notificationType.getType()); writeCRC(); + rewind(); outputStream.write(getBuffer()); } diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 37aa3fe7..84290c58 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -85,6 +85,8 @@ public class RaceStatusMessage extends Message{ } writeCRC(); + rewind(); + outputStream.write(getBuffer()); } diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index a0044ea7..2cf3a5b5 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -62,6 +62,7 @@ public class XMLMessage extends Message{ putBytes(content.getBytes()); writeCRC(); + rewind(); outputStream.write(getBuffer()); } From 85f461c88c1e6a00282bbf1b39eec2020fdcb402 Mon Sep 17 00:00:00 2001 From: William Muir Date: Sun, 30 Apr 2017 17:08:06 +1200 Subject: [PATCH 19/23] Fixed bug so XML messages are located and sent properly on the server Imported appache commons to read inputstream to a bytearray #story[829] --- pom.xml | 5 +++++ src/main/java/seng302/App.java | 2 +- src/main/java/seng302/server/ServerThread.java | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 524b4562..8d10c6fc 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,11 @@ 4.12 test + + org.apache.commons + commons-io + 1.3.2 + com.googlecode.json-simple json-simple diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index b1e68e98..6baafb6c 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -16,7 +16,7 @@ public class App extends Application primaryStage.setTitle("RaceVision"); primaryStage.setScene(new Scene(root)); - primaryStage.show(); +// primaryStage.show(); } public static void main(String[] args) { diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 2bb36072..7de30449 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -3,8 +3,11 @@ package seng302.server; import seng302.server.messages.*; import seng302.server.simulator.Boat; import seng302.server.simulator.Simulator; +import sun.misc.IOUtils; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; @@ -56,11 +59,11 @@ public class ServerThread implements Runnable, Observer { String fileContents = null; try { - fileContents = new String(Files.readAllBytes(Paths.get(this.getClass().getResource(fileName).getPath().substring(1)))); + InputStream thisStream = this.getClass().getResourceAsStream(fileName); + fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream)); } catch (IOException e) { e.printStackTrace(); - } - catch (NullPointerException e){ + } catch (NullPointerException e){ return null; } @@ -206,20 +209,23 @@ public class ServerThread implements Runnable, Observer { */ private void sendXml(){ try{ - Message raceData = getXmlMessage("server_config/race.xml", XMLMessageSubType.RACE); - Message boatData = getXmlMessage("server_config/boats.xml", XMLMessageSubType.BOAT); - Message regatta = getXmlMessage("server_config/regatta.xml", XMLMessageSubType.REGATTA); + Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); + Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); + Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); if (raceData != null){ server.send(raceData); + serverLog("Sending race data", 0); } if (boatData != null){ server.send(boatData); + serverLog("Sending boat data", 0); } if (regatta != null){ server.send(regatta); + serverLog("Sending regatta data", 0); } } catch (IOException e) { serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); From 6491efec4c26af55f6244b85a18d85cccfed7d74 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Sun, 30 Apr 2017 17:46:56 +1200 Subject: [PATCH 20/23] Fixed race status sent in race status messages #story[829] --- src/main/java/seng302/server/ServerThread.java | 13 ++++++------- .../seng302/server/messages/RaceStatusMessage.java | 7 +++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 2bb36072..c11d938c 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -24,7 +24,7 @@ public class ServerThread implements Runnable, Observer { private final int RACE_START_STATUS_PERIOD = 1000/2; private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; - private final int TIME_TILL_RACE_START = 20; + private final int TIME_TILL_RACE_START = 20*1000; private static final int LOG_LEVEL = 1; public ServerThread(String threadName){ @@ -80,10 +80,9 @@ public class ServerThread implements Runnable, Observer { RaceStatus raceStatus; boolean thereAreBoatsNotFinished = false; - for (Boat b : boats){ - if (raceStarted){ - boatStatus = BoatStatus.RACING; + if (!raceStarted){ + boatStatus = BoatStatus.PRESTART; thereAreBoatsNotFinished = true; } else if(boatsFinished.get(b.getSourceID())){ @@ -103,7 +102,7 @@ public class ServerThread implements Runnable, Observer { raceStatus = RaceStatus.STARTED; } else{ - long currentTime = System.currentTimeMillis()/1000; + long currentTime = System.currentTimeMillis(); long timeDifference = startTime - currentTime; if (timeDifference > 60*3){ @@ -166,7 +165,7 @@ public class ServerThread implements Runnable, Observer { Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1, RaceStartNotificationType.SET_RACE_START_TIME); try { - if (startTime < System.currentTimeMillis()/1000 && !raceStarted){ + if (startTime < System.currentTimeMillis() && !raceStarted){ startRaceSim(); raceStarted = true; serverLog("Race Started", 0); @@ -237,7 +236,7 @@ public class ServerThread implements Runnable, Observer { // Wait for client to connect server.start(); - startTime = (System.currentTimeMillis()/1000) + TIME_TILL_RACE_START; + startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; startSendingHeartbeats(); sendXml(); diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 84290c58..52983b22 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -39,7 +39,7 @@ public class RaceStatusMessage extends Message{ */ public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection, long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List boats){ - currentTime = System.currentTimeMillis() / 1000L; + currentTime = System.currentTimeMillis(); this.raceId = raceId; this.raceStatus = raceStatus; this.expectedStartTime = expectedStartTime; @@ -71,10 +71,10 @@ public class RaceStatusMessage extends Message{ writeHeaderToBuffer(); putByte((byte) MESSAGE_VERSION); - putInt((int) currentTime, 6); + putInt(currentTime, 6); putInt((int) raceId, 4); putByte((byte) raceStatus.getCode()); - putInt((int) expectedStartTime, 6); + putInt(expectedStartTime, 6); putInt((int) raceWindDirection.getCode(), 2); putInt((int) windSpeed, 2); putByte((byte) numBoatsInRace); @@ -87,7 +87,6 @@ public class RaceStatusMessage extends Message{ writeCRC(); rewind(); - outputStream.write(getBuffer()); } } From e7f9954970d3dfd19e9756de687b460013081997 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Sun, 30 Apr 2017 23:29:15 +1200 Subject: [PATCH 21/23] Removed unneeded files, also fixed heading calculation Tags #story[829] --- src/main/java/seng302/App.java | 5 - .../java/seng302/controllers/Controller.java | 176 ------------- src/main/java/seng302/models/Boat.java | 174 ------------- src/main/java/seng302/models/Colors.java | 20 -- src/main/java/seng302/models/Course.java | 54 ---- src/main/java/seng302/models/Leg.java | 97 ------- src/main/java/seng302/models/Race.java | 236 ------------------ .../java/seng302/models/mark/GateMark.java | 50 ---- src/main/java/seng302/models/mark/Mark.java | 54 ---- .../java/seng302/models/mark/MarkType.java | 36 --- .../java/seng302/models/mark/SingleMark.java | 45 ---- .../seng302/models/parsers/ConfigParser.java | 78 ------ .../seng302/models/parsers/CourseParser.java | 140 ----------- .../seng302/models/parsers/FileParser.java | 37 --- .../seng302/models/parsers/TeamsParser.java | 63 ----- .../java/seng302/server/ServerThread.java | 14 +- .../seng302/server/StreamingServerSocket.java | 5 - .../server/messages/BoatLocationMessage.java | 4 +- .../java/seng302/server/messages/Message.java | 12 +- .../server/messages/RaceStatusMessage.java | 4 - .../seng302/server/simulator/Simulator.java | 3 +- 21 files changed, 15 insertions(+), 1292 deletions(-) delete mode 100644 src/main/java/seng302/controllers/Controller.java delete mode 100644 src/main/java/seng302/models/Boat.java delete mode 100644 src/main/java/seng302/models/Colors.java delete mode 100644 src/main/java/seng302/models/Course.java delete mode 100644 src/main/java/seng302/models/Leg.java delete mode 100644 src/main/java/seng302/models/Race.java delete mode 100644 src/main/java/seng302/models/mark/GateMark.java delete mode 100644 src/main/java/seng302/models/mark/Mark.java delete mode 100644 src/main/java/seng302/models/mark/MarkType.java delete mode 100644 src/main/java/seng302/models/mark/SingleMark.java delete mode 100644 src/main/java/seng302/models/parsers/ConfigParser.java delete mode 100644 src/main/java/seng302/models/parsers/CourseParser.java delete mode 100644 src/main/java/seng302/models/parsers/FileParser.java delete mode 100644 src/main/java/seng302/models/parsers/TeamsParser.java diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 6baafb6c..4c15c09f 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -6,7 +6,6 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import seng302.server.ServerThread; -import seng302.server.simulator.Simulator; public class App extends Application { @@ -15,13 +14,9 @@ public class App extends Application Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); primaryStage.setTitle("RaceVision"); primaryStage.setScene(new Scene(root)); - -// primaryStage.show(); } public static void main(String[] args) { new ServerThread("Racevision Test Server"); - //new Thread(new Simulator(1000)).run(); - //launch(args); } } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java deleted file mode 100644 index 080e06ab..00000000 --- a/src/main/java/seng302/controllers/Controller.java +++ /dev/null @@ -1,176 +0,0 @@ -package seng302.controllers; - -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.Group; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.control.Button; -import javafx.scene.paint.Color; -import seng302.models.Boat; -import seng302.models.Course; -import seng302.models.Race; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkType; -import seng302.models.parsers.ConfigParser; -import seng302.models.parsers.CourseParser; -import seng302.models.parsers.TeamsParser; - -import java.net.URL; -import java.util.ArrayList; -import java.util.ResourceBundle; - -/** - * Controller for main window for RaceVision - * Created by Kusal on 3/22/2017. - */ -public class Controller implements Initializable { - - private static final int MARKER_WIDTH = 10; - private static final int MARKER_HEIGHT = 10; - private static final double ORIGINAL_LAT = 32.321504; - private static final double ORIGINAL_LON = -64.857063; - - - @FXML - private Canvas courseCanvas; - - @FXML - private Group boatGroup; - - @FXML - private Button playPauseButton; - - - private Course thisCourse; - private ArrayList startingBoats; - private int raceDuration; - private Race race; - private boolean raceRunning = false; - - private CourseParser cp; - private TeamsParser tp; - private ConfigParser cop; - - @Override - public void initialize(URL location, ResourceBundle resources) { - cp = new CourseParser("/config/course.xml"); - tp = new TeamsParser("/config/teams.xml"); - cop = new ConfigParser("/config/config.xml"); - - thisCourse = new Course(cp.getCourse()); - startingBoats = tp.getBoats(); - race = new Race(thisCourse.getMarks(), startingBoats, cop.getTimeScale(), this); - init(); - } - - /** - * Initialises a race on the screen after it has been loaded - */ - private void init() { - initMap(); - initBoats(); - playPauseButton.setDisable(false); - } - - /** - * Initialise the map by drawing it onto courseCanvas. - */ - private void initMap() { - //Create the boundary of the course displayed on the map - drawCourse(); - } - - - /** - * Draw the markers and gates onto the courseCanvas. - */ - private void drawCourse() { - GraphicsContext gc = courseCanvas.getGraphicsContext2D(); - gc.save(); - - for(Mark rp : thisCourse.getMarks()) { - if (rp.getMarkType().equals(MarkType.SINGLE_MARK)) { - gc.setFill(Color.GRAY); - gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); - } else if (rp.getMarkType().equals(MarkType.GATE_MARK)) { - gc.setFill(Color.GRAY); - gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); -// gc.fillOval(((OpenGate) rp).getDrawX2(), ((OpenGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT); - - gc.setLineWidth(2); - gc.setFill(Color.GREEN); - gc.setStroke(Color.GREEN); - } - gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); -// gc.fillOval(((ClosedGate) rp).getDrawX2(), ((ClosedGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT); -// gc.strokeLine(convertLongToX(rp.getLongitude()) + 5, convertLatToY(rp.getLatitude()) + 5, ((ClosedGate) rp).getDrawX2() + 5, ((ClosedGate) rp).getDrawY2() + 5); - } - gc.restore(); - } - - /** - * Places boats at starting line - */ - private void initBoats() { - -// int startingX = (convertLongToX(thisCourse.getMarks().get(0).getLongitude())).intValue(); -// int startingY = (convertLatToY(thisCourse.getMarks().get(0).getLongitude())).intValue(); - - int startingX = 50; - int startingY = 100; - - for(Boat boat : startingBoats) { - boat.moveBoatTo(startingX, startingY); - boatGroup.getChildren().add(boat.getBoatObject()); - boat.setCurrentLeg(race.getRaceLegs().get(0)); - boat.setHeading(race.getRaceLegs().get(0).getHeading()); - boat.setLegDistance(0d); - } - } - - - /** - * Starts and stops the race depending on whether or not it is already running - */ - public void playPause() { - if (!raceRunning) { - play(); - } else { - pause(); - } - } - - /** - * Plays the race and updates the play / pause button - */ - private void play() { - race.run(); - raceRunning = true; - playPauseButton.setText("Pause"); - - } - - /** - * Pauses the race and updates the play / pause button - */ - private void pause() { - race.pause(); - raceRunning = false; - playPauseButton.setText("Play"); - } - - - - private Double convertLongToX(Double lon) { - return (lon - ORIGINAL_LON) * thisCourse.getDistanceScaleFactor(); - } - - private Double convertLatToY(Double lat) { - return (ORIGINAL_LAT - lat) * thisCourse.getDistanceScaleFactor(); - } - - public Button getPlayPauseButton() { - return playPauseButton; - } -} diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java deleted file mode 100644 index e081efb9..00000000 --- a/src/main/java/seng302/models/Boat.java +++ /dev/null @@ -1,174 +0,0 @@ -package seng302.models; - -import javafx.scene.paint.Color; -import javafx.scene.shape.Polygon; -import javafx.scene.text.Text; - -/** -* Represents a boat in the race. -*/ -public class Boat { - - private static final double BOAT_HEIGHT = 15d; - private static final double BOAT_WIDTH = 10d; - private static final double VELOCITY_WAKE_RATIO = 1/2d; //Ratio for deciding how long the wake will be wrt velocity - - private String teamName; // The name of the team, this is also the name of the boat - private Double velocity; // In meters/second - private Double lat; // Boats position - private Double lon; // - - private Double legDistance; - private Color color; - private Leg currentLeg; - private Double heading; - private String shortName; - - private Polygon boatObject = new Polygon(); - - public Boat(String teamName) { - this.teamName = teamName; - this.velocity = 10d; // Default velocity - this.lat = 0.0; - this.lon = 0.0; - this.legDistance = 0.0; - this.shortName = ""; - } - - /** - * Represents a boat in the race. - * - * @param teamName The name of the team sailing the boat - * @param boatVelocity The speed of the boat in meters/second - * @param shortName A shorter version of the teams name - */ - public Boat(String teamName, double boatVelocity, String shortName) { - this.teamName = teamName; - this.velocity = boatVelocity; - this.legDistance = 0.0; - this.color = Colors.getColor(); - this.shortName = shortName; - this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0, - BOAT_WIDTH, BOAT_HEIGHT, - 0.0, BOAT_HEIGHT); - } - - /** - * Returns the name of the team sailing the boat - * - * @return The name of the team - */ - public String getTeamName() { - return this.teamName; - } - - /** - * Sets the name of the team sailing the boat - * - * @param teamName The name of the team - */ - public void setTeamName(String teamName) { - this.teamName = teamName; - } - - /** - * Gets velocity of the boat - * - * @return a float number of the boat velocity - */ - public double getVelocity() { - return this.velocity; - } - - /** - * Sets velocity of the boat - * - * @param velocity The velocity of boat - */ - public void setVelocity(double velocity) { - this.velocity = velocity; - } - - /** - * Sets the boats location - * - * @param lat, the boats latitude - * @param lon, the boats longitude - */ - public void setLocation(double lat, double lon) { - this.lat = lat; - this.lon = lon; - } - - public Double getLegDistance() { - return legDistance; - } - - public void setLegDistance(double legDistance){ - this.legDistance = legDistance; - } - - public double getLatitude(){ - return this.lat; - } - - public double getLongitude(){ - return this.lon; - } - - public Color getColor() { - return color; - } - - public double getSpeedInKnots(){ - return Math.round((this.velocity * 1.94384) * 100d) / 100d; - } - - public Leg getCurrentLeg() { - return currentLeg; - } - - public void setCurrentLeg(Leg currentLeg) { - this.currentLeg = currentLeg; - } - - public void setHeading(double heading){ - this.heading = heading; - } - - public double getHeading(){ - return this.heading; - } - - public String getShortName(){ - return this.shortName; - } - - - /** - * Moves the boat and its children annotations from its current coordinates by specified amounts. - * @param x The amount to move the X coordinate by - * @param y The amount to move the Y coordinate by - */ - void moveBoatBy(Double x, Double y) { - boatObject.setLayoutX(boatObject.getLayoutX() + x); - boatObject.setLayoutY(boatObject.getLayoutY() + y); - boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); - - } - - /** - * Moves the boat and its children annotations to coordinates specified - * @param x The X coordinate to move the boat to - * @param y The Y coordinate to move the boat to - */ - public void moveBoatTo(int x, int y) { - boatObject.setLayoutX(x); - boatObject.setLayoutY(y); - boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); - - } - - public Polygon getBoatObject() { - return boatObject; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/Colors.java b/src/main/java/seng302/models/Colors.java deleted file mode 100644 index 419753dc..00000000 --- a/src/main/java/seng302/models/Colors.java +++ /dev/null @@ -1,20 +0,0 @@ -package seng302.models; - -import javafx.scene.paint.Color; - -/** - * Created by ryan_ on 16/03/2017. - */ -public enum Colors { - RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE; - - static Integer index = 0; - - public static Color getColor() { - index++; - if (index > 6) { - index = 1; - } - return Color.valueOf(values()[index-1].toString()); - } -} diff --git a/src/main/java/seng302/models/Course.java b/src/main/java/seng302/models/Course.java deleted file mode 100644 index d6647a02..00000000 --- a/src/main/java/seng302/models/Course.java +++ /dev/null @@ -1,54 +0,0 @@ -package seng302.models; - -/** - * Created by wmu16 on 4/8/17. - */ -import javafx.scene.canvas.Canvas; -import javafx.util.Pair; -import seng302.models.mark.Mark; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * Class for describing the course information for the canvas - * Created by wmu16 on 22/03/17. - */ -public class Course { - - private ArrayList Legs; - private ArrayList marks; - public static final Integer SCALE = 160000; - - - public Course(ArrayList marks) { - this.marks = marks; - this.Legs = makeLegs(); - } - - /** - * Makes the race legs out of all the marker marks for the course - * @return ArrayList of Legs - */ - private ArrayList makeLegs() { - ArrayList Legs = new ArrayList<>(); - for (int i = 0; i < marks.size()-1; i++) { - Legs.add(new Leg(marks.get(i), marks.get(i+1))); - } - return Legs; - } - - - public ArrayList getMarks() { - return marks; - } - - public double getDistanceScaleFactor() { - return SCALE; - } - - public ArrayList getLegs() { - return Legs; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/Leg.java b/src/main/java/seng302/models/Leg.java deleted file mode 100644 index 7e03e195..00000000 --- a/src/main/java/seng302/models/Leg.java +++ /dev/null @@ -1,97 +0,0 @@ -package seng302.models; - - -import seng302.models.mark.Mark; - -/** - * Class for defining the leg of a race between two markers - * Created by cir27 on 3/03/17. - */ -public class Leg { - - private final double ORIGIN_LAT = 32.320504; - private final double ORIGIN_LON = -64.857063; - private final double SCALE = 16000; - - private Double distance; - private Double heading; - private Mark end; - private Mark start; - - Leg(Mark start, Mark end) { - this.distance = calculateMarkerDistance(start, end); - this.heading = angleFromCoordinate(start, end); - this.start = start; - this.end = end; - } - - /** - * Calculates the euclidian distance between two markers on the canvas using xy coordinates - * - * @param geoPointOne first geographical point - * @param geoPointTwo second geographical point - * @return the distance between two points in meters - */ - private Double calculateMarkerDistance(Mark geoPointOne, Mark geoPointTwo) { - - double earth_radius = 6378.137; - double dLat = geoPointTwo.getLatitude() * Math.PI / 180 - geoPointOne.getLatitude() * Math.PI / 180; - double dLon = geoPointTwo.getLongitude() * Math.PI / 180 - geoPointOne.getLongitude() * Math.PI / 180; - - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(geoPointOne.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double d = earth_radius * c; - - return d * 1000; - } - - /** - * Calculates the angle between to angular co-ordinates on a sphere. - * - * @param geoPointOne first geographical location - * @param geoPointTwo second geographical location - * @return the angle from point one to point two - */ - private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) { - - if (geoPointTwo == null){ - return 0.0; - } - - double x1 = (geoPointOne.getLongitude() - ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - geoPointOne.getLatitude()) * SCALE; - double x2 = (geoPointTwo.getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - geoPointTwo.getLatitude()) * SCALE; - - double headingRadians = Math.atan2(y2-y1, x2-x1); - - if (headingRadians < 0){ - headingRadians += 2 * Math.PI; - } - - // Convert back to degrees, and flip 180 degrees -// return ((headingRadians) * 180) / Math.PI; - return (Math.toDegrees(headingRadians) + 90) % 360; - - } - - Double getDistance() - { - return this.distance; - } - - Mark getEnd() - { - return this.end; - } - - public Mark getStart() { - return start; - } - - public Double getHeading() - { - return this.heading; - } -} diff --git a/src/main/java/seng302/models/Race.java b/src/main/java/seng302/models/Race.java deleted file mode 100644 index a396b508..00000000 --- a/src/main/java/seng302/models/Race.java +++ /dev/null @@ -1,236 +0,0 @@ -package seng302.models; - -import javafx.animation.AnimationTimer; -import seng302.controllers.Controller; -import seng302.models.mark.Mark; - -import java.util.*; - -/** - * Race class containing the boats and legs in the race - * Created by mra106 on 8/3/2017. - */ -public class Race extends Thread { - - private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps - - - private ArrayList boats; // The boats in the race - private ArrayList finishingOrder; // The order in which the boats finish the race - private List markers; // Marks in the race - private List raceLegs; - private boolean raceFinished = false; // Race is finished - private int raceTime = -2; // Current time in the race - private Double timeScale = null; - private boolean raceStarted = false; - private Controller controller; - - /** - * Race class containing the boats and legs in the race - */ - public Race(List markers, ArrayList boats, Double timescale, Controller controller) { - this.boats = boats; - this.markers = markers; - this.raceLegs = makeRaceLegs(); - this.controller = controller; - - this.timeScale = timescale; - this.finishingOrder = new ArrayList<>(); - } - - - /** - * Makes the race legs out of all the marker points for the course - * @return ArrayList of raceLegs - */ - private ArrayList makeRaceLegs() { - ArrayList raceLegs = new ArrayList<>(); - for (int i=0; i < markers.size()-1; i++) { - raceLegs.add(new Leg(markers.get(i), markers.get(i+1))); - } - return raceLegs; - } - - - /** - * A timer that is called every frame to call the action of moving the boats - */ - private AnimationTimer at = new AnimationTimer() { - @Override - public void handle(long now) { - - //Updating Boat positions - if(finishingOrder.size() != boats.size()) { - // update the time - boats.stream().filter(boat -> !finishingOrder.contains(boat)).forEach(boat -> { - updateBoatPosition(boat); - }); - } else { - at.stop(); - } - } - - }; - - - /** - * Begins the race simulation - */ - @Override - public void run() { - if (!raceStarted) { -// controller.getPlayPauseButton().setDisable(true); - at.start(); - } - } - - - /** - * Moves the coordinates of the boats. - * @param boat The boat to update the position of - */ - private void updateBoatPosition(Boat boat) { - Double thisHeading = boat.getHeading(); - // TODO: 4/8/17 wmu16 - Add a distance scale factor from lat long distance in Metres to xy equivalent - Double hypMove = boat.getVelocity() * UPDATE_TIME * timeScale; //* distanceScaleFactor - boat.setLegDistance(boat.getLegDistance() + hypMove); - moveBoat(boat, thisHeading, hypMove); - } - - /** - * Moves a boat along coordinates by breaking down the distance moved along the hypotenuse into x and y components - * @param boat The Boat to move - * @param heading The heading the boat is moving at - * @param hypMove The distance moved along the hypotenuse (absolute distance) - */ - private void moveBoat(Boat boat, Double heading, Double hypMove) { - //If the boat has not yet passed the marker at the end of the leg increase its (x,y) as per normal - // TODO: 4/8/17 wmu16 - Initialising boat positions and legs and headings etc. - if(boat.getLegDistance() <= boat.getCurrentLeg().getDistance()) { - Double xMove = hypMove * Math.sin(Math.toRadians(heading)); - Double yMove = - hypMove * Math.cos(Math.toRadians(heading)); - boat.moveBoatBy(xMove, yMove); - - //If the boat has finished... - } else if (getNextRaceLeg(boat.getCurrentLeg()).equals(boat.getCurrentLeg())) { - finishingOrder.add(boat); - //Otherwise apply the overflow distance of the leg to the next leg - } else { - Double overflowDistance = boat.getLegDistance() - boat.getCurrentLeg().getDistance(); - boat.setCurrentLeg(getNextRaceLeg(boat.getCurrentLeg())); - boat.setHeading(boat.getCurrentLeg().getHeading()); - boat.setLegDistance(overflowDistance); - moveBoat(boat, boat.getHeading(), overflowDistance); - } - - } - - /** - * Returns the next leg in the race - * @param currentRaceLeg The leg that you are currently on - * @return The next race leg or the same race leg if it has reached the end - */ - private Leg getNextRaceLeg(Leg currentRaceLeg) { - Leg nextRaceLeg = currentRaceLeg; - for(int i = 0; i< raceLegs.size() - 1; i++) { - if (raceLegs.get(i).equals(currentRaceLeg)) { - nextRaceLeg = raceLegs.get(i + 1); - } - } - return nextRaceLeg; - } - - - /** - * Returns a list of boats in the order that they - * finished the race (position 0 is first place) - * - * @return a list of boats - */ - public Boat[] getFinishedBoats() { - return this.finishingOrder.toArray(new Boat[this.finishingOrder.size()]); - } - - - /** - * Returns a list of boats in the race - * - * @return a list of the boats competing in the race - */ - public Boat[] getBoats() { - return boats.toArray(new Boat[boats.size()]); - } - - /** - * Starts a race and generates all events for the race. - */ - public void startRace() { - // record start time. - - - - } - - public void pause() { - at.stop(); - } - - /** - * Get a list of marks in the markers - * @return - */ - public List getMarkers() { - return markers; - } - - - /** - * Set a boat as finished - * @param boat The boat that has finished the race - */ - public void setBoatFinished(Boat boat){ - this.finishingOrder.add(boat); - } - - /** - * Set the race as finished - */ - public void setRaceFinished(){ - this.raceFinished = true; - } - - /** - * Return whether or not the race is finished - * @return true if the race is finished - */ - public boolean isRaceFinished(){ - return this.raceFinished; - } - - /** - * Set the race time - * @param raceTime the race time in seconds - */ - public void setRaceTime(int raceTime){ - this.raceTime = raceTime; - } - - /** - * Return the race time - * @return the race time in seconds - */ - public int getRaceTime(){ - return this.raceTime; - } - - /** - * Increment the race time by one second - */ - public void incrementRaceTime() { - this.raceTime += this.timeScale; - } - - public List getRaceLegs() { - return raceLegs; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/mark/GateMark.java b/src/main/java/seng302/models/mark/GateMark.java deleted file mode 100644 index 2b152e65..00000000 --- a/src/main/java/seng302/models/mark/GateMark.java +++ /dev/null @@ -1,50 +0,0 @@ -package seng302.models.mark; - -/** - * To represent a gate mark which contains two single marks. - * Created by ptg19 on 16/03/17. - * Modified by Haoming Yin (hyi25) on 17/3/2017. - */ -public class GateMark extends Mark { - - private SingleMark singleMark1; - private SingleMark singleMark2; - - /** - * Create an instance of Gate Mark which contains two single mark - * @param name the name of the gate mark - * @param singleMark1 one single mark inside of the gate mark - * @param singleMark2 the second mark inside of the gate mark - */ - public GateMark(String name, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) { - super(name, MarkType.GATE_MARK, latitude, longitude); - this.singleMark1 = singleMark1; - this.singleMark2 = singleMark2; - } - - public SingleMark getSingleMark1() { - return singleMark1; - } - - public void setSingleMark1(SingleMark singleMark1) { - this.singleMark1 = singleMark1; - } - - public SingleMark getSingleMark2() { - return singleMark2; - } - - public void setSingleMark2(SingleMark singleMark2) { - this.singleMark2 = singleMark2; - } - - public double getLatitude(){ - //return (this.getSingleMark1().getLatitude() + this.getSingleMark2().getLatitude()) / 2; - return (this.getSingleMark1().getLatitude()); - } - - public double getLongitude(){ - //return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2; - return (this.getSingleMark1().getLongitude()); - } -} diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java deleted file mode 100644 index 3e635856..00000000 --- a/src/main/java/seng302/models/mark/Mark.java +++ /dev/null @@ -1,54 +0,0 @@ -package seng302.models.mark; - -/** - * An abstract class to represent general marks - * Created by Haoming Yin (hyi25) on 17/3/17. - */ -public abstract class Mark { - - private String name; - private MarkType markType; - private double latitude; - private double longitude; - - /** - * Create a mark instance by passing its name and type - * @param name the name of the mark - * @param markType the type of mark. either GATE_MARK or SINGLE_MARK. - */ - public Mark (String name, MarkType markType) { - this.name = name; - this.markType = markType; - } - - public Mark(String name, MarkType markType, double latitude, double longitude) { - this.name = name; - this.markType = markType; - this.latitude = latitude; - this.longitude = longitude; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public MarkType getMarkType() { - return markType; - } - - public void setMarkType(MarkType markType) { - this.markType = markType; - } - - public double getLatitude() { - return latitude; - } - - public double getLongitude() { - return longitude; - } -} diff --git a/src/main/java/seng302/models/mark/MarkType.java b/src/main/java/seng302/models/mark/MarkType.java deleted file mode 100644 index 43366b1d..00000000 --- a/src/main/java/seng302/models/mark/MarkType.java +++ /dev/null @@ -1,36 +0,0 @@ -package seng302.models.mark; - -/** - * To represent two types of mark - * Created by Haoming Yin (hyi25) on 17/3/17. - */ - - -public enum MarkType { - - UNKNOWN(0), - ROUNDING_MARK(1), - GATE_MARK(2), - // above mark types are from AC35 spec. - - // more specific types for gate mark - WINDWARD(201), - LEEWARD(202), - START(203), - FINISH(204), - - // single_mark is from old team-13 code base, for compatibility, it has not - // been removed yet - SINGLE_MARK(5); - - private int type; - - MarkType(int markType) { - this.type = markType; - } - - public int getType() { - return this.type; - } - -} diff --git a/src/main/java/seng302/models/mark/SingleMark.java b/src/main/java/seng302/models/mark/SingleMark.java deleted file mode 100644 index 81f6f0b4..00000000 --- a/src/main/java/seng302/models/mark/SingleMark.java +++ /dev/null @@ -1,45 +0,0 @@ -package seng302.models.mark; - -/** - * Represents the marker as a single mark - * - * Created by Haoming Yin (hyi25) on 17/3/2017 - */ -public class SingleMark extends Mark { - private double lat; - private double lon; - private String name; - - - /** - * Represents a marker - * - * @param name, the name of the marker* - * @param lat, the latitude of the marker - * @param lon, the longitude of the marker - */ - public SingleMark(String name, double lat, double lon) { - super(name, MarkType.SINGLE_MARK); - this.lat = lat; - this.lon = lon; - } - - /** - * Represents the marker at the beginning of a leg - * - * @param name, the name of the marker - */ - public SingleMark(String name) { - super(name, MarkType.SINGLE_MARK); - this.lat = 0; - this.lon = 0; - } - - public double getLatitude() { - return this.lat; - } - - public double getLongitude() { - return this.lon; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/parsers/ConfigParser.java b/src/main/java/seng302/models/parsers/ConfigParser.java deleted file mode 100644 index 1d870c67..00000000 --- a/src/main/java/seng302/models/parsers/ConfigParser.java +++ /dev/null @@ -1,78 +0,0 @@ -package seng302.models.parsers; - - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.DoubleSummaryStatistics; - -public class ConfigParser extends FileParser { - - private Document doc; - - public ConfigParser(String path) { - super(path); - this.doc = this.parseFile(); - } - - /** - * Gets wind direction from config file. - * - * @return a double type degree, or 0 if no value or invalid value is found - */ - public double getWindDirection() { - return getDoubleByTagName("wind-direction", 0.0); - } - - /** - * Gets a non negative time scale for the race - * - * @return a double type scale, or 0 if no scale or invalid scale is found - */ - public double getTimeScale() { - return getDoubleByTagName("time-scale", 1.0); - } - - /** - * Gets a double type number by given tag name found in xml file - * - * @param tagName a string of tag name - * @param defaultVal value returned if no value or invalid value is found - * @return value found - */ - public double getDoubleByTagName(String tagName, double defaultVal) { - double val = defaultVal; - try { - Node node = this.doc.getElementsByTagName(tagName).item(0); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - val = Double.valueOf(element.getTextContent()); - } - } catch (Exception e) { - } finally { - return val; - } - } - - /** - * Gets a string by given tag name found in xml file - * - * @param tagName a string of tag name - * @param defaultVal a string returned if no value or invalid value is found - * @return string found - */ - public String getStringByTagName(String tagName, String defaultVal) { - String string = defaultVal; - try { - Node node = this.doc.getElementsByTagName(tagName).item(0); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - string = element.getTextContent(); - } - } catch (Exception e) { - } finally { - return string; - } - } -} diff --git a/src/main/java/seng302/models/parsers/CourseParser.java b/src/main/java/seng302/models/parsers/CourseParser.java deleted file mode 100644 index 0ad0c471..00000000 --- a/src/main/java/seng302/models/parsers/CourseParser.java +++ /dev/null @@ -1,140 +0,0 @@ -package seng302.models.parsers; - -import org.w3c.dom.*; -import seng302.models.mark.*; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.NoSuchElementException; - -/** - * parse a course xml file - * Created by Haoming Yin (hyi25) on 16/3/2017 - */ -public class CourseParser extends FileParser { - - private Document doc; - private HashMap marks = new HashMap<>(); - - public CourseParser(String path) { - super(path); - this.doc = this.parseFile(); - } - - /** - * create a mark by given node - * - * @param node - * @return a mark, or null if fails to create a mark - */ - private SingleMark generateSingleMark(Node node) { - try { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String name = element.getElementsByTagName("name").item(0).getTextContent(); - double lat = Double.valueOf(element.getElementsByTagName("latitude").item(0).getTextContent()); - double lon = Double.valueOf(element.getElementsByTagName("longitude").item(0).getTextContent()); - SingleMark singleMark = new SingleMark(name, lat, lon); - return singleMark; - } else { - throw new NoSuchElementException("Cannot generate a mark by given node."); - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * generate an arrayList of gates - * - * @return an arrayList of gates, or null if no gate has been found. - */ - private void generateGateMarks() { - ArrayList gateMarks = new ArrayList<>(); - - try { - NodeList nodes = doc.getElementsByTagName("gate"); - - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String name = element.getElementsByTagName("name").item(0).getTextContent(); - SingleMark mark1 = generateSingleMark(element.getElementsByTagName("mark").item(0)); - SingleMark mark2 = generateSingleMark(element.getElementsByTagName("mark").item(1)); - GateMark gateMark = new GateMark(name, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); - marks.put(name, gateMark); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * generate an arrayList of marks - * - * @return an arrayList of marks, or null if no gate has been found. - */ - private void generateSingleMarks() { - ArrayList singleMarks = new ArrayList<>(); - - try { - // find the "marks" tag - Node node = doc.getElementsByTagName("marks").item(0); - // iterate all "marks"'s children - for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) { - // if node's tag name is "mark" - if (n.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) n; - if (element.getNodeName() == "mark") { - Mark mark = generateSingleMark(n); - marks.put(mark.getName(), mark); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * return the order of all the marks along a course - * - * @return an arrayList of the names of ordered course marks - */ - private ArrayList getOrder() { - ArrayList markOrder = new ArrayList<>(); - - try { - Node orderNode = doc.getElementsByTagName("order").item(0); - for (Node node = orderNode.getFirstChild(); node != null; node = node.getNextSibling()) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String name = element.getTextContent(); - markOrder.add(name); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return markOrder; - } - - public ArrayList getCourse() { - generateSingleMarks(); - generateGateMarks(); - ArrayList course = new ArrayList<>(); - try { - for (String mark : getOrder()) { - course.add(marks.get(mark)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return course; - } -} diff --git a/src/main/java/seng302/models/parsers/FileParser.java b/src/main/java/seng302/models/parsers/FileParser.java deleted file mode 100644 index b3d66b05..00000000 --- a/src/main/java/seng302/models/parsers/FileParser.java +++ /dev/null @@ -1,37 +0,0 @@ -package seng302.models.parsers; - -import org.w3c.dom.Document; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -/** - * Created by Haoming Yin (hyi25) on 16/3/2017 - */ -public abstract class FileParser { - - private String filePath; - - public FileParser(String path) { - this.filePath = path; - } - - protected Document parseFile() { - try { - InputStream is = getClass().getResourceAsStream(this.filePath); - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(is); - // optional, in order to recover info from broken line. - doc.getDocumentElement().normalize(); - return doc; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - - } -} diff --git a/src/main/java/seng302/models/parsers/TeamsParser.java b/src/main/java/seng302/models/parsers/TeamsParser.java deleted file mode 100644 index 2986f448..00000000 --- a/src/main/java/seng302/models/parsers/TeamsParser.java +++ /dev/null @@ -1,63 +0,0 @@ -package seng302.models.parsers; - -import org.w3c.dom.*; -import seng302.models.Boat; - -import java.util.ArrayList; -import java.util.NoSuchElementException; - -public class TeamsParser extends FileParser { - - private Document doc; - - public TeamsParser(String path) { - super(path); - this.doc = this.parseFile(); - } - - /** - * Create a boat instance by a given team node - * @param node a boat node containing name, alias and velocity - * @return an instance of Boat - */ - private Boat parseBoat(Node node) { - try { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String name = element.getElementsByTagName("name").item(0).getTextContent(); - String alias = element.getElementsByTagName("alias").item(0).getTextContent(); - double velocity = Double.valueOf(element.getElementsByTagName("velocity").item(0).getTextContent()); - Boat boat = new Boat(name, velocity, alias); - return boat; - } else { - throw new NoSuchElementException("Cannot generate a boat by given node"); - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * Create an arraylist of boats instance. - * @return an arraylist of boats in teams file - */ - public ArrayList getBoats() { - ArrayList boats = new ArrayList<>(); - try { - NodeList nodes = this.doc.getElementsByTagName("team"); - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - - boats.add(parseBoat(node)); - } - return boats; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - -} - diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 14d7659f..e4e5a84a 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -17,14 +17,13 @@ public class ServerThread implements Runnable, Observer { private StreamingServerSocket server; private long startTime; boolean raceStarted = false; - boolean raceFinished = false; Map boatsFinished = new HashMap<>(); private List boats; private Simulator raceSimulator; private final int HEARTBEAT_PERIOD = 5000; - private final int RACE_STATUS_PERIOD = 1000/2; - private final int RACE_START_STATUS_PERIOD = 1000/2; + private final int RACE_STATUS_PERIOD = 1000; + private final int RACE_START_STATUS_PERIOD = 1000; private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; private final int TIME_TILL_RACE_START = 20*1000; @@ -188,6 +187,7 @@ public class ServerThread implements Runnable, Observer { * Start sending race start status messages until race starts */ private void startSendingRaceStatusMessages(){ + serverLog("Sending race status messages", 0); Timer t = new Timer(); t.schedule(new TimerTask() { @Override @@ -248,9 +248,6 @@ public class ServerThread implements Runnable, Observer { sendXml(); startSendingRaceStartStatusMessages(); startSendingRaceStatusMessages(); - - //serverLog("Sending Race Status Messages", 0); - } /** @@ -267,16 +264,15 @@ public class ServerThread implements Runnable, Observer { for (Boat b : ((Simulator) o).getBoats()){ try { - Message m = new BoatLocationMessage(b.getSourceID(), 1, b.getLat(), b.getLng(), b.getHeadingCorner().getBearingToNextCorner(), ((long) b.getSpeed())); server.send(m); } catch (IOException e) { - serverLog("Couldn't send a boat status message", 1); + serverLog("Couldn't send a boat status message", 3); + return; } catch (NullPointerException e){ - //raceFinished = true; serverLog("Boat " + b.getSourceID() + " finished the race", 1); boatsFinished.put(b.getSourceID(), true); } diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java index d86e441d..dc249ea4 100644 --- a/src/main/java/seng302/server/StreamingServerSocket.java +++ b/src/main/java/seng302/server/StreamingServerSocket.java @@ -16,14 +16,12 @@ import java.util.List; class StreamingServerSocket { private ServerSocketChannel socket; private SocketChannel client; - private List clients; private short seqNum; private boolean isServerStarted; StreamingServerSocket(int port) throws IOException{ socket = ServerSocketChannel.open(); socket.socket().bind(new InetSocketAddress("localhost", port)); - clients = new ArrayList<>(); //socket.setSoTimeout(10000); seqNum = 0; isServerStarted = false; @@ -50,11 +48,8 @@ class StreamingServerSocket { return; } - //DataOutputStream outputStream = new DataOutputStream(client.getOutputStream()); - //System.out.println(client); message.send(client); - seqNum++; } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index 9638c0bd..2bffdc72 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -133,9 +133,7 @@ public class BoatLocationMessage extends Message { allocateBuffer(); writeHeaderToBuffer(); - heading = (heading + 180.0) % 360.0; - - long headingToSend = (long)((heading/360.0)*49152.0); + long headingToSend = (long)((heading/360.0) * 65535.0); putByte((byte) messageVersionNumber); putInt(time, 6); diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 2ef71afe..e7dd6f74 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -3,6 +3,7 @@ package seng302.server.messages; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Array; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.SocketChannel; @@ -170,7 +171,6 @@ public abstract class Message { return buffer; } - /** * Rewind the buffer to the beginning */ @@ -185,11 +185,13 @@ public abstract class Message { * @return */ public static byte[] intToByteArray(long val, int len){ + long vor = val; int index = 0; byte[] data = new byte[len]; for (int i = 0; i < len; i++){ - data[len - index - 1] = (byte) ((val >>> (8 * index))); + data[len - index - 1] = (byte) (val & 0xFF); + val >>>= 8; index++; } @@ -202,9 +204,9 @@ public abstract class Message { */ public static void reverse(byte[] data) { for (int left = 0, right = data.length - 1; left < right; left++, right--) { - byte temp = data[left]; - data[left] = data[right]; - data[right] = temp; + byte temp = (byte) (data[left] & 0xff); + data[left] = (byte) (data[right] & 0xff); + data[right] = (byte) (temp & 0xff); } } diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 52983b22..32ea9abd 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -1,11 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.zip.CRC32; diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index 0bdd9716..d7f1d72c 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -7,6 +7,7 @@ import seng302.server.simulator.parsers.RaceParser; import java.util.List; import java.util.Observable; +import java.util.concurrent.ThreadLocalRandom; public class Simulator extends Observable implements Runnable { @@ -34,7 +35,7 @@ public class Simulator extends Observable implements Runnable { boat.setLng(startLng); boat.setLastPassedCorner(course.get(0)); boat.setHeadingCorner(course.get(1)); - boat.setSpeed(50000); + boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1)); } } From 50083a929711659f42b8c4473066345a0d90b773 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Sun, 30 Apr 2017 23:54:32 +1200 Subject: [PATCH 22/23] Added tests for reverse and intToBytes #story[829] --- src/test/java/seng302/server/TestMessage.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/test/java/seng302/server/TestMessage.java b/src/test/java/seng302/server/TestMessage.java index 8d8dfa46..6ba3a735 100644 --- a/src/test/java/seng302/server/TestMessage.java +++ b/src/test/java/seng302/server/TestMessage.java @@ -9,12 +9,11 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; public class TestMessage { private static int XML_MESSAGE_LEN = 14; - private static int RACE_STATUS_BASE_LEN = 24; - private static int BOAT_SUB_MESSAGE_LEN = 20; private static int CRC_LEN = 4; @@ -27,5 +26,31 @@ public class TestMessage { assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length())); } + @Test + public void testMessageBytesReverse(){ + byte[] bytes = {1,2,3,4,5}; + Message.reverse(bytes); + + int testValue = 1; + for (int i = 5; i > 0; i--){ + assertEquals((byte) testValue, bytes[i-1]); + testValue++; + } + } + + @Test + public void testIntToByteArray(){ + long originalValue = 0x5050; + long testValue = 0; + + byte[] bytes = Message.intToByteArray(originalValue, 6); + Message.reverse(bytes); + + for (int i = 0; i < bytes.length; i++){ + testValue += ((long) bytes[i] & 0xffL) << (8 * i); + } + + assertEquals(originalValue, testValue); + } } From a670f677e9e2b7ade34ebfc537b6ce3a03172e8e Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Mon, 1 May 2017 00:10:07 +1200 Subject: [PATCH 23/23] Added other email to .mailmap --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 99d9ff08..97b5f43d 100644 --- a/.mailmap +++ b/.mailmap @@ -16,4 +16,4 @@ # https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html # http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/ -Michael Rausch \ No newline at end of file +Michael Rausch \ No newline at end of file