mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
417 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a501b21d66 | |||
| 5fb8a0c2c1 | |||
| d867a4b7a2 | |||
| 3c91db59f3 | |||
| 72a390b484 | |||
| 978def4cf7 | |||
| 2331942bf9 | |||
| d41bdfebbc | |||
| d0565503e8 | |||
| f0c48e76e1 | |||
| 3639e0d3ce | |||
| 0276911b88 | |||
| af3a400841 | |||
| 5a2a78a7d5 | |||
| 5fc7442359 | |||
| 39cfaf6780 | |||
| 7a11b5eb77 | |||
| faf4600f51 | |||
| 3e383465a9 | |||
| ac279583df | |||
| ef2659a7b9 | |||
| 769d1956b3 | |||
| 8ca455ed24 | |||
| 7bda2bc580 | |||
| d7a290478d | |||
| d73e4f8ec5 | |||
| 6e02d3e533 | |||
| 7b4a70817b | |||
| 87acce71ea | |||
| 95b1c8a01f | |||
| dfd421c3b7 | |||
| 4596f1e0f8 | |||
| a59d06fbbf | |||
| 65286f273b | |||
| 9727e86249 | |||
| db614fe845 | |||
| 85899e3fbe | |||
| 87d6799c10 | |||
| 67f0c213c2 | |||
| c103595bba | |||
| 86a7c2565c | |||
| 21e6819f16 | |||
| 28569506f0 | |||
| dad2ebf693 | |||
| 02a7b804c1 | |||
| 9dbb31dcef | |||
| a932f41cc2 | |||
| 3570a3d2eb | |||
| 85852df176 | |||
| 6d045e9976 | |||
| 4fc00f8a3c | |||
| 9c2bac36b6 | |||
| 8501fc0b6d | |||
| e9e7f306cd | |||
| fa68a5fdff | |||
| 07a39722fb | |||
| 1d4ab0e1fa | |||
| 653651f97f | |||
| b1ba6e729a | |||
| 99685d76a9 | |||
| 711c94001b | |||
| 7f3d66d01d | |||
| 1db75a8ae4 | |||
| 7b9d28ade9 | |||
| 5843fc9212 | |||
| 3542c646f8 | |||
| 8fb5ea2223 | |||
| 958c1e216f | |||
| 100689a20b | |||
| 2da887e677 | |||
| bd213bcd9e | |||
| bf016356a6 | |||
| 76a750a764 | |||
| 000d562ffe | |||
| d02f2385dd | |||
| a5261494cd | |||
| ac47e9d88a | |||
| 7329f7dc65 | |||
| 7c5f146b11 | |||
| e3fbbd4590 | |||
| dc8baa09a3 | |||
| 720ce0ae5b | |||
| a7a667b4bc | |||
| 4e68cf31cf | |||
| d03460d69e | |||
| 50baf6f85b | |||
| 23a04facbe | |||
| 7fbecc8b3d | |||
| 9cb5956f3c | |||
| 366ebb3adb | |||
| 458bd5408f | |||
| c00d165f47 | |||
| d2bb15471a | |||
| c0cd260610 | |||
| 6a9357f598 | |||
| 6909f99773 | |||
| ce5424cc79 | |||
| c125708a4a | |||
| 2dc0ba07d9 | |||
| 2a3231d334 | |||
| d6a436d2eb | |||
| baacd8a9c0 | |||
| e1dddd317d | |||
| b9ae9c4730 | |||
| fab5f9229f | |||
| ff92262a78 | |||
| a4547e12cb | |||
| 1a39b6e4a3 | |||
| 58446ffaed | |||
| 1f09005147 | |||
| 3754b71f4d | |||
| 1d2222b599 | |||
| 89ceab0c4b | |||
| 76dabb8138 | |||
| 8b543488e3 | |||
| c52c345e53 | |||
| 9420717b44 | |||
| 9c1fe72f6b | |||
| 2e892f35ea | |||
| bedd742c93 | |||
| add6856727 | |||
| 4dc6b2af2b | |||
| e65558b8ac | |||
| e4cc5a0950 | |||
| 4b4f8a25a3 | |||
| c77adf385e | |||
| 9b7f4a93d8 | |||
| e780b47160 | |||
| 028fc44dce | |||
| a185c9dc96 | |||
| 52e10997f1 | |||
| f2f750298c | |||
| 1755b00079 | |||
| a844585faf | |||
| b7fe79a5cc | |||
| 2d5492601f | |||
| 0ee12021e2 | |||
| 6218d5506b | |||
| d79b0421c2 | |||
| 0b978593d4 | |||
| 8cb5b8caec | |||
| 2093e79b6a | |||
| 8ec6490627 | |||
| 0a7a9018f3 | |||
| 93cb0ca600 | |||
| 25c2de63a2 | |||
| 2411a3cc2c | |||
| fda233f5ad | |||
| c58cb1a476 | |||
| d2fd9ebaea | |||
| fda6625256 | |||
| 6ddaaa0dfa | |||
| 55c22fa7e2 | |||
| a7b8b0dbc3 | |||
| 7e1686a980 | |||
| 32b231e78a | |||
| 3ad37faedc | |||
| 09c4f98056 | |||
| 1c2870649a | |||
| a2ee4411be | |||
| a23352ef85 | |||
| f9d5bd10b1 | |||
| 0b8ad137b3 | |||
| a746191dba | |||
| 1772d1c05e | |||
| 1d7b527130 | |||
| 430779c943 | |||
| 9c79897e01 | |||
| 07386ed2db | |||
| abb15f6edf | |||
| 87f2f1fe63 | |||
| 249ad9e5c0 | |||
| 9d02d2fbea | |||
| b1598ccb0f | |||
| 08304f9c3e | |||
| 4cc48a355e | |||
| 2c5fddb695 | |||
| 126d8ea870 | |||
| 30a6cb98ec | |||
| 8813d06010 | |||
| 0fbca89030 | |||
| 421ef3c65a | |||
| 5937f8b640 | |||
| 5ec67d0b80 | |||
| 941febaf62 | |||
| a545e9dbc3 | |||
| b0e7dddaf3 | |||
| ed0a783374 | |||
| 97696cc95b | |||
| 4375b73257 | |||
| f97b18d594 | |||
| c7857872ce | |||
| 79105a1bdc | |||
| a4b22190c0 | |||
| a3ce5998ff | |||
| 7f0329dda6 | |||
| 8a40119a98 | |||
| a470cb66a2 | |||
| ecf2c52cfa | |||
| 43788bd153 | |||
| 81c2a8e0fd | |||
| e90a0ce435 | |||
| a727014fcb | |||
| ae28ccf228 | |||
| 281ce2d842 | |||
| f8af9cc259 | |||
| 8af80e6c3a | |||
| 874cdec654 | |||
| 99d5545ed3 | |||
| 423f1acdb6 | |||
| 454e9ac9f1 | |||
| a8e70b3631 | |||
| eb83e9dcc5 | |||
| 1ab849fd0d | |||
| db078538ff | |||
| 1c0d869894 | |||
| f9e6df46c1 | |||
| 5228c078bc | |||
| f5f73ede6b | |||
| 1160f274ee | |||
| 53f5d63f15 | |||
| 1150ec3e43 | |||
| c655cb3fab | |||
| 8e24c204fd | |||
| 7885b3fae2 | |||
| b01d39f19f | |||
| bbf494e9a1 | |||
| eae201cb4b | |||
| 87ef37a689 | |||
| b2c7b65191 | |||
| 908c0749cf | |||
| 47c5e6f155 | |||
| 9deba732b0 | |||
| b82d0d0137 | |||
| f1ad03e913 | |||
| 6cae338c1e | |||
| 7894e31926 | |||
| 25d8c8f9c4 | |||
| 1d9dd76356 | |||
| c2c34705d5 | |||
| 96ed5e445e | |||
| 870dc07fd2 | |||
| ecbb3f6658 | |||
| 34704bd93d | |||
| 201c88a253 | |||
| 6c4da58d9d | |||
| b56fa5cba3 | |||
| 9cedbeb6f6 | |||
| 4c6d107102 | |||
| 7917a2584b | |||
| f99f8a0d7c | |||
| 7db716f51c | |||
| 592e5a088f | |||
| 2d6850950c | |||
| ef6821a0cd | |||
| 6e9535d78f | |||
| 72a45f5984 | |||
| de600fa062 | |||
| 87e3b4f246 | |||
| 7b47d72ef0 | |||
| 7392bdb80d | |||
| af9f1417f1 | |||
| 8fc00fd750 | |||
| 4e5b67abfa | |||
| 84e8ac89fc | |||
| 12d081a1af | |||
| 5e6b402bf5 | |||
| 2bfa6cb038 | |||
| 8ac44d13df | |||
| d99055901f | |||
| 9c9f6e4e80 | |||
| 08e369f1ae | |||
| b0e99ab444 | |||
| c77a48f589 | |||
| 0135426dfe | |||
| 37c745c139 | |||
| 7880039801 | |||
| a56dac1e87 | |||
| 4ae422b47b | |||
| acd54dec7a | |||
| 6242ab0b2e | |||
| c8a96dcce9 | |||
| 4f2dca7ecf | |||
| e569574c01 | |||
| f544734b4d | |||
| 80b439470b | |||
| 539197cef5 | |||
| 1a867be387 | |||
| 3785cd705f | |||
| 5d7a438080 | |||
| 2f12f3e34f | |||
| 52bfa3ad34 | |||
| d1d659b698 | |||
| cdb9337aed | |||
| 8b0af5bb62 | |||
| 83232a935e | |||
| a30a1aa7c7 | |||
| 07cebb6c5b | |||
| 45bf65a3d3 | |||
| 60f5a99b0c | |||
| 526c12127f | |||
| 1daac842f2 | |||
| 3e4a6f0f2e | |||
| c1e937049e | |||
| 8f8d5c7384 | |||
| aad93d8913 | |||
| 027c7a1480 | |||
| df2efa3329 | |||
| 9d754c8819 | |||
| e11ceed28c | |||
| 8b8b6e4afa | |||
| ed2a22b573 | |||
| 41851ee925 | |||
| ffc61942a9 | |||
| 2e4382bff6 | |||
| f542dbb61e | |||
| 2869d139a3 | |||
| 3ec930491f | |||
| a0005064ac | |||
| 33fae9d69a | |||
| 913e5fee7b | |||
| 3992073303 | |||
| 797a99f632 | |||
| e891ed8a64 | |||
| e8c2cf809b | |||
| ec761893c7 | |||
| 5df7efda03 | |||
| 2fff73c075 | |||
| d37cbd263e | |||
| 3ec1242a9a | |||
| 881f7f8e30 | |||
| 12c2f31af9 | |||
| 49c0c029c3 | |||
| da7a34fc55 | |||
| 322ff740e2 | |||
| b1575e57df | |||
| 82b219cdba | |||
| e317de7562 | |||
| 9ecaa7c3b3 | |||
| 037b0db01b | |||
| 1e80d76acd | |||
| 360c55fdb9 | |||
| 176d65e0b2 | |||
| 0c08f5a03c | |||
| e257602b78 | |||
| 8f00f3a80c | |||
| 63d24c001f | |||
| 67668fe1fc | |||
| dbbb41e12f | |||
| 45053ba507 | |||
| d9f5f7a137 | |||
| b301ce5d27 | |||
| f02bd3b3f8 | |||
| e83eaa38e1 | |||
| 102b5f3ca1 | |||
| 63958a6717 | |||
| 4b8ac32ca9 | |||
| 00b29a1890 | |||
| c7e5f93bc4 | |||
| e4d87c91a2 | |||
| f84091e54e | |||
| e03e8825b2 | |||
| 355f8543f5 | |||
| 77e7db79cc | |||
| 2809d0d832 | |||
| 5b908ec355 | |||
| c480fca72a | |||
| c19f66a6a4 | |||
| 1e6fd1af09 | |||
| 55db2c9961 | |||
| 6ec8b0c3c5 | |||
| 78557a4536 | |||
| 8090cd7985 | |||
| 5ce34bed92 | |||
| 035841f221 | |||
| ef61a687d6 | |||
| fcb1e5e593 | |||
| 752863a0d3 | |||
| 1a3e330eb4 | |||
| 5f9da6b40a | |||
| aee62c29fe | |||
| fe76ef9cdc | |||
| acbaa838ec | |||
| f4134d83b5 | |||
| 24cc10e1cd | |||
| 20b79b40f2 | |||
| 72e2776b7e | |||
| 49e4c92da6 | |||
| ba761e4951 | |||
| c1aa1d8eae | |||
| 4231c3ccd8 | |||
| 65223ceaaf | |||
| ca22615c08 | |||
| 559a9f38c0 | |||
| 3bd8added4 | |||
| ba527a1979 | |||
| b73e4c89db | |||
| 945acb6071 | |||
| f163dfdd11 | |||
| a1eda8d91d | |||
| 5ed02a1fe1 | |||
| 66d4a4b958 | |||
| 0cd2867ac0 | |||
| 4d29354797 | |||
| 3085125f3e | |||
| 5e26ad7c36 | |||
| 765ea06c3b | |||
| 14a7305a2d | |||
| e7060d4b6f | |||
| 641039720e | |||
| 6f132f1e38 | |||
| a1e8d29b9c | |||
| c449da2916 | |||
| d22d758757 | |||
| acbde5aad8 | |||
| e26f2af93d | |||
| 6a6ed3ed44 |
@@ -180,3 +180,7 @@ local.properties
|
|||||||
.recommenders/
|
.recommenders/
|
||||||
|
|
||||||
Makefile
|
Makefile
|
||||||
|
|
||||||
|
infer-out/
|
||||||
|
infer.txt
|
||||||
|
log.log
|
||||||
@@ -23,5 +23,5 @@ Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
|||||||
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
|
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
|
||||||
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
|
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
|
||||||
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
|
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
|
||||||
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com>
|
Alistair McIntyre <ajm412@uclive.ac.nz> <alistairjmcintyre@gmail.com>
|
||||||
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
||||||
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
|
|||||||
|
|
||||||
- Configuration file
|
- Configuration file
|
||||||
|
|
||||||
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file.
|
We decided to store the team information including team names and boat currentVelocity, as well as race configuration setting in external file.
|
||||||
|
|
||||||
To read external files, "Json-simple" library has been used to parse information.
|
To read external files, "Json-simple" library has been used to parse information.
|
||||||
By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
|
By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja
|
|||||||
|
|
||||||
## The config file
|
## The config file
|
||||||
|
|
||||||
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second).
|
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a currentVelocity (in meters per second).
|
||||||
|
|
||||||
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
|
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,40 @@
|
|||||||
<version>2.7.13</version>
|
<version>2.7.13</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.cukes</groupId>
|
||||||
|
<artifactId>cucumber-junit</artifactId>
|
||||||
|
<version>1.2.5</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-java -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.cukes</groupId>
|
||||||
|
<artifactId>cucumber-java</artifactId>
|
||||||
|
<version>1.2.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.freemarker</groupId>
|
||||||
|
<artifactId>freemarker</artifactId>
|
||||||
|
<version>2.3.26-incubating</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>1.1.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-cli</groupId>
|
||||||
|
<artifactId>commons-cli</artifactId>
|
||||||
|
<version>1.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,82 +1,101 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import seng302.models.PolarTable;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import seng302.models.stream.StreamParser;
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
import seng302.models.stream.StreamReceiver;
|
import org.apache.commons.cli.DefaultParser;
|
||||||
import seng302.server.ServerThread;
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.model.PolarTable;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
public static void parseArgs(String[] args) throws ParseException {
|
||||||
|
Options options = new Options();
|
||||||
|
CommandLineParser parser = new DefaultParser();
|
||||||
|
CommandLine cmd;
|
||||||
|
|
||||||
|
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
|
||||||
|
.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
|
||||||
|
options.addOption("debugLevel", true, "Set the application debug level");
|
||||||
|
|
||||||
|
cmd = parser.parse(options, args);
|
||||||
|
|
||||||
|
if (cmd.hasOption("debugLevel")) {
|
||||||
|
|
||||||
|
switch (cmd.getOptionValue("debugLevel")) {
|
||||||
|
case "DEBUG":
|
||||||
|
rootLogger.setLevel(Level.DEBUG);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ALL":
|
||||||
|
rootLogger.setLevel(Level.ALL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "WARNING":
|
||||||
|
rootLogger.setLevel(Level.WARN);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ERROR":
|
||||||
|
rootLogger.setLevel(Level.ERROR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "INFO":
|
||||||
|
rootLogger.setLevel(Level.INFO);
|
||||||
|
|
||||||
|
case "TRACE":
|
||||||
|
rootLogger.setLevel(Level.TRACE);
|
||||||
|
|
||||||
|
default:
|
||||||
|
rootLogger.setLevel(Level.ALL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rootLogger.setLevel(Level.WARN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile());
|
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
|
||||||
|
|
||||||
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
|
||||||
primaryStage.setTitle("RaceVision");
|
primaryStage.setTitle("RaceVision");
|
||||||
primaryStage.setScene(new Scene(root, 1530, 960));
|
Scene scene = new Scene(root, 1530, 960);
|
||||||
primaryStage.setMaxWidth(1530);
|
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
primaryStage.setMaxHeight(960);
|
primaryStage.setScene(scene);
|
||||||
|
// primaryStage.setMaxWidth(1530);
|
||||||
|
// primaryStage.setMaxHeight(960);
|
||||||
|
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||||
// primaryStage.setMaximized(true);
|
// primaryStage.setMaximized(true);
|
||||||
|
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
|
|
||||||
primaryStage.setOnCloseRequest(e -> {
|
primaryStage.setOnCloseRequest(e -> {
|
||||||
StreamParser.appClose();
|
// ClientPacketParser.appClose();
|
||||||
StreamReceiver.noMoreBytes();
|
// ClientPacketParser.appClose();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ClientState.primaryStage = primaryStage;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
StreamReceiver sr = null;
|
|
||||||
|
|
||||||
new ServerThread("Racevision Test Server");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(2000);
|
parseArgs(args);
|
||||||
} catch (InterruptedException e) {
|
} catch (ParseException e) {
|
||||||
e.printStackTrace();
|
logger.error("Could not parse command line arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length == 1 && args[0].equals("-standalone")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length == 3 && args[0].equals("-server")) {
|
|
||||||
|
|
||||||
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
|
|
||||||
|
|
||||||
} else if (args.length == 2 && args[0].equals("-server")) {
|
|
||||||
switch (args[1]) {
|
|
||||||
case "internal":
|
|
||||||
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
|
||||||
break;
|
|
||||||
case "staffserver":
|
|
||||||
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
|
|
||||||
break;
|
|
||||||
case "official":
|
|
||||||
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Change the StreamReceiver in this else block to change the default data source.
|
|
||||||
else{
|
|
||||||
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
|
||||||
}
|
|
||||||
|
|
||||||
sr.start();
|
|
||||||
StreamParser streamParser = new StreamParser("StreamParser");
|
|
||||||
streamParser.start();
|
|
||||||
|
|
||||||
launch(args);
|
launch(args);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Class for performing geometric calculations on the canvas
|
|
||||||
* Created by wmu16 on 24/05/17.
|
|
||||||
*/
|
|
||||||
public final class GeometryUtils {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the line function on two points of a line and a test point to test which side of the line that point is
|
|
||||||
* on. If the return value is
|
|
||||||
* return 1, then the point is on one side of the line,
|
|
||||||
* return -1 then the point is on the other side of the line
|
|
||||||
* return 0 then the point is exactly on the line.
|
|
||||||
* @param linePoint1 One point of the line
|
|
||||||
* @param linePoint2 Second point of the line
|
|
||||||
* @param testPoint The point to test with this line
|
|
||||||
* @return A return value indicating which side of the line the point is on
|
|
||||||
*/
|
|
||||||
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
|
|
||||||
|
|
||||||
Double x = testPoint.getX();
|
|
||||||
Double y = testPoint.getY();
|
|
||||||
Double x1 = linePoint1.getX();
|
|
||||||
Double y1 = linePoint1.getY();
|
|
||||||
Double x2 = linePoint2.getX();
|
|
||||||
Double y2 = linePoint2.getY();
|
|
||||||
|
|
||||||
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
|
|
||||||
|
|
||||||
if (result > 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (result < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
|
|
||||||
* point
|
|
||||||
* @param originPoint The point with which to use as the base for our vector addition
|
|
||||||
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
|
|
||||||
* @param vectorLength The length out on this angle from the origin point to create the new point
|
|
||||||
* @return a Point2D
|
|
||||||
*/
|
|
||||||
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
|
|
||||||
|
|
||||||
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
|
|
||||||
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
|
|
||||||
|
|
||||||
return new Point2D(endPointX, endPointY);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.animation.AnimationTimer;
|
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.Group;
|
|
||||||
import javafx.scene.canvas.Canvas;
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import seng302.models.BoatGroup;
|
|
||||||
import seng302.models.Colors;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.map.Boundary;
|
|
||||||
import seng302.models.map.CanvasMap;
|
|
||||||
import seng302.models.mark.*;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
import seng302.models.stream.XMLParser;
|
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
|
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
|
||||||
import seng302.models.stream.packets.BoatPositionPacket;
|
|
||||||
import seng302.server.simulator.GeoUtility;
|
|
||||||
import seng302.server.simulator.mark.Position;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 Group group;
|
|
||||||
private GraphicsContext gc;
|
|
||||||
private ImageView mapImage;
|
|
||||||
|
|
||||||
private final int BUFFER_SIZE = 50;
|
|
||||||
private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
|
|
||||||
private final int PANEL_HEIGHT = 960;
|
|
||||||
private final int CANVAS_WIDTH = 720;
|
|
||||||
private final int CANVAS_HEIGHT = 720;
|
|
||||||
private boolean horizontalInversion = false;
|
|
||||||
|
|
||||||
private double distanceScaleFactor;
|
|
||||||
private ScaleDirection scaleDirection;
|
|
||||||
private Mark minLatPoint;
|
|
||||||
private Mark minLonPoint;
|
|
||||||
private Mark maxLatPoint;
|
|
||||||
private Mark maxLonPoint;
|
|
||||||
private double referencePointX;
|
|
||||||
private double referencePointY;
|
|
||||||
private double metersPerPixelX;
|
|
||||||
private double metersPerPixelY;
|
|
||||||
|
|
||||||
private List<MarkGroup> markGroups = new ArrayList<>();
|
|
||||||
private List<BoatGroup> boatGroups = new ArrayList<>();
|
|
||||||
|
|
||||||
//FRAME RATE
|
|
||||||
private Double frameRate = 60.0;
|
|
||||||
private final long[] frameTimes = new long[30];
|
|
||||||
private int frameTimeIndex = 0;
|
|
||||||
private boolean arrayFilled = false;
|
|
||||||
|
|
||||||
public AnimationTimer timer;
|
|
||||||
|
|
||||||
private enum ScaleDirection {
|
|
||||||
HORIZONTAL,
|
|
||||||
VERTICAL
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setup(RaceViewController raceViewController){
|
|
||||||
this.raceViewController = raceViewController;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initialize() {
|
|
||||||
raceViewController = new RaceViewController();
|
|
||||||
canvas = new ResizableCanvas();
|
|
||||||
group = new Group();
|
|
||||||
|
|
||||||
// create image view for map, bind panel size to image
|
|
||||||
mapImage = new ImageView();
|
|
||||||
canvasPane.getChildren().add(mapImage);
|
|
||||||
mapImage.fitWidthProperty().bind(canvasPane.widthProperty());
|
|
||||||
mapImage.fitHeightProperty().bind(canvasPane.heightProperty());
|
|
||||||
|
|
||||||
canvasPane.getChildren().add(canvas);
|
|
||||||
canvasPane.getChildren().add(group);
|
|
||||||
// Bind canvas size to stack pane size.
|
|
||||||
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
|
|
||||||
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initializeCanvas (){
|
|
||||||
|
|
||||||
gc = canvas.getGraphicsContext2D();
|
|
||||||
gc.setGlobalAlpha(0.5);
|
|
||||||
fitMarksToCanvas();
|
|
||||||
drawGoogleMap();
|
|
||||||
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
|
||||||
initializeBoats();
|
|
||||||
initializeMarks();
|
|
||||||
timer = new AnimationTimer() {
|
|
||||||
|
|
||||||
private int UPDATE_FPM_PERIOD = 50; // update FPM label every 50 frames
|
|
||||||
private int updateFPMCounter = 100;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(long now) {
|
|
||||||
|
|
||||||
//fps stuff
|
|
||||||
long oldFrameTime = frameTimes[frameTimeIndex] ;
|
|
||||||
frameTimes[frameTimeIndex] = now ;
|
|
||||||
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ;
|
|
||||||
if (frameTimeIndex == 0) {
|
|
||||||
arrayFilled = true ;
|
|
||||||
}
|
|
||||||
long elapsedNanos;
|
|
||||||
if (arrayFilled) {
|
|
||||||
elapsedNanos = now - oldFrameTime ;
|
|
||||||
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
|
|
||||||
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
|
||||||
if (updateFPMCounter++ > UPDATE_FPM_PERIOD) {
|
|
||||||
updateFPMCounter = 0;
|
|
||||||
drawFps(frameRate.intValue());
|
|
||||||
}
|
|
||||||
raceViewController.updateSparkLine();
|
|
||||||
}
|
|
||||||
updateGroups();
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First find the top right and bottom left points' geo locations, then retrieve
|
|
||||||
* map from google to display on image view. - Haoming 22/5/2017
|
|
||||||
*/
|
|
||||||
private void drawGoogleMap() {
|
|
||||||
findMetersPerPixel();
|
|
||||||
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
|
||||||
// distance from top left extreme to panel origin (top left corner)
|
|
||||||
double distanceFromTopLeftToOrigin = Math.sqrt(Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math.pow(topLeftPoint.getY() * metersPerPixelY, 2));
|
|
||||||
// angle from top left extreme to panel origin
|
|
||||||
double bearingFromTopLeftToOrigin = Math.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
|
|
||||||
// the top left extreme
|
|
||||||
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
|
||||||
Position originPos = GeoUtility.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
|
|
||||||
|
|
||||||
// distance from origin corner to bottom right corner of the panel
|
|
||||||
double distanceFromOriginToBottomRight = Math.sqrt(Math.pow(PANEL_HEIGHT* metersPerPixelY, 2) + Math.pow(PANEL_WIDTH * metersPerPixelX, 2));
|
|
||||||
double bearingFromOriginToBottomRight = Math.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
|
|
||||||
Position bottomRightPos = GeoUtility.getGeoCoordinate(originPos, bearingFromOriginToBottomRight, distanceFromOriginToBottomRight);
|
|
||||||
|
|
||||||
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), bottomRightPos.getLat(), originPos.getLng());
|
|
||||||
CanvasMap canvasMap = new CanvasMap(boundary);
|
|
||||||
mapImage.setImage(canvasMap.getMapImage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds border marks to the canvas, taken from the XML file
|
|
||||||
*
|
|
||||||
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are
|
|
||||||
* named the same as those in the model package but are, however not the same, so they do not have things such as
|
|
||||||
* a type and must be derived from the number of marks in a compound mark etc..
|
|
||||||
*/
|
|
||||||
private void addRaceBorder() {
|
|
||||||
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
|
|
||||||
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
|
|
||||||
gc.setStroke(Color.DARKBLUE);
|
|
||||||
gc.setLineWidth(3);
|
|
||||||
double[] xBoundaryPoints = new double[courseLimits.size()];
|
|
||||||
double[] yBoundaryPoints = new double[courseLimits.size()];
|
|
||||||
for (int i = 0; i < courseLimits.size() - 1; i++) {
|
|
||||||
Limit thisPoint1 = courseLimits.get(i);
|
|
||||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
|
||||||
Limit thisPoint2 = courseLimits.get(i+1);
|
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
|
||||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
|
||||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
|
||||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
|
||||||
borderPoint2.getX(), borderPoint2.getY());
|
|
||||||
xBoundaryPoints[i] = borderPoint1.getX();
|
|
||||||
yBoundaryPoints[i] = borderPoint1.getY();
|
|
||||||
}
|
|
||||||
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
|
|
||||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
|
||||||
Limit thisPoint2 = courseLimits.get(0);
|
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
|
||||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
|
||||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
|
||||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
|
||||||
borderPoint2.getX(), borderPoint2.getY());
|
|
||||||
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
|
||||||
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
|
||||||
// gc.setFill(Color.LIGHTBLUE);
|
|
||||||
// gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateGroups(){
|
|
||||||
for (BoatGroup boatGroup : boatGroups) {
|
|
||||||
// some raceObjects will have multiple ID's (for instance gate marks)
|
|
||||||
//checking if the current "ID" has any updates associated with it
|
|
||||||
if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
|
|
||||||
if (boatGroup.isStopped()) {
|
|
||||||
updateBoatGroup(boatGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boatGroup.move();
|
|
||||||
}
|
|
||||||
for (MarkGroup markGroup : markGroups) {
|
|
||||||
for (Long id : markGroup.getRaceIds()) {
|
|
||||||
if (StreamParser.markLocations.containsKey(id)) {
|
|
||||||
updateMarkGroup(id, markGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkForCourseChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkForCourseChanges() {
|
|
||||||
if (StreamParser.isNewRaceXmlReceived()){
|
|
||||||
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
||||||
drawGoogleMap();
|
|
||||||
addRaceBorder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBoatGroup(BoatGroup boatGroup) {
|
|
||||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
|
|
||||||
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets
|
|
||||||
if (movementQueue.size() > 0){
|
|
||||||
try {
|
|
||||||
BoatPositionPacket positionPacket = movementQueue.take();
|
|
||||||
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
|
||||||
double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
|
||||||
boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), positionPacket.getTimeValid(), frameRate, boatGroup.getRaceId());
|
|
||||||
} catch (InterruptedException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateMarkGroup (long raceId, MarkGroup markGroup) {
|
|
||||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId);
|
|
||||||
if (movementQueue.size() > 0){
|
|
||||||
try {
|
|
||||||
BoatPositionPacket positionPacket = movementQueue.take();
|
|
||||||
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
|
||||||
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
|
|
||||||
} catch (InterruptedException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws all the boats.
|
|
||||||
*/
|
|
||||||
private void initializeBoats() {
|
|
||||||
Map<Integer, Yacht> boats = StreamParser.getBoats();
|
|
||||||
Group boatAnnotations = new Group();
|
|
||||||
|
|
||||||
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML().getParticipants();
|
|
||||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
|
||||||
for (Participant p : participants) {
|
|
||||||
participantIDs.add(p.getsourceID());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Yacht boat : boats.values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) {
|
|
||||||
boat.setColour(Colors.getColor());
|
|
||||||
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
|
|
||||||
boatGroups.add(boatGroup);
|
|
||||||
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.getChildren().add(boatAnnotations);
|
|
||||||
group.getChildren().addAll(boatGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeMarks() {
|
|
||||||
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
|
|
||||||
for (Mark mark : allMarks) {
|
|
||||||
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
|
||||||
SingleMark sMark = (SingleMark) mark;
|
|
||||||
|
|
||||||
MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark));
|
|
||||||
markGroups.add(markGroup);
|
|
||||||
} else {
|
|
||||||
GateMark gMark = (GateMark) mark;
|
|
||||||
|
|
||||||
MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list.
|
|
||||||
markGroups.add(markGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.getChildren().addAll(markGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResizableCanvas extends Canvas {
|
|
||||||
|
|
||||||
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.clearRect(5, 5, 60, 30);
|
|
||||||
gc.setFont(new Font(16));
|
|
||||||
gc.setLineWidth(4);
|
|
||||||
gc.setGlobalAlpha(0.75);
|
|
||||||
gc.fillText(fps + " FPS", 5, 20);
|
|
||||||
gc.setGlobalAlpha(0.5);
|
|
||||||
} else {
|
|
||||||
gc.clearRect(5,5,60,30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
|
|
||||||
*/
|
|
||||||
private void fitMarksToCanvas() {
|
|
||||||
//Check is called once to avoid unnecessarily change the course limits once the race is running
|
|
||||||
StreamParser.isNewRaceXmlReceived();
|
|
||||||
findMinMaxPoint();
|
|
||||||
double minLonToMaxLon = scaleRaceExtremities();
|
|
||||||
calculateReferencePointLocation(minLonToMaxLon);
|
|
||||||
//givePointsXY();
|
|
||||||
addRaceBorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost
|
|
||||||
* marker, rightmost marker, southern most marker and northern most marker respectively.
|
|
||||||
*/
|
|
||||||
private void findMinMaxPoint() {
|
|
||||||
List<Limit> sortedPoints = new ArrayList<>();
|
|
||||||
for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) {
|
|
||||||
sortedPoints.add(limit);
|
|
||||||
}
|
|
||||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
|
||||||
Limit minLatMark = sortedPoints.get(0);
|
|
||||||
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
|
|
||||||
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID());
|
|
||||||
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID());
|
|
||||||
|
|
||||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
|
|
||||||
//If the course is on a point on the earth where longitudes wrap around.
|
|
||||||
Limit minLonMark = sortedPoints.get(0);
|
|
||||||
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
|
|
||||||
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
|
|
||||||
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
|
|
||||||
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) {
|
|
||||||
horizontalInversion = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the location of a reference point, this is always the point with minimum latitude, in relation to the
|
|
||||||
* canvas.
|
|
||||||
*
|
|
||||||
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to maximum longitude.
|
|
||||||
*/
|
|
||||||
private void calculateReferencePointLocation (double minLonToMaxLon) {
|
|
||||||
Mark referencePoint = minLatPoint;
|
|
||||||
double referenceAngle;
|
|
||||||
|
|
||||||
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
|
||||||
referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
|
||||||
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
|
|
||||||
referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE);
|
|
||||||
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
|
||||||
referencePointY = referencePointY / 2;
|
|
||||||
referencePointY += BUFFER_SIZE;
|
|
||||||
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
|
||||||
} else {
|
|
||||||
referencePointY = CANVAS_HEIGHT - BUFFER_SIZE;
|
|
||||||
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
|
||||||
referencePointX = BUFFER_SIZE;
|
|
||||||
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
|
||||||
referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
|
|
||||||
}
|
|
||||||
if(horizontalInversion) {
|
|
||||||
referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor
|
|
||||||
* Returns the max horizontal distance of the map.
|
|
||||||
*/
|
|
||||||
private double scaleRaceExtremities () {
|
|
||||||
|
|
||||||
double vertAngle = Math.abs(Mark.calculateHeadingRad(minLatPoint, maxLatPoint));
|
|
||||||
double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint);
|
|
||||||
double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
|
|
||||||
|
|
||||||
if (horiAngle <= (Math.PI / 2))
|
|
||||||
horiAngle = (Math.PI / 2) - horiAngle;
|
|
||||||
else
|
|
||||||
horiAngle = horiAngle - (Math.PI / 2);
|
|
||||||
double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
|
|
||||||
|
|
||||||
double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance;
|
|
||||||
|
|
||||||
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) {
|
|
||||||
distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance;
|
|
||||||
scaleDirection = ScaleDirection.HORIZONTAL;
|
|
||||||
} else {
|
|
||||||
distanceScaleFactor = vertScale;
|
|
||||||
scaleDirection = ScaleDirection.VERTICAL;
|
|
||||||
}
|
|
||||||
return horiDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D findScaledXY (Mark unscaled) {
|
|
||||||
return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
|
|
||||||
double distanceFromReference;
|
|
||||||
double angleFromReference;
|
|
||||||
int xAxisLocation = (int) referencePointX;
|
|
||||||
int yAxisLocation = (int) referencePointY;
|
|
||||||
|
|
||||||
angleFromReference = Mark.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
|
|
||||||
distanceFromReference = Mark.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
|
|
||||||
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
|
||||||
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
|
||||||
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
|
||||||
} else if (angleFromReference >= 0) {
|
|
||||||
angleFromReference = angleFromReference - Math.PI / 2;
|
|
||||||
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
|
||||||
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
|
||||||
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
|
|
||||||
angleFromReference = Math.abs(angleFromReference);
|
|
||||||
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
|
||||||
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
|
||||||
} else {
|
|
||||||
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
|
|
||||||
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
|
||||||
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
|
||||||
}
|
|
||||||
if(horizontalInversion) {
|
|
||||||
xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
return new Point2D(xAxisLocation, yAxisLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the number of meters per pixel.
|
|
||||||
*/
|
|
||||||
private void findMetersPerPixel () {
|
|
||||||
Point2D p1, p2;
|
|
||||||
Mark m1, m2;
|
|
||||||
double theta, distance, dx, dy, dHorizontal, dVertical;
|
|
||||||
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
|
|
||||||
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
|
|
||||||
p1 = findScaledXY(m1);
|
|
||||||
p2 = findScaledXY(m2);
|
|
||||||
theta = Mark.calculateHeadingRad(m1, m2);
|
|
||||||
distance = Mark.calculateDistance(m1, m2);
|
|
||||||
dHorizontal = Math.abs(Math.sin(theta) * distance);
|
|
||||||
dVertical = Math.abs(Math.cos(theta) * distance);
|
|
||||||
dx = Math.abs(p1.getX() - p2.getX());
|
|
||||||
dy = Math.abs(p1.getY() - p2.getY());
|
|
||||||
metersPerPixelX = dHorizontal / dx;
|
|
||||||
metersPerPixelY = dVertical / dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BoatGroup> getBoatGroups() {
|
|
||||||
return boatGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MarkGroup> getMarkGroups() {
|
|
||||||
return markGroups;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
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 java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
|
|
||||||
public class Controller implements Initializable {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private AnchorPane contentPane;
|
|
||||||
|
|
||||||
private void setContentPane(String jfxUrl) {
|
|
||||||
try {
|
|
||||||
contentPane.getChildren().removeAll();
|
|
||||||
contentPane.getChildren().clear();
|
|
||||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
setContentPane("/views/StartScreenView.fxml");
|
|
||||||
StreamParser.boatLocations.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,685 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import javafx.animation.Timeline;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.geometry.Side;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.chart.LineChart;
|
|
||||||
import javafx.scene.chart.NumberAxis;
|
|
||||||
import javafx.scene.chart.XYChart;
|
|
||||||
import javafx.scene.chart.XYChart.Series;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.CheckBox;
|
|
||||||
import javafx.scene.control.ComboBox;
|
|
||||||
import javafx.scene.control.Slider;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.paint.Paint;
|
|
||||||
import javafx.scene.shape.Line;
|
|
||||||
import javafx.scene.text.Text;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.stage.StageStyle;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import javafx.util.StringConverter;
|
|
||||||
import seng302.GeometryUtils;
|
|
||||||
import seng302.controllers.annotations.Annotation;
|
|
||||||
import seng302.controllers.annotations.ImportantAnnotationController;
|
|
||||||
import seng302.controllers.annotations.ImportantAnnotationDelegate;
|
|
||||||
import seng302.controllers.annotations.ImportantAnnotationsState;
|
|
||||||
import seng302.models.*;
|
|
||||||
import seng302.models.mark.GateMark;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
import seng302.models.mark.MarkGroup;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
import seng302.models.stream.XMLParser;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by ptg19 on 29/03/17.
|
|
||||||
*/
|
|
||||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private LineChart raceSparkLine;
|
|
||||||
@FXML
|
|
||||||
private NumberAxis sparklineYAxis;
|
|
||||||
@FXML
|
|
||||||
private VBox positionVbox;
|
|
||||||
@FXML
|
|
||||||
private CheckBox toggleFps;
|
|
||||||
@FXML
|
|
||||||
private Text timerLabel;
|
|
||||||
@FXML
|
|
||||||
private AnchorPane contentAnchorPane;
|
|
||||||
@FXML
|
|
||||||
private Text windArrowText, windDirectionText;
|
|
||||||
@FXML
|
|
||||||
private Slider annotationSlider;
|
|
||||||
@FXML
|
|
||||||
private Button selectAnnotationBtn;
|
|
||||||
@FXML
|
|
||||||
private ComboBox boatSelectionComboBox;
|
|
||||||
@FXML
|
|
||||||
private CanvasController includedCanvasController;
|
|
||||||
|
|
||||||
private static ArrayList<Yacht> startingBoats = new ArrayList<>();
|
|
||||||
private boolean displayFps;
|
|
||||||
private Timeline timerTimeline;
|
|
||||||
private Stage stage;
|
|
||||||
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
|
|
||||||
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
|
|
||||||
private ImportantAnnotationsState importantAnnotations;
|
|
||||||
private Yacht selectedBoat;
|
|
||||||
|
|
||||||
public void initialize() {
|
|
||||||
// Load a default important annotation state
|
|
||||||
importantAnnotations = new ImportantAnnotationsState();
|
|
||||||
|
|
||||||
//Formatting the y axis of the sparkline
|
|
||||||
raceSparkLine.getYAxis().setRotate(180);
|
|
||||||
raceSparkLine.getYAxis().setTickLabelRotation(180);
|
|
||||||
raceSparkLine.getYAxis().setTranslateX(-5);
|
|
||||||
raceSparkLine.getYAxis().setAutoRanging(false);
|
|
||||||
sparklineYAxis.setTickMarkVisible(false);
|
|
||||||
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
|
|
||||||
|
|
||||||
includedCanvasController.setup(this);
|
|
||||||
includedCanvasController.initializeCanvas();
|
|
||||||
initializeUpdateTimer();
|
|
||||||
initialiseFPSCheckBox();
|
|
||||||
initialiseAnnotationSlider();
|
|
||||||
initialiseBoatSelectionComboBox();
|
|
||||||
includedCanvasController.timer.start();
|
|
||||||
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The important annotations have been changed, update this view
|
|
||||||
*
|
|
||||||
* @param importantAnnotationsState The current state of the selected annotations
|
|
||||||
*/
|
|
||||||
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
|
||||||
this.importantAnnotations = importantAnnotationsState;
|
|
||||||
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the "select annotations" view in a new window
|
|
||||||
*/
|
|
||||||
private void loadSelectAnnotationView() {
|
|
||||||
try {
|
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader();
|
|
||||||
Stage stage = new Stage();
|
|
||||||
|
|
||||||
// Set controller
|
|
||||||
ImportantAnnotationController controller = new ImportantAnnotationController(this,
|
|
||||||
stage);
|
|
||||||
fxmlLoader.setController(controller);
|
|
||||||
|
|
||||||
// Load FXML and set CSS
|
|
||||||
fxmlLoader
|
|
||||||
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
|
|
||||||
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
|
|
||||||
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
stage.initStyle(StageStyle.UNDECORATED);
|
|
||||||
|
|
||||||
stage.setScene(scene);
|
|
||||||
stage.show();
|
|
||||||
|
|
||||||
controller.loadState(importantAnnotations);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initialiseFPSCheckBox() {
|
|
||||||
displayFps = true;
|
|
||||||
toggleFps.selectedProperty().addListener(
|
|
||||||
(observable, oldValue, newValue) -> displayFps = !displayFps);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialiseAnnotationSlider() {
|
|
||||||
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
|
||||||
@Override
|
|
||||||
public String toString(Double n) {
|
|
||||||
if (n == 0) {
|
|
||||||
return "None";
|
|
||||||
}
|
|
||||||
if (n == 1) {
|
|
||||||
return "Important";
|
|
||||||
}
|
|
||||||
if (n == 2) {
|
|
||||||
return "All";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "All";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double fromString(String s) {
|
|
||||||
switch (s) {
|
|
||||||
case "None":
|
|
||||||
return 0d;
|
|
||||||
case "Important":
|
|
||||||
return 1d;
|
|
||||||
case "All":
|
|
||||||
return 2d;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 2d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
|
|
||||||
setAnnotations((int) annotationSlider.getValue()));
|
|
||||||
|
|
||||||
annotationSlider.setValue(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to add any new boats into the race that may have started late or not have had data received yet
|
|
||||||
*/
|
|
||||||
void updateSparkLine(){
|
|
||||||
// Collect the racing boats that aren't already in the chart
|
|
||||||
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID())
|
|
||||||
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
|
||||||
|
|
||||||
// Obtain the qualifying boats to set the max on the Y axis
|
|
||||||
racingBoats = startingBoats.stream().filter(yacht ->
|
|
||||||
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
|
||||||
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
|
|
||||||
|
|
||||||
// Create a new data series for new boats
|
|
||||||
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
|
|
||||||
Series<String, Double> yachtData = new Series<>();
|
|
||||||
yachtData.setName(yacht.getBoatName());
|
|
||||||
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
|
||||||
sparkLineData.put(yacht.getSourceID(), yachtData);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Lambda function to sort the series in order of leg (later legs shown more to the right)
|
|
||||||
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
|
|
||||||
Collections.sort(positions, (o1, o2) -> {
|
|
||||||
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
|
|
||||||
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
|
|
||||||
if (leg2 < leg1){
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Adds the new data series to the sparkline (and set the colour of the series)
|
|
||||||
raceSparkLine.setCreateSymbols(false);
|
|
||||||
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
|
|
||||||
raceSparkLine.getData().add(spark);
|
|
||||||
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the yachts sparkline of the desired boat and using the new leg number
|
|
||||||
* @param yacht The yacht to be updated on the sparkline
|
|
||||||
* @param legNumber the leg number that the position will be assigned to
|
|
||||||
*/
|
|
||||||
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
|
|
||||||
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
|
|
||||||
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets the rgb string of the boats colour to use for the chart via css
|
|
||||||
* @param boatName boat passed in to get the boats colour
|
|
||||||
* @return the colour as an rgb string
|
|
||||||
*/
|
|
||||||
private String getBoatColorAsRGB(String boatName){
|
|
||||||
Color color = Color.WHITE;
|
|
||||||
for (Yacht yacht: startingBoats){
|
|
||||||
if (Objects.equals(yacht.getBoatName(), boatName)){
|
|
||||||
color = yacht.getColour();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (color == null){
|
|
||||||
return String.format( "#%02X%02X%02X",255,255,255);
|
|
||||||
}
|
|
||||||
return String.format( "#%02X%02X%02X",
|
|
||||||
(int)( color.getRed() * 255 ),
|
|
||||||
(int)( color.getGreen() * 255 ),
|
|
||||||
(int)( color.getBlue() * 255 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
|
|
||||||
* orderings etc.. which are dependent on the info from the stream parser constantly.
|
|
||||||
* Updates of each of these attributes are called ONCE EACH SECOND
|
|
||||||
*/
|
|
||||||
private void initializeUpdateTimer() {
|
|
||||||
timerTimeline = new Timeline();
|
|
||||||
timerTimeline.setCycleCount(Timeline.INDEFINITE);
|
|
||||||
// Run timer update every second
|
|
||||||
timerTimeline.getKeyFrames().add(
|
|
||||||
new KeyFrame(Duration.seconds(1),
|
|
||||||
event -> {
|
|
||||||
updateRaceTime();
|
|
||||||
updateWindDirection();
|
|
||||||
updateOrder();
|
|
||||||
updateBoatSelectionComboBox();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start the timer
|
|
||||||
timerTimeline.playFromStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterates over all corners until ones SeqID matches with the boats current leg number.
|
|
||||||
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
|
|
||||||
* Returns null if no next mark found.
|
|
||||||
* @param bg The BoatGroup to find the next mark of
|
|
||||||
* @return The next Mark or null if none found
|
|
||||||
*/
|
|
||||||
private Mark getNextMark(BoatGroup bg) {
|
|
||||||
Integer legNumber = bg.getBoat().getLegNumber();
|
|
||||||
|
|
||||||
List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence();
|
|
||||||
|
|
||||||
if (legNumber == 0) {
|
|
||||||
return null;
|
|
||||||
} else if (legNumber == markSequence.size() - 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
|
|
||||||
if (legNumber + 2 == corner.getSeqID()) {
|
|
||||||
Integer thisCompoundMarkID = corner.getCompoundMarkID();
|
|
||||||
|
|
||||||
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
|
|
||||||
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
|
|
||||||
return mark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the wind direction arrow and text as from info from the StreamParser
|
|
||||||
*/
|
|
||||||
private void updateWindDirection() {
|
|
||||||
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
|
|
||||||
windArrowText.setRotate(StreamParser.getWindDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the clock for the race
|
|
||||||
*/
|
|
||||||
private void updateRaceTime() {
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
timerLabel.setFill(Color.RED);
|
|
||||||
timerLabel.setText("Race Finished!");
|
|
||||||
} else {
|
|
||||||
timerLabel.setText(getTimeSinceStartOfRace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
|
|
||||||
* in the boat selection combo box
|
|
||||||
*/
|
|
||||||
private void updateBoatSelectionComboBox() {
|
|
||||||
ObservableList<Yacht> observableBoats = FXCollections
|
|
||||||
.observableArrayList(StreamParser.getBoatsPos().values());
|
|
||||||
boatSelectionComboBox.setItems(observableBoats);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the order of the boats as from the StreamParser and sets them in the boat order
|
|
||||||
* section
|
|
||||||
*/
|
|
||||||
private void updateOrder() {
|
|
||||||
positionVbox.getChildren().clear();
|
|
||||||
positionVbox.getChildren().removeAll();
|
|
||||||
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
|
|
||||||
// list of racing boat id
|
|
||||||
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
|
||||||
.getParticipants();
|
|
||||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
|
||||||
for (Participant p : participants) {
|
|
||||||
participantIDs.add(p.getsourceID());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StreamParser.isRaceStarted()) {
|
|
||||||
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
|
||||||
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
|
||||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
|
||||||
boat.getShortName() + " (Finished)");
|
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
|
||||||
positionVbox.getChildren().add(textToAdd);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
|
||||||
boat.getShortName() + " ");
|
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
|
||||||
textToAdd.setStyle("");
|
|
||||||
positionVbox.getChildren().add(textToAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (Yacht boat : StreamParser.getBoats().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
|
||||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
|
||||||
boat.getShortName() + " ");
|
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
|
||||||
textToAdd.setStyle("");
|
|
||||||
positionVbox.getChildren().add(textToAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void updateLaylines(BoatGroup bg) {
|
|
||||||
|
|
||||||
Mark nextMark = getNextMark(bg);
|
|
||||||
Boolean isUpwind = null;
|
|
||||||
// Can only calc leg direction if there is a next mark and it is a gate mark
|
|
||||||
if (nextMark != null) {
|
|
||||||
if (nextMark instanceof GateMark) {
|
|
||||||
if (bg.isUpwindLeg(includedCanvasController, nextMark)) {
|
|
||||||
isUpwind = true;
|
|
||||||
} else {
|
|
||||||
isUpwind = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(MarkGroup mg : includedCanvasController.getMarkGroups()) {
|
|
||||||
|
|
||||||
mg.removeLaylines();
|
|
||||||
|
|
||||||
if (mg.getMainMark().getId() == nextMark.getId()) {
|
|
||||||
|
|
||||||
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
|
|
||||||
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
|
|
||||||
Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
|
|
||||||
Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
|
|
||||||
HashMap<Double, Double> angleAndSpeed;
|
|
||||||
if (isUpwind) {
|
|
||||||
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
|
|
||||||
} else {
|
|
||||||
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
|
|
||||||
}
|
|
||||||
|
|
||||||
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
|
||||||
|
|
||||||
|
|
||||||
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
|
||||||
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
|
||||||
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
|
||||||
Line rightLayline = new Line();
|
|
||||||
Line leftLayline = new Line();
|
|
||||||
if (lineFuncResult == 1) {
|
|
||||||
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
} else if (lineFuncResult == -1) {
|
|
||||||
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
leftLayline.setStrokeWidth(0.5);
|
|
||||||
leftLayline.setStroke(bg.getBoat().getColour());
|
|
||||||
|
|
||||||
rightLayline.setStrokeWidth(0.5);
|
|
||||||
rightLayline.setStroke(bg.getBoat().getColour());
|
|
||||||
|
|
||||||
bg.setLaylines(leftLayline, rightLayline);
|
|
||||||
mg.addLaylines(leftLayline, rightLayline);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
|
|
||||||
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
|
|
||||||
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
|
|
||||||
|
|
||||||
return new Point2D(newX, newY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
|
||||||
|
|
||||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
|
|
||||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
|
||||||
return line;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
|
||||||
|
|
||||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
|
|
||||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
|
||||||
return line;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialised the combo box with any boats currently in the race and adds the required listener
|
|
||||||
* for the combobox to take action upon selection
|
|
||||||
*/
|
|
||||||
private void initialiseBoatSelectionComboBox() {
|
|
||||||
updateBoatSelectionComboBox();
|
|
||||||
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
|
||||||
//This listener is fired whenever the combo box changes. This means when the values are updated
|
|
||||||
//We dont want to set the selected value if the values are updated but nothing clicked (null)
|
|
||||||
if (newValue != null && newValue != selectedBoat) {
|
|
||||||
Yacht thisYacht = (Yacht) newValue;
|
|
||||||
setSelectedBoat(thisYacht);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the list of boats in the order they finished the race
|
|
||||||
*/
|
|
||||||
private void loadRaceResultView() {
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTimeSinceStartOfRace() {
|
|
||||||
String timerString = "0:00";
|
|
||||||
if (StreamParser.getTimeSinceStart() > 0) {
|
|
||||||
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
timerString = "-" + timerMinute + ":" + timerSecond;
|
|
||||||
} else {
|
|
||||||
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
timerString = timerMinute + ":" + timerSecond;
|
|
||||||
}
|
|
||||||
return timerString;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
boolean isDisplayFps() {
|
|
||||||
return displayFps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the important annotations for a specific BoatGroup
|
|
||||||
* @param bg The boat group to set the annotations for
|
|
||||||
*/
|
|
||||||
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.NAME)) {
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setTeamNameObjectVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.SPEED)) {
|
|
||||||
bg.setVelocityObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.TRACK)) {
|
|
||||||
bg.setLineGroupVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.WAKE)) {
|
|
||||||
bg.setWakeVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
//TODO fix boat annotations with new boatgroup
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) {
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.LEGTIME)) {
|
|
||||||
bg.setLegTimeObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setLegTimeObjectVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAnnotations(Integer annotationLevel) {
|
|
||||||
switch (annotationLevel) {
|
|
||||||
// No Annotations
|
|
||||||
case 0:
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
bg.setTeamNameObjectVisible(false);
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(false);
|
|
||||||
bg.setLegTimeObjectVisible(false);
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Important Annotations
|
|
||||||
case 1:
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
setBoatGroupImportantAnnotations(bg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// All Annotations
|
|
||||||
case 2:
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
bg.setVelocityObjectVisible(true);
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(true);
|
|
||||||
bg.setLegTimeObjectVisible(true);
|
|
||||||
bg.setLineGroupVisible(true);
|
|
||||||
bg.setWakeVisible(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets all the annotations of the selected boat to be visible and all others to be hidden
|
|
||||||
*
|
|
||||||
* @param yacht The yacht for which we want to view all annotations
|
|
||||||
*/
|
|
||||||
private void setSelectedBoat(Yacht yacht) {
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
|
|
||||||
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
|
|
||||||
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
|
|
||||||
updateLaylines(bg);
|
|
||||||
bg.setIsSelected(true);
|
|
||||||
selectedBoat = yacht;
|
|
||||||
} else {
|
|
||||||
bg.setIsSelected(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStage(Stage stage) {
|
|
||||||
this.stage = stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stage getStage() {
|
|
||||||
return stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
|
|
||||||
* @param yachtId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean sparkLineStatus(Integer yachtId) {
|
|
||||||
return sparkLineData.containsKey(yachtId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
|
||||||
|
|
||||||
public class StartScreenController implements Initializable {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private GridPane gridPane;
|
|
||||||
@FXML
|
|
||||||
private Label timeTillLive;
|
|
||||||
@FXML
|
|
||||||
private Button streamButton;
|
|
||||||
@FXML
|
|
||||||
private Button switchToRaceViewButton;
|
|
||||||
@FXML
|
|
||||||
private TableView<Yacht> teamList;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> boatNameCol;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> shortNameCol;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> countryCol;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> posCol;
|
|
||||||
@FXML
|
|
||||||
private Label realTime;
|
|
||||||
|
|
||||||
private boolean switchedToRaceView = false;
|
|
||||||
|
|
||||||
private void setContentPane(String jfxUrl) {
|
|
||||||
try {
|
|
||||||
// get the main controller anchor pane (MainView.fxml)
|
|
||||||
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
|
|
||||||
contentPane.getChildren().removeAll();
|
|
||||||
contentPane.getChildren().clear();
|
|
||||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
contentPane.getChildren()
|
|
||||||
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
|
||||||
} catch (javafx.fxml.LoadException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Running a timer to update the livestream status on welcome screen. Update interval is 1
|
|
||||||
* second.
|
|
||||||
*/
|
|
||||||
public void startStream() {
|
|
||||||
if (StreamParser.isStreamStatus()) {
|
|
||||||
streamButton.setVisible(false);
|
|
||||||
realTime.setVisible(true);
|
|
||||||
timeTillLive.setVisible(true);
|
|
||||||
timeTillLive.setTextFill(Color.GREEN);
|
|
||||||
timeTillLive.setText("Connecting...");
|
|
||||||
Timer timer = new Timer();
|
|
||||||
timer.scheduleAtFixedRate(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (StreamParser.isRaceStarted()) {
|
|
||||||
if (!switchedToRaceView) {
|
|
||||||
switchToRaceView();
|
|
||||||
}
|
|
||||||
timer.cancel();
|
|
||||||
}
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
realTime.setText(StreamParser.getCurrentTimeString());
|
|
||||||
timeTillLive.setTextFill(Color.RED);
|
|
||||||
timeTillLive.setText("Race finished! Waiting for new race...");
|
|
||||||
switchToRaceViewButton.setDisable(true);
|
|
||||||
} else if (StreamParser.getTimeSinceStart() > 0) {
|
|
||||||
realTime.setText(StreamParser.getCurrentTimeString());
|
|
||||||
updateTeamList();
|
|
||||||
timeTillLive.setTextFill(Color.RED);
|
|
||||||
switchToRaceViewButton.setDisable(false);
|
|
||||||
String timerMinute = Long
|
|
||||||
.toString(StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long
|
|
||||||
.toString(StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
String timerString = "-" + timerMinute + ":" + timerSecond;
|
|
||||||
timeTillLive.setText(timerString);
|
|
||||||
} else {
|
|
||||||
realTime.setText(StreamParser.getCurrentTimeString());
|
|
||||||
updateTeamList();
|
|
||||||
timeTillLive.setTextFill(Color.BLACK);
|
|
||||||
switchToRaceViewButton.setDisable(false);
|
|
||||||
String timerMinute = Long
|
|
||||||
.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long
|
|
||||||
.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
String timerString = timerMinute + ":" + timerSecond;
|
|
||||||
timeTillLive.setText(timerString);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 0, 1000);
|
|
||||||
} else {
|
|
||||||
timeTillLive.setText("Stream not available.");
|
|
||||||
timeTillLive.setTextFill(Color.RED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void switchToRaceView() {
|
|
||||||
StreamParser.boatLocations.clear();
|
|
||||||
switchedToRaceView = true;
|
|
||||||
setContentPane("/views/RaceView.fxml");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTeamList() {
|
|
||||||
ObservableList<Yacht> data = FXCollections.observableArrayList();
|
|
||||||
|
|
||||||
teamList.setItems(data);
|
|
||||||
|
|
||||||
boatNameCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("boatName")
|
|
||||||
);
|
|
||||||
shortNameCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("shortName")
|
|
||||||
);
|
|
||||||
countryCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("country")
|
|
||||||
);
|
|
||||||
posCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("position")
|
|
||||||
);
|
|
||||||
|
|
||||||
// check if the boat is racing
|
|
||||||
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
|
||||||
.getParticipants();
|
|
||||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
|
||||||
for (Participant p : participants) {
|
|
||||||
participantIDs.add(p.getsourceID());
|
|
||||||
}
|
|
||||||
|
|
||||||
// add boats to the start screen list
|
|
||||||
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
|
|
||||||
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) {
|
|
||||||
data.add(boat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // else use StreamParser.getBoats()
|
|
||||||
for (Yacht boat : StreamParser.getBoats().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) {
|
|
||||||
data.add(boat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
teamList.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.model.Player;
|
||||||
|
|
||||||
|
public interface ClientConnectionDelegate {
|
||||||
|
/**
|
||||||
|
* A player has connected to the server
|
||||||
|
* @param serverToClientThread The player that has connected
|
||||||
|
*/
|
||||||
|
void clientConnected(ServerToClientThread serverToClientThread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player has disconnected from the server
|
||||||
|
* @param player The player that has disconnected
|
||||||
|
*/
|
||||||
|
void clientDisconnected(Player player);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum describing the states of the game
|
||||||
|
* Created by wmu16 on 11/07/17.
|
||||||
|
*/
|
||||||
|
public enum GameStages {
|
||||||
|
|
||||||
|
LOBBYING(0),
|
||||||
|
PRE_RACE(1),
|
||||||
|
RACING(2),
|
||||||
|
FINISHED(3),
|
||||||
|
CANCELLED(4);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
GameStages(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,702 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import seng302.gameServer.messages.BoatAction;
|
||||||
|
import seng302.gameServer.messages.BoatStatus;
|
||||||
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
|
import seng302.gameServer.messages.MarkRoundingMessage;
|
||||||
|
import seng302.gameServer.messages.MarkType;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RoundingBoatStatus;
|
||||||
|
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.model.Limit;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.PolarTable;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.mark.CompoundMark;
|
||||||
|
import seng302.model.mark.Mark;
|
||||||
|
import seng302.model.mark.MarkOrder;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
import seng302.utilities.XMLParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Static class to hold information about the current state of the game (model)
|
||||||
|
* Also contains logic for updating itself on regular time intervals on its own thread
|
||||||
|
* Created by wmu16 on 10/07/17.
|
||||||
|
*/
|
||||||
|
public class GameState implements Runnable {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface NewMessageListener {
|
||||||
|
|
||||||
|
void notify(Message message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||||
|
|
||||||
|
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
||||||
|
public static Integer MAX_PLAYERS = 8;
|
||||||
|
public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
||||||
|
public static final Double MARK_COLLISION_DISTANCE = 15d;
|
||||||
|
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
|
||||||
|
public static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
||||||
|
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
|
||||||
|
public static final Double COLLISION_VELOCITY_PENALTY = 0.3;
|
||||||
|
|
||||||
|
private static Long previousUpdateTime;
|
||||||
|
public static Double windDirection;
|
||||||
|
private static Double windSpeed;
|
||||||
|
|
||||||
|
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
|
||||||
|
|
||||||
|
private static String hostIpAddress;
|
||||||
|
private static List<Player> players;
|
||||||
|
private static Map<Integer, ServerYacht> yachts;
|
||||||
|
private static Boolean isRaceStarted;
|
||||||
|
private static GameStages currentStage;
|
||||||
|
private static MarkOrder markOrder;
|
||||||
|
private static long startTime;
|
||||||
|
private static Set<Mark> marks;
|
||||||
|
private static List<Limit> courseLimit;
|
||||||
|
|
||||||
|
private static List<NewMessageListener> markListeners;
|
||||||
|
|
||||||
|
private static Map<Player, String> playerStringMap = new HashMap<>();
|
||||||
|
/*
|
||||||
|
Ideally I would like to make this class an object instantiated by the server and given to
|
||||||
|
it's created threads if necessary. Outside of that I think the dependencies on it
|
||||||
|
(atm only Yacht & GameClient) can be removed from most other classes. The observable list of
|
||||||
|
players could be pulled directly from the server by the GameClient since it instantiates it
|
||||||
|
and it is reasonable for it to pull data. The current setup of publicly available statics is
|
||||||
|
pretty meh IMO because anything can change it making it unreliable and like people did with
|
||||||
|
the old ServerParser class everything that needs shared just gets thrown in the static
|
||||||
|
collections and things become a real mess.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public GameState(String hostIpAddress) {
|
||||||
|
windDirection = 180d;
|
||||||
|
windSpeed = 10000d;
|
||||||
|
this.hostIpAddress = hostIpAddress;
|
||||||
|
yachts = new HashMap<>();
|
||||||
|
players = new ArrayList<>();
|
||||||
|
GameState.hostIpAddress = hostIpAddress;
|
||||||
|
customizationFlag = false;
|
||||||
|
|
||||||
|
currentStage = GameStages.LOBBYING;
|
||||||
|
isRaceStarted = false;
|
||||||
|
//set this when game stage changes to prerace
|
||||||
|
previousUpdateTime = System.currentTimeMillis();
|
||||||
|
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
|
||||||
|
markListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
resetStartTime();
|
||||||
|
|
||||||
|
new Thread(this, "GameState").start(); //Run the auto updates on the game state
|
||||||
|
|
||||||
|
marks = new MarkOrder().getAllMarks();
|
||||||
|
setCourseLimit("/server_config/race.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCourseLimit(String url) {
|
||||||
|
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
documentBuilderFactory.setNamespaceAware(true);
|
||||||
|
DocumentBuilder documentBuilder;
|
||||||
|
Document document = null;
|
||||||
|
try {
|
||||||
|
documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||||
|
document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// sorry, we have to catch general one, otherwise we have to catch five different exceptions.
|
||||||
|
logger.trace("Failed to load course limit for boundary collision detection.", e);
|
||||||
|
}
|
||||||
|
courseLimit = XMLParser.parseRace(document).getCourseLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHostIpAddress() {
|
||||||
|
return hostIpAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Mark> getMarks() {
|
||||||
|
return Collections.unmodifiableSet(marks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Player> getPlayers() {
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addPlayer(Player player) {
|
||||||
|
players.add(player);
|
||||||
|
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
|
||||||
|
+ " " + player.getYacht().getCountry();
|
||||||
|
playerStringMap.put(player, playerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removePlayer(Player player) {
|
||||||
|
players.remove(player);
|
||||||
|
playerStringMap.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addYacht(Integer sourceId, ServerYacht yacht) {
|
||||||
|
yachts.put(sourceId, yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeYacht(Integer yachtId) {
|
||||||
|
yachts.remove(yachtId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean getIsRaceStarted() {
|
||||||
|
return isRaceStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameStages getCurrentStage() {
|
||||||
|
return currentStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCurrentStage(GameStages currentStage) {
|
||||||
|
GameState.currentStage = currentStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MarkOrder getMarkOrder() {
|
||||||
|
return markOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetStartTime(){
|
||||||
|
startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setWindDirection(Double newWindDirection) {
|
||||||
|
windDirection = newWindDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setWindSpeed(Double newWindSpeed) {
|
||||||
|
windSpeed = newWindSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double getWindSpeedMMS() {
|
||||||
|
return windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double getWindSpeedKnots() {
|
||||||
|
return GeoUtility.mmsToKnots(windSpeed); // TODO: 26/07/17 cir27 - remove magic numbers
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<Integer, ServerYacht> getYachts() {
|
||||||
|
return yachts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new ID based off the size of current players + 1
|
||||||
|
*
|
||||||
|
* @return a playerID to be allocated to a new connetion
|
||||||
|
*/
|
||||||
|
public static Integer getUniquePlayerID() {
|
||||||
|
// TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on.
|
||||||
|
return yachts.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread to have the game state update itself at certain intervals
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
while (currentStage != GameStages.FINISHED) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("[GameState] interrupted exception");
|
||||||
|
}
|
||||||
|
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStage == GameStages.RACING) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateBoat(Integer sourceId, BoatAction actionType) {
|
||||||
|
ServerYacht playerYacht = yachts.get(sourceId);
|
||||||
|
switch (actionType) {
|
||||||
|
case VMG:
|
||||||
|
playerYacht.turnToVMG();
|
||||||
|
break;
|
||||||
|
case SAILS_IN:
|
||||||
|
playerYacht.toggleSailIn();
|
||||||
|
break;
|
||||||
|
case SAILS_OUT:
|
||||||
|
playerYacht.toggleSailIn();
|
||||||
|
break;
|
||||||
|
case TACK_GYBE:
|
||||||
|
playerYacht.tackGybe(windDirection);
|
||||||
|
break;
|
||||||
|
case UPWIND:
|
||||||
|
playerYacht.turnUpwind();
|
||||||
|
break;
|
||||||
|
case DOWNWIND:
|
||||||
|
playerYacht.turnDownwind();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called periodically in this GameState thread to update the GameState values
|
||||||
|
*/
|
||||||
|
public void update() {
|
||||||
|
Boolean raceFinished = true;
|
||||||
|
|
||||||
|
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
|
||||||
|
previousUpdateTime = System.currentTimeMillis();
|
||||||
|
if (System.currentTimeMillis() > startTime) {
|
||||||
|
GameState.setCurrentStage(GameStages.RACING);
|
||||||
|
}
|
||||||
|
for (ServerYacht yacht : yachts.values()) {
|
||||||
|
updateVelocity(yacht);
|
||||||
|
yacht.runAutoPilot();
|
||||||
|
yacht.updateLocation(timeInterval);
|
||||||
|
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||||
|
checkCollision(yacht);
|
||||||
|
checkForLegProgression(yacht);
|
||||||
|
raceFinished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raceFinished) {
|
||||||
|
currentStage = GameStages.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the yacht has crossed the course limit
|
||||||
|
*
|
||||||
|
* @param yacht the yacht to be tested
|
||||||
|
* @return a boolean value of if there is a boundary collision
|
||||||
|
*/
|
||||||
|
private static Boolean checkBoundaryCollision(ServerYacht yacht) {
|
||||||
|
for (int i = 0; i < courseLimit.size() - 1; i++) {
|
||||||
|
if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1),
|
||||||
|
yacht.getLastLocation(), yacht.getLocation()) != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
|
||||||
|
yacht.getLastLocation(), yacht.getLocation()) != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkCollision(ServerYacht serverYacht) {
|
||||||
|
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
|
||||||
|
if (collidedYacht != null) {
|
||||||
|
GeoPoint originalLocation = serverYacht.getLocation();
|
||||||
|
serverYacht.setLocation(
|
||||||
|
calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
|
||||||
|
);
|
||||||
|
serverYacht.setCurrentVelocity(
|
||||||
|
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
|
);
|
||||||
|
collidedYacht.setLocation(
|
||||||
|
calculateBounceBack(collidedYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
|
||||||
|
);
|
||||||
|
collidedYacht.setCurrentVelocity(
|
||||||
|
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
|
);
|
||||||
|
notifyMessageListeners(
|
||||||
|
new YachtEventCodeMessage(serverYacht.getSourceId())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Mark collidedMark = checkMarkCollision(serverYacht);
|
||||||
|
if (collidedMark != null) {
|
||||||
|
serverYacht.setLocation(
|
||||||
|
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
|
||||||
|
);
|
||||||
|
serverYacht.setCurrentVelocity(
|
||||||
|
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
|
);
|
||||||
|
notifyMessageListeners(
|
||||||
|
new YachtEventCodeMessage(serverYacht.getSourceId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (checkBoundaryCollision(serverYacht)) {
|
||||||
|
serverYacht.setLocation(
|
||||||
|
calculateBounceBack(serverYacht, serverYacht.getLocation(),
|
||||||
|
BOUNCE_DISTANCE_YACHT)
|
||||||
|
);
|
||||||
|
serverYacht.setCurrentVelocity(
|
||||||
|
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
|
);
|
||||||
|
notifyMessageListeners(
|
||||||
|
new YachtEventCodeMessage(serverYacht.getSourceId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateVelocity(ServerYacht yacht) {
|
||||||
|
Double velocity = yacht.getCurrentVelocity();
|
||||||
|
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
|
||||||
|
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
|
||||||
|
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
|
||||||
|
// TODO: 15/08/17 remove magic numbers from these equations.
|
||||||
|
if (yacht.getSailIn()) {
|
||||||
|
if (velocity < maxBoatSpeed - 500) {
|
||||||
|
yacht.changeVelocity(maxBoatSpeed / 100);
|
||||||
|
} else if (velocity > maxBoatSpeed + 500) {
|
||||||
|
yacht.changeVelocity(-velocity / 200);
|
||||||
|
} else {
|
||||||
|
yacht.setCurrentVelocity(maxBoatSpeed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (velocity > 3000) {
|
||||||
|
yacht.changeVelocity(-velocity / 200);
|
||||||
|
} else if (velocity > 100) {
|
||||||
|
yacht.changeVelocity(-velocity / 50);
|
||||||
|
} else if (velocity <= 100) {
|
||||||
|
yacht.setCurrentVelocity(0d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes of
|
||||||
|
* mark rounding
|
||||||
|
*
|
||||||
|
* @return A distance in metres. Returns -1 if there is no next mark
|
||||||
|
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
|
||||||
|
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
|
||||||
|
*/
|
||||||
|
private Double calcDistanceToCurrentMark(ServerYacht yacht) throws IndexOutOfBoundsException {
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
GeoPoint location = yacht.getLocation();
|
||||||
|
|
||||||
|
if (currentMark.isGate()) {
|
||||||
|
Mark sub1 = currentMark.getSubMark(1);
|
||||||
|
Mark sub2 = currentMark.getSubMark(2);
|
||||||
|
Double distance1 = GeoUtility.getDistance(location, sub1);
|
||||||
|
Double distance2 = GeoUtility.getDistance(location, sub2);
|
||||||
|
if (distance1 < distance2) {
|
||||||
|
yacht.setClosestCurrentMark(sub1);
|
||||||
|
return distance1;
|
||||||
|
} else {
|
||||||
|
yacht.setClosestCurrentMark(sub2);
|
||||||
|
return distance2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yacht.setClosestCurrentMark(currentMark.getSubMark(1));
|
||||||
|
return GeoUtility.getDistance(location, currentMark.getSubMark(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** lobbyController.setPlayerListSource(clientLobbyList);
|
||||||
|
* 4 Different cases of progression in the race 1 - Passing the start line 2 - Passing any
|
||||||
|
* in-race Gate 3 - Passing any in-race Mark 4 - Passing the finish line
|
||||||
|
*
|
||||||
|
* @param yacht the current yacht to check for progression
|
||||||
|
*/
|
||||||
|
private void checkForLegProgression(ServerYacht yacht) {
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Boolean hasProgressed;
|
||||||
|
if (currentMarkSeqID == 0) {
|
||||||
|
hasProgressed = checkStartLineCrossing(yacht);
|
||||||
|
} else if (markOrder.isLastMark(currentMarkSeqID)) {
|
||||||
|
hasProgressed = checkFinishLineCrossing(yacht);
|
||||||
|
} else if (currentMark.isGate()) {
|
||||||
|
hasProgressed = checkGateRounding(yacht);
|
||||||
|
} else {
|
||||||
|
hasProgressed = checkMarkRounding(yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasProgressed) {
|
||||||
|
yacht.incrementLegNumber();
|
||||||
|
sendMarkRoundingMessage(yacht);
|
||||||
|
logMarkRounding(yacht);
|
||||||
|
yacht.setHasPassedLine(false);
|
||||||
|
yacht.setHasEnteredRoundingZone(false);
|
||||||
|
yacht.setHasPassedThroughGate(false);
|
||||||
|
if (!markOrder.isLastMark(currentMarkSeqID)) {
|
||||||
|
yacht.incrementMarkSeqID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we pass the start line gate in the correct direction, progress
|
||||||
|
*
|
||||||
|
* @param yacht The current yacht to check for
|
||||||
|
*/
|
||||||
|
private Boolean checkStartLineCrossing(ServerYacht yacht) {
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
GeoPoint lastLocation = yacht.getLastLocation();
|
||||||
|
GeoPoint location = yacht.getLocation();
|
||||||
|
|
||||||
|
Mark mark1 = currentMark.getSubMark(1);
|
||||||
|
Mark mark2 = currentMark.getSubMark(2);
|
||||||
|
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||||
|
if (crossedLine > 0) {
|
||||||
|
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
|
||||||
|
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
|
||||||
|
yacht.setClosestCurrentMark(mark1);
|
||||||
|
yacht.setBoatStatus(BoatStatus.RACING);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
|
||||||
|
* of the yacht if so. A visual representation of this algorithm can be seen on the Wiki under
|
||||||
|
* 'mark passing algorithm'
|
||||||
|
*
|
||||||
|
* @param yacht The current yacht to check for
|
||||||
|
*/
|
||||||
|
private Boolean checkMarkRounding(ServerYacht yacht) {
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
GeoPoint lastLocation = yacht.getLastLocation();
|
||||||
|
GeoPoint location = yacht.getLocation();
|
||||||
|
GeoPoint nextPoint = markOrder.getNextMark(currentMarkSeqID).getMidPoint();
|
||||||
|
GeoPoint prevPoint = markOrder.getPreviousMark(currentMarkSeqID).getMidPoint();
|
||||||
|
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
|
||||||
|
|
||||||
|
if (calcDistanceToCurrentMark(yacht) < ROUNDING_DISTANCE) {
|
||||||
|
yacht.setHasEnteredRoundingZone(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//In case current mark is a gate, loop through all marks just in case
|
||||||
|
for (Mark thisCurrentMark : currentMark.getMarks()) {
|
||||||
|
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
|
||||||
|
yacht.setHasPassedLine(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return yacht.hasPassedLine() && yacht.hasEnteredRoundingZone();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a gate line has been crossed and in the correct direction
|
||||||
|
*
|
||||||
|
* @param yacht The current yacht to check for
|
||||||
|
*/
|
||||||
|
private Boolean checkGateRounding(ServerYacht yacht) {
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
GeoPoint lastLocation = yacht.getLastLocation();
|
||||||
|
GeoPoint location = yacht.getLocation();
|
||||||
|
|
||||||
|
Mark mark1 = currentMark.getSubMark(1);
|
||||||
|
Mark mark2 = currentMark.getSubMark(2);
|
||||||
|
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
|
||||||
|
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||||
|
|
||||||
|
//We have crossed the line
|
||||||
|
if (crossedLine > 0) {
|
||||||
|
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||||
|
|
||||||
|
//Check we cross the line in the correct direction
|
||||||
|
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||||
|
yacht.setHasPassedThroughGate(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||||
|
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
|
||||||
|
|
||||||
|
if (yacht.hasPassedThroughGate()) {
|
||||||
|
//Check if we need to round this gate after passing through
|
||||||
|
if (prevMarkSide == nextMarkSide) {
|
||||||
|
return checkMarkRounding(yacht);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we pass the finish gate in the correct direction
|
||||||
|
*
|
||||||
|
* @param yacht The current yacht to check for
|
||||||
|
*/
|
||||||
|
private Boolean checkFinishLineCrossing(ServerYacht yacht) {
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
GeoPoint lastLocation = yacht.getLastLocation();
|
||||||
|
GeoPoint location = yacht.getLocation();
|
||||||
|
|
||||||
|
Mark mark1 = currentMark.getSubMark(1);
|
||||||
|
Mark mark2 = currentMark.getSubMark(2);
|
||||||
|
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
|
||||||
|
|
||||||
|
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||||
|
if (crossedLine > 0) {
|
||||||
|
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||||
|
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||||
|
yacht.setClosestCurrentMark(mark1);
|
||||||
|
yacht.setBoatStatus(BoatStatus.FINISHED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles player customization.
|
||||||
|
*
|
||||||
|
* @param playerID The ID of the player being modified.
|
||||||
|
* @param requestType the type of player customization the player wants
|
||||||
|
* @param customizeData the data related to the customization (color, name, shape)
|
||||||
|
*/
|
||||||
|
public static void customizePlayer(long playerID, CustomizeRequestType requestType,
|
||||||
|
byte[] customizeData) {
|
||||||
|
ServerYacht playerYacht = yachts.get((int) playerID);
|
||||||
|
|
||||||
|
if (requestType.equals(CustomizeRequestType.NAME)) {
|
||||||
|
String name = new String(customizeData);
|
||||||
|
playerYacht.setBoatName(name);
|
||||||
|
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
|
||||||
|
int red = customizeData[0] & 0xFF;
|
||||||
|
int green = customizeData[1] & 0xFF;
|
||||||
|
int blue = customizeData[2] & 0xFF;
|
||||||
|
Color yachtColor = Color.rgb(red, green, blue);
|
||||||
|
playerYacht.setBoatColor(yachtColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mark checkMarkCollision(ServerYacht yacht) {
|
||||||
|
Set<Mark> marksInRace = GameState.getMarks();
|
||||||
|
for (Mark mark : marksInRace) {
|
||||||
|
if (GeoUtility.getDistance(yacht.getLocation(), mark)
|
||||||
|
<= MARK_COLLISION_DISTANCE) {
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the new position of the boat after it has had a collision
|
||||||
|
*
|
||||||
|
* @return The boats new position
|
||||||
|
*/
|
||||||
|
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith,
|
||||||
|
Double bounceDistance) {
|
||||||
|
Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith);
|
||||||
|
// Invert heading
|
||||||
|
heading -= 180;
|
||||||
|
Integer newHeading = Math.floorMod(heading.intValue(), 360);
|
||||||
|
return GeoUtility
|
||||||
|
.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collision detection which iterates through all the yachts and check if any yacht collided
|
||||||
|
* with this yacht. Return collided yacht or null if no collision.
|
||||||
|
*
|
||||||
|
* @return yacht to compare to all other yachts.
|
||||||
|
*/
|
||||||
|
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
|
||||||
|
|
||||||
|
for (ServerYacht otherYacht : GameState.getYachts().values()) {
|
||||||
|
if (otherYacht != yacht) {
|
||||||
|
Double distance = GeoUtility
|
||||||
|
.getDistance(otherYacht.getLocation(), yacht.getLocation());
|
||||||
|
if (distance < YACHT_COLLISION_DISTANCE) {
|
||||||
|
return otherYacht;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMarkRoundingMessage(ServerYacht yacht) {
|
||||||
|
Integer sourceID = yacht.getSourceId();
|
||||||
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
|
MarkType markType = (currentMark.isGate()) ? MarkType.GATE : MarkType.ROUNDING_MARK;
|
||||||
|
Mark roundingMark = yacht.getClosestCurrentMark();
|
||||||
|
|
||||||
|
// TODO: 13/8/17 figure out the rounding side, rounded mark source ID and boat status.
|
||||||
|
Message markRoundingMessage = new MarkRoundingMessage(0, 0,
|
||||||
|
sourceID, RoundingBoatStatus.RACING, roundingMark.getRoundingSide(), markType,
|
||||||
|
currentMarkSeqID + 1);
|
||||||
|
|
||||||
|
notifyMessageListeners(markRoundingMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void notifyMessageListeners(Message message) {
|
||||||
|
for (NewMessageListener mpl : markListeners) {
|
||||||
|
mpl.notify(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logMarkRounding(ServerYacht yacht) {
|
||||||
|
Mark roundingMark = yacht.getClosestCurrentMark();
|
||||||
|
logger.debug(
|
||||||
|
String.format("Yacht srcID(%d) passed Mark srcID(%d)", yacht.getSourceId(),
|
||||||
|
roundingMark.getSourceID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void addMarkPassListener(NewMessageListener listener) {
|
||||||
|
markListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCustomizationFlag() {
|
||||||
|
customizationFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean getCustomizationFlag() {
|
||||||
|
return customizationFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetCustomizationFlag() {
|
||||||
|
customizationFlag = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.gameServer.messages.Heartbeat;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send Heartbeat messages to connected player at a specified interval
|
||||||
|
* Will call .clientDisconnected on the delegate when a heartbeat message
|
||||||
|
* cannot be sent to a player
|
||||||
|
*/
|
||||||
|
public class HeartbeatThread implements Runnable {
|
||||||
|
private final int HEARTBEAT_PERIOD = 200;
|
||||||
|
private ClientConnectionDelegate delegate;
|
||||||
|
private Integer seqNum;
|
||||||
|
private Stack<Player> disconnectedPlayers;
|
||||||
|
|
||||||
|
public HeartbeatThread(ClientConnectionDelegate delegate){
|
||||||
|
this.delegate = delegate;
|
||||||
|
seqNum = 0;
|
||||||
|
disconnectedPlayers = new Stack<>();
|
||||||
|
|
||||||
|
Thread thread = new Thread(this, "HeartBeat");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player has lost connection to the server
|
||||||
|
* The player is added to a stack so that the delegate
|
||||||
|
* can be notified
|
||||||
|
*
|
||||||
|
* @param player The player that has disconnected
|
||||||
|
*/
|
||||||
|
private void playerLostConnection(Player player){
|
||||||
|
disconnectedPlayers.push(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a heartbeat message to each connected player
|
||||||
|
* The delegate is notified if a player has disconnected
|
||||||
|
*/
|
||||||
|
private void sendHeartbeatToAllPlayers(){
|
||||||
|
Message heartbeat = new Heartbeat(seqNum);
|
||||||
|
for (Player player : GameState.getPlayers()){
|
||||||
|
if (!player.getSocket().isConnected()) {
|
||||||
|
playerLostConnection(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
|
||||||
|
} catch (IOException e) {
|
||||||
|
playerLostConnection(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDelegate();
|
||||||
|
seqNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the delegate about
|
||||||
|
* each disconnected player
|
||||||
|
*/
|
||||||
|
private void updateDelegate() {
|
||||||
|
while (!disconnectedPlayers.empty()){
|
||||||
|
delegate.clientDisconnected(disconnectedPlayers.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(){
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sendHeartbeatToAllPlayers();
|
||||||
|
}
|
||||||
|
}, 0, HEARTBEAT_PERIOD);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.gameServer.messages.*;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.PolarTable;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.mark.CompoundMark;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing the overall server, which creates and collects server threads for each client
|
||||||
|
* Created by wmu16 on 13/07/17.
|
||||||
|
*/
|
||||||
|
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||||
|
|
||||||
|
private static final int PORT = 4942;
|
||||||
|
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||||
|
private static final int LOG_LEVEL = 1;
|
||||||
|
private static final int WARNING_TIME = 10 * -1000;
|
||||||
|
private static final int PREPATORY_TIME = 5 * -1000;
|
||||||
|
public static final int TIME_TILL_START = 10 * 1000;
|
||||||
|
|
||||||
|
private static final int MAX_WIND_SPEED = 12000;
|
||||||
|
private static final int MIN_WIND_SPEED = 8000;
|
||||||
|
|
||||||
|
public static int windSpeed = 1000;
|
||||||
|
|
||||||
|
private boolean terminated;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private ServerSocket serverSocket = null;
|
||||||
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||||
|
|
||||||
|
public MainServerThread() {
|
||||||
|
new GameState("localhost");
|
||||||
|
try {
|
||||||
|
serverSocket = new ServerSocket(PORT);
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
|
||||||
|
}
|
||||||
|
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
||||||
|
GameState.addMarkPassListener(this::broadcastMessage);
|
||||||
|
terminated = false;
|
||||||
|
thread = new Thread(this, "MainServer");
|
||||||
|
startUpdatingWind();
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
new HeartbeatThread(this);
|
||||||
|
new ServerListenThread(serverSocket, this);
|
||||||
|
|
||||||
|
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
||||||
|
while (!terminated) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
|
||||||
|
}
|
||||||
|
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
|
||||||
|
.getCustomizationFlag()) {
|
||||||
|
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
|
||||||
|
for (ServerToClientThread thread : serverToClientThreads) {
|
||||||
|
thread.sendSetupMessages();
|
||||||
|
}
|
||||||
|
GameState.resetCustomizationFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
updateClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
//RACING
|
||||||
|
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||||
|
updateClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
//FINISHED
|
||||||
|
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
|
||||||
|
try {
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
serverToClientThread.terminate();
|
||||||
|
}
|
||||||
|
serverSocket.close();
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("IO error in server thread handler upon closing socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateClients() {
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
serverToClientThread.sendBoatLocationPackets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastMessage(Message message) {
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
serverToClientThread.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateWind(){
|
||||||
|
Integer direction = GameState.getWindDirection().intValue();
|
||||||
|
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
if (Math.floorMod(random.nextInt(), 2) == 0){
|
||||||
|
direction += random.nextInt(4);
|
||||||
|
windSpeed += random.nextInt(20) + 50;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
direction -= random.nextInt(4);
|
||||||
|
windSpeed -= random.nextInt(20) + 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
direction = Math.floorMod(direction, 360);
|
||||||
|
|
||||||
|
if (windSpeed > MAX_WIND_SPEED){
|
||||||
|
windSpeed -= random.nextInt(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windSpeed <= MIN_WIND_SPEED){
|
||||||
|
windSpeed += random.nextInt(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameState.setWindSpeed(Double.valueOf(windSpeed));
|
||||||
|
GameState.setWindDirection(direction.doubleValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startUpdatingWind(){
|
||||||
|
Timer timer = new Timer();
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateWind();
|
||||||
|
}
|
||||||
|
}, 0, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void serverLog(String message, int logLevel) {
|
||||||
|
if (logLevel <= LOG_LEVEL) {
|
||||||
|
System.out.println(
|
||||||
|
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client has tried to connect to the server
|
||||||
|
*
|
||||||
|
* @param serverToClientThread The player that connected
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clientConnected(ServerToClientThread serverToClientThread) {
|
||||||
|
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||||
|
serverToClientThreads.add(serverToClientThread);
|
||||||
|
serverToClientThread.addConnectionListener(() -> {
|
||||||
|
for (ServerToClientThread thread : serverToClientThreads) {
|
||||||
|
thread.sendSetupMessages();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player has left the game, remove the player from the GameState
|
||||||
|
*
|
||||||
|
* @param player The player that left
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clientDisconnected(Player player) {
|
||||||
|
// try {
|
||||||
|
// player.getSocket().close();
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// serverLog("Cannot disconnect the socket for the disconnected player.", 0);
|
||||||
|
// }
|
||||||
|
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
||||||
|
GameState.removeYacht(player.getYacht().getSourceId());
|
||||||
|
GameState.removePlayer(player);
|
||||||
|
ServerToClientThread closedConnection = null;
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
if (serverToClientThread.getSocket() == player.getSocket()) {
|
||||||
|
closedConnection = serverToClientThread;
|
||||||
|
} else if (GameState.getCurrentStage() != GameStages.RACING){
|
||||||
|
serverToClientThread.sendSetupMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serverToClientThreads.remove(closedConnection);
|
||||||
|
closedConnection.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startGame() {
|
||||||
|
initialiseBoatPositions();
|
||||||
|
Timer t = new Timer();
|
||||||
|
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
broadcastMessage(makeRaceStatusMessage());
|
||||||
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
broadcastMessage(makeRaceStartMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private RaceStartStatusMessage makeRaceStartMessage() {
|
||||||
|
Long raceStartTime = GameState.getStartTime();
|
||||||
|
|
||||||
|
return new RaceStartStatusMessage(1, raceStartTime ,
|
||||||
|
1, RaceStartNotificationType.SET_RACE_START_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RaceStatusMessage makeRaceStatusMessage() {
|
||||||
|
// variables taken from GameServerThread
|
||||||
|
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
|
||||||
|
for (Player player : GameState.getPlayers()) {
|
||||||
|
ServerYacht y = player.getYacht();
|
||||||
|
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
||||||
|
y.getLegNumber(),
|
||||||
|
0, 0, 1234L,
|
||||||
|
1234L);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
|
||||||
|
if (timeTillStart > WARNING_TIME) {
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeTillStart > PREPATORY_TIME) {
|
||||||
|
raceStatus = RaceStatus.PREPARATORY;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
||||||
|
GameState.getWindDirection(),
|
||||||
|
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||||
|
RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
terminated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise boats to specific spaced out geopoints behind starting line.
|
||||||
|
*/
|
||||||
|
private void initialiseBoatPositions() {
|
||||||
|
// Getting the start line compound marks
|
||||||
|
// if (gameClient== null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
||||||
|
GeoPoint startMark1 = cm.getSubMark(1);
|
||||||
|
GeoPoint startMark2 = cm.getSubMark(2);
|
||||||
|
|
||||||
|
// Calculating midpoint
|
||||||
|
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
||||||
|
Double length = GeoUtility.getDistance(startMark1, startMark2);
|
||||||
|
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
||||||
|
|
||||||
|
// Setting each boats position side by side
|
||||||
|
double DISTANCE_FACTOR = 50.0; // distance apart in meters
|
||||||
|
int boatIndex = 0;
|
||||||
|
for (ServerYacht yacht : GameState.getYachts().values()) {
|
||||||
|
int distanceApart = boatIndex / 2;
|
||||||
|
|
||||||
|
if (boatIndex % 2 == 1 && boatIndex != 0) {
|
||||||
|
distanceApart++;
|
||||||
|
distanceApart *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeoPoint spawnMark = GeoUtility
|
||||||
|
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
|
||||||
|
|
||||||
|
if (yacht.getHeading() < perpendicularAngle) {
|
||||||
|
spawnMark = GeoUtility
|
||||||
|
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
|
||||||
|
} else {
|
||||||
|
spawnMark = GeoUtility
|
||||||
|
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
yacht.setLocation(spawnMark);
|
||||||
|
boatIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for a thread to listen to connections
|
||||||
|
* Created by wmu16 on 11/07/17.
|
||||||
|
*/
|
||||||
|
public class ServerListenThread implements Runnable {
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private ClientConnectionDelegate delegate;
|
||||||
|
|
||||||
|
public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
this.delegate = delegate;
|
||||||
|
|
||||||
|
Thread thread = new Thread(this, "ServerListen");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState
|
||||||
|
*/
|
||||||
|
private void acceptConnection() {
|
||||||
|
try {
|
||||||
|
Socket thisClient = serverSocket.accept();
|
||||||
|
if (thisClient != null && GameState.getCurrentStage().equals(GameStages.LOBBYING)) {
|
||||||
|
ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
|
||||||
|
delegate.clientConnected(thisConnection);
|
||||||
|
} else {
|
||||||
|
thisClient.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(){
|
||||||
|
while (serverSocket != null && !serverSocket.isClosed()){
|
||||||
|
acceptConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import seng302.gameServer.messages.BoatAction;
|
||||||
|
import seng302.gameServer.messages.ClientType;
|
||||||
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
|
|
||||||
|
|
||||||
|
public class ServerPacketParser {
|
||||||
|
|
||||||
|
public static BoatAction extractBoatAction(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||||
|
return BoatAction.getType((int) actionTypeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClientType extractClientType(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||||
|
return ClientType.getClientType((int) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomizeRequestType extractCustomizationType(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
|
||||||
|
return CustomizeRequestType.getRequestType((int) type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.Observer;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.BoatAction;
|
||||||
|
import seng302.gameServer.messages.BoatLocationMessage;
|
||||||
|
import seng302.gameServer.messages.ClientType;
|
||||||
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseMessage;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseStatus;
|
||||||
|
import seng302.gameServer.messages.XMLMessage;
|
||||||
|
import seng302.gameServer.messages.XMLMessageSubType;
|
||||||
|
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||||
|
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
|
import seng302.model.stream.xml.generator.Race;
|
||||||
|
import seng302.model.stream.xml.generator.Regatta;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
import seng302.gameServer.messages.BoatAction;
|
||||||
|
import seng302.gameServer.messages.BoatLocationMessage;
|
||||||
|
import seng302.gameServer.messages.ClientType;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseMessage;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseStatus;
|
||||||
|
import seng302.gameServer.messages.XMLMessage;
|
||||||
|
import seng302.gameServer.messages.XMLMessageSubType;
|
||||||
|
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
|
import seng302.model.stream.xml.generator.Race;
|
||||||
|
import seng302.model.stream.xml.generator.Regatta;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing a single connection to a Client for the purposes of sending and receiving on
|
||||||
|
* its own thread. All server threads created and owned by the server thread handler which can
|
||||||
|
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
||||||
|
*/
|
||||||
|
public class ServerToClientThread implements Runnable, Observer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to notify listeners when this thread receives a connection correctly.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ConnectionListener {
|
||||||
|
void notifyConnection ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 17/08/17 this is only temporary disconnects should be handled consistently
|
||||||
|
@FunctionalInterface
|
||||||
|
interface DisconnectListener {
|
||||||
|
void notifyDisconnect (Player player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class);
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private InputStream is;
|
||||||
|
private OutputStream os;
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
private ByteArrayOutputStream crcBuffer;
|
||||||
|
|
||||||
|
private Integer seqNo;
|
||||||
|
private Integer sourceId;
|
||||||
|
|
||||||
|
private ClientType clientType;
|
||||||
|
private Boolean isRegistered = false;
|
||||||
|
|
||||||
|
private XMLGenerator xml;
|
||||||
|
|
||||||
|
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
||||||
|
private DisconnectListener disconnectListener;
|
||||||
|
|
||||||
|
private ServerYacht yacht;
|
||||||
|
private Player player;
|
||||||
|
|
||||||
|
public ServerToClientThread(Socket socket) {
|
||||||
|
this.socket = socket;
|
||||||
|
seqNo = 0;
|
||||||
|
|
||||||
|
try{
|
||||||
|
is = socket.getInputStream();
|
||||||
|
os = socket.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = new Thread(this, "ServerToClient");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpPlayer(){
|
||||||
|
BufferedReader fn;
|
||||||
|
String fName = "";
|
||||||
|
BufferedReader ln;
|
||||||
|
String lName = "";
|
||||||
|
|
||||||
|
fn = new BufferedReader(
|
||||||
|
new InputStreamReader(
|
||||||
|
ServerToClientThread.class.getResourceAsStream(
|
||||||
|
"/server_config/CSV_Database_of_First_Names.csv"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
List<String> all = fn.lines().collect(Collectors.toList());
|
||||||
|
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||||
|
ln = new BufferedReader(
|
||||||
|
new InputStreamReader(
|
||||||
|
ServerToClientThread.class.getResourceAsStream(
|
||||||
|
"/server_config/CSV_Database_of_Last_Names.csv"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
all = ln.lines().collect(Collectors.toList());
|
||||||
|
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||||
|
|
||||||
|
ServerYacht yacht = new ServerYacht(
|
||||||
|
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||||
|
);
|
||||||
|
|
||||||
|
yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17
|
||||||
|
player = new Player(socket, yacht);
|
||||||
|
GameState.addYacht(sourceId, yacht);
|
||||||
|
GameState.addPlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
if (arg != null) {
|
||||||
|
sendMessage((Message) arg);
|
||||||
|
} else {
|
||||||
|
sendSetupMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void completeRegistration(ClientType clientType) throws IOException {
|
||||||
|
// Fail if not a player
|
||||||
|
if (!clientType.equals(ClientType.PLAYER)){
|
||||||
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL);
|
||||||
|
os.write(responseMessage.getBuffer());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){
|
||||||
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
|
||||||
|
os.write(responseMessage.getBuffer());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer sourceId = GameState.getUniquePlayerID();
|
||||||
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING);
|
||||||
|
|
||||||
|
this.clientType = clientType;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
isRegistered = true;
|
||||||
|
os.write(responseMessage.getBuffer());
|
||||||
|
|
||||||
|
setUpPlayer();
|
||||||
|
|
||||||
|
for (ConnectionListener listener : connectionListeners) {
|
||||||
|
listener.notifyConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
int sync1;
|
||||||
|
int sync2;
|
||||||
|
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||||
|
|
||||||
|
while (socket.isConnected() && !socket.isClosed()) {
|
||||||
|
try {
|
||||||
|
crcBuffer = new ByteArrayOutputStream();
|
||||||
|
sync1 = readByte();
|
||||||
|
sync2 = readByte();
|
||||||
|
//checking if it is the start of the packet
|
||||||
|
if (sync1 == 0x47 && sync2 == 0x83) {
|
||||||
|
int type = readByte();
|
||||||
|
//No. of milliseconds since Jan 1st 1970
|
||||||
|
long timeStamp = Message.bytesToLong(getBytes(6));
|
||||||
|
skipBytes(4);
|
||||||
|
long payloadLength = Message.bytesToLong(getBytes(2));
|
||||||
|
byte[] payload = getBytes((int) payloadLength);
|
||||||
|
Checksum checksum = new CRC32();
|
||||||
|
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
|
||||||
|
long computedCrc = checksum.getValue();
|
||||||
|
long packetCrc = Message.bytesToLong(getBytes(4));
|
||||||
|
if (computedCrc == packetCrc) {
|
||||||
|
switch (PacketType.assignPacketType(type, payload)) {
|
||||||
|
case BOAT_ACTION:
|
||||||
|
BoatAction actionType = ServerPacketParser
|
||||||
|
.extractBoatAction(
|
||||||
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
GameState.updateBoat(sourceId, actionType);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RACE_REGISTRATION_REQUEST:
|
||||||
|
ClientType requestedType = ServerPacketParser.extractClientType(
|
||||||
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
|
||||||
|
completeRegistration(requestedType);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RACE_CUSTOMIZATION_REQUEST:
|
||||||
|
Long sourceID = Message
|
||||||
|
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
|
||||||
|
CustomizeRequestType requestType = ServerPacketParser
|
||||||
|
.extractCustomizationType(
|
||||||
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
GameState.customizePlayer(sourceID, requestType,
|
||||||
|
Arrays.copyOfRange(payload, 6, payload.length));
|
||||||
|
GameState.setCustomizationFlag();
|
||||||
|
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("Packet has been dropped", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
closeSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.warn("Closed serverToClientThread" + thread, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendSetupMessages() {
|
||||||
|
xml = new XMLGenerator();
|
||||||
|
Race race = new Race();
|
||||||
|
|
||||||
|
for (ServerYacht yacht : GameState.getYachts().values()) {
|
||||||
|
race.addBoat(yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@TODO calculate lat/lng values
|
||||||
|
xml.setRegatta(
|
||||||
|
new Regatta(
|
||||||
|
"Party Parrot Test Server", "Bermuda Test Course",
|
||||||
|
57.6679590, 11.8503233)
|
||||||
|
);
|
||||||
|
xml.setRace(race);
|
||||||
|
|
||||||
|
XMLMessage xmlMessage;
|
||||||
|
xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
|
||||||
|
xml.getRegattaAsXml().length());
|
||||||
|
sendMessage(xmlMessage);
|
||||||
|
|
||||||
|
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
|
||||||
|
xml.getBoatsAsXml().length());
|
||||||
|
sendMessage(xmlMessage);
|
||||||
|
|
||||||
|
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
|
||||||
|
xml.getRaceAsXml().length());
|
||||||
|
sendMessage(xmlMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSocket() {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("IO error in server thread upon trying to close socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readByte() throws Exception {
|
||||||
|
int currentByte = -1;
|
||||||
|
try {
|
||||||
|
currentByte = is.read();
|
||||||
|
crcBuffer.write(currentByte);
|
||||||
|
} catch (SocketException se) {
|
||||||
|
disconnectListener.notifyDisconnect(this.player);
|
||||||
|
} catch (IOException e) {
|
||||||
|
disconnectListener.notifyDisconnect(this.player);
|
||||||
|
logger.warn("Socket read failed", 1);
|
||||||
|
}
|
||||||
|
if (currentByte == -1) {
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
return currentByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getBytes(int n) throws Exception {
|
||||||
|
byte[] bytes = new byte[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
bytes[i] = (byte) readByte();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipBytes(long n) throws Exception {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
readByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(Message message) {
|
||||||
|
try {
|
||||||
|
os.write(message.getBuffer());
|
||||||
|
} catch (SocketException e) {
|
||||||
|
logger.warn("Player " + sourceId + " side socket disconnected", 1);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Message send failed", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSeqNo() {
|
||||||
|
seqNo++;
|
||||||
|
return seqNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void sendBoatLocationPackets() {
|
||||||
|
ArrayList<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
for (ServerYacht yacht : yachts) {
|
||||||
|
BoatLocationMessage boatLocationMessage =
|
||||||
|
new BoatLocationMessage(
|
||||||
|
yacht.getSourceId(),
|
||||||
|
getSeqNo(),
|
||||||
|
yacht.getLocation().getLat(),
|
||||||
|
yacht.getLocation().getLng(),
|
||||||
|
yacht.getHeading(),
|
||||||
|
yacht.getCurrentVelocity().longValue());
|
||||||
|
|
||||||
|
sendMessage(boatLocationMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerYacht getYacht() {
|
||||||
|
return yacht;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCollisionMessage(Integer yachtId) {
|
||||||
|
sendMessage(new YachtEventCodeMessage(yachtId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addConnectionListener(ConnectionListener listener) {
|
||||||
|
connectionListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeConnectionListener(ConnectionListener listener) {
|
||||||
|
connectionListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate () {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
logger.warn("IOException attempting to terminate serverToClientThread " + this.thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDisconnectListener(DisconnectListener disconnectListener) {
|
||||||
|
this.disconnectListener = disconnectListener;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 12/07/17.
|
||||||
|
*/
|
||||||
|
public enum BoatAction {
|
||||||
|
|
||||||
|
VMG(1),
|
||||||
|
SAILS_IN(2),
|
||||||
|
SAILS_OUT(3),
|
||||||
|
TACK_GYBE(4),
|
||||||
|
UPWIND(5),
|
||||||
|
DOWNWIND(6),
|
||||||
|
MAINTAIN_HEADING(7);
|
||||||
|
|
||||||
|
private final int type;
|
||||||
|
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (BoatAction type : BoatAction.values()) {
|
||||||
|
intToTypeMap.put(type.getValue(), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoatAction(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BoatAction getType(int value) {
|
||||||
|
return intToTypeMap.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 12/07/17.
|
||||||
|
*/
|
||||||
|
public class BoatActionMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
|
||||||
|
private final int MESSAGE_SIZE = 1;
|
||||||
|
private BoatAction actionType;
|
||||||
|
|
||||||
|
public BoatActionMessage(BoatAction actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
// Write message fields
|
||||||
|
putInt(actionType.getValue(), 1);
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+69
-71
@@ -1,9 +1,7 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
public class BoatLocationMessage extends Message {
|
public class BoatLocationMessage extends Message {
|
||||||
|
|
||||||
private final int MESSAGE_SIZE = 56;
|
private final int MESSAGE_SIZE = 56;
|
||||||
|
|
||||||
private long messageVersionNumber;
|
private long messageVersionNumber;
|
||||||
@@ -31,6 +29,7 @@ public class BoatLocationMessage extends Message {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the location, altitude and sensor data from the boat.
|
* Describes the location, altitude and sensor data from the boat.
|
||||||
|
*
|
||||||
* @param sourceId ID of the boat
|
* @param sourceId ID of the boat
|
||||||
* @param sequenceNum Sequence number of the message
|
* @param sequenceNum Sequence number of the message
|
||||||
* @param latitude The boats latitude
|
* @param latitude The boats latitude
|
||||||
@@ -38,8 +37,8 @@ public class BoatLocationMessage extends Message {
|
|||||||
* @param heading The boats heading
|
* @param heading The boats heading
|
||||||
* @param boatSpeed The boats speed
|
* @param boatSpeed The boats speed
|
||||||
*/
|
*/
|
||||||
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude,
|
||||||
boatSpeed /= 10;
|
double heading, long boatSpeed) {
|
||||||
messageVersionNumber = 1;
|
messageVersionNumber = 1;
|
||||||
time = System.currentTimeMillis();
|
time = System.currentTimeMillis();
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
@@ -64,70 +63,6 @@ public class BoatLocationMessage extends Message {
|
|||||||
this.rudderAngle = 0;
|
this.rudderAngle = 0;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
|
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 MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException{
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -158,7 +93,70 @@ public class BoatLocationMessage extends Message {
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
/**
|
||||||
|
* 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 MESSAGE_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current status of a boat
|
* The current status of a boat
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 20/07/17.
|
||||||
|
*/
|
||||||
|
public class ChatterMessage extends Message {
|
||||||
|
|
||||||
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
|
private final int MESSAGE_SIZE = 3;
|
||||||
|
private int message_type;
|
||||||
|
private int message_size = 21;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ChatterMessage(int message_type, int message_size, String message) {
|
||||||
|
this.message_type = message_type;
|
||||||
|
this.message_size = message_size;
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||||
|
putInt(message_type, 1);
|
||||||
|
putInt(message_size, 1);
|
||||||
|
putBytes(message.getBytes());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE + message_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
public enum ClientType {
|
||||||
|
SPECTATOR(0x00),
|
||||||
|
PLAYER(0x01),
|
||||||
|
CONTROL_TUTORIAL(0x02),
|
||||||
|
GHOST_MODE(0x03);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
ClientType(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode(){
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClientType getClientType(int typeCode){
|
||||||
|
switch (typeCode){
|
||||||
|
case 0x00:
|
||||||
|
return SPECTATOR;
|
||||||
|
case 0x01:
|
||||||
|
return PLAYER;
|
||||||
|
case 0x02:
|
||||||
|
return CONTROL_TUTORIAL;
|
||||||
|
case 0x03:
|
||||||
|
return GHOST_MODE;
|
||||||
|
default:
|
||||||
|
return PLAYER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
|
||||||
|
public class CustomizeRequestMessage extends Message {
|
||||||
|
|
||||||
|
|
||||||
|
private static int MESSAGE_LENGTH = 6;
|
||||||
|
|
||||||
|
//Message fields
|
||||||
|
private CustomizeRequestType customizeType;
|
||||||
|
private Integer payloadLength;
|
||||||
|
|
||||||
|
public CustomizeRequestMessage(CustomizeRequestType customizeType, double sourceID,
|
||||||
|
byte[] payload) {
|
||||||
|
payloadLength = payload.length;
|
||||||
|
setHeader(new Header(MessageType.CUSTOMIZATION_REQUEST, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
|
||||||
|
putInt((int) sourceID, 4);
|
||||||
|
putInt((int) customizeType.getType(), 2);
|
||||||
|
putBytes(payload);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_LENGTH + payloadLength; // placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
|
||||||
|
public enum CustomizeRequestType {
|
||||||
|
NAME(0x00),
|
||||||
|
COLOR(0x01),
|
||||||
|
SHAPE(0x02);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
CustomizeRequestType(int type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomizeRequestType getRequestType(int typeCode) {
|
||||||
|
switch (typeCode) {
|
||||||
|
case 0x00:
|
||||||
|
return NAME;
|
||||||
|
case 0x01:
|
||||||
|
return COLOR;
|
||||||
|
case 0x02:
|
||||||
|
return SHAPE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ajm412 on 14/08/17.
|
||||||
|
*/
|
||||||
|
public class CustomizeResponseMessage extends Message {
|
||||||
|
|
||||||
|
private static int MESSAGE_LENGTH = 2;
|
||||||
|
|
||||||
|
public CustomizeResponseMessage(CustomizeResponseType responseType) {
|
||||||
|
setHeader(new Header(MessageType.CUSTOMIZATION_RESPONSE, 1, (short) getSize()));
|
||||||
|
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putInt(responseType.getType(), 2);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_LENGTH; // placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
|
||||||
|
public enum CustomizeResponseType {
|
||||||
|
SUCCESS(0x00),
|
||||||
|
FAILURE(0x01),
|
||||||
|
FAILURE_MALFORMED_DATA(0x02),
|
||||||
|
FAILURE_INCOMPATIBLE(0x03);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
CustomizeResponseType(int type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomizeResponseType getResponseType(int typeCode) {
|
||||||
|
switch (typeCode) {
|
||||||
|
case 0x00:
|
||||||
|
return SUCCESS;
|
||||||
|
case 0x01:
|
||||||
|
return FAILURE;
|
||||||
|
case 0x02:
|
||||||
|
return FAILURE_MALFORMED_DATA;
|
||||||
|
case 0x03:
|
||||||
|
return FAILURE_INCOMPATIBLE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
public enum DeviceType {
|
public enum DeviceType {
|
||||||
UNKNOWN(0),
|
UNKNOWN(0),
|
||||||
+12
-4
@@ -1,9 +1,6 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
public class Header {
|
public class Header {
|
||||||
// From API spec
|
// From API spec
|
||||||
@@ -43,10 +40,21 @@ public class Header {
|
|||||||
buff.position(buffPos);
|
buff.position(buffPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the buffer
|
||||||
|
*/
|
||||||
|
public void reset(){
|
||||||
|
buffPos = 0;
|
||||||
|
buff.clear();
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a ByteBuffer containing the message header
|
* @return a ByteBuffer containing the message header
|
||||||
*/
|
*/
|
||||||
public ByteBuffer getByteBuffer(){
|
public ByteBuffer getByteBuffer(){
|
||||||
|
reset();
|
||||||
|
|
||||||
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
||||||
|
|
||||||
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
||||||
+6
-21
@@ -1,32 +1,13 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.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 {
|
public class Heartbeat extends Message {
|
||||||
private final int MESSAGE_SIZE = 4;
|
private final int MESSAGE_SIZE = 4;
|
||||||
private int seqNo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Heartbeat from the AC35 Streaming data spec
|
* Heartbeat from the AC35 Streaming data spec
|
||||||
* @param seqNo Increment every time a message is sent
|
* @param seqNo Increment every time a message is sent
|
||||||
*/
|
*/
|
||||||
public Heartbeat(int seqNo){
|
public Heartbeat(int seqNo){
|
||||||
this.seqNo = seqNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
|
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
|
||||||
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
@@ -36,7 +17,11 @@ public class Heartbeat extends Message {
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+15
-19
@@ -1,10 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.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{
|
public class MarkRoundingMessage extends Message{
|
||||||
private final long MESSAGE_VERSION_NUMBER = 1;
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
@@ -18,13 +12,20 @@ public class MarkRoundingMessage extends Message{
|
|||||||
private RoundingSide roundingSide;
|
private RoundingSide roundingSide;
|
||||||
private long markId;
|
private long markId;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This message is sent when a boat passes a mark, start line, or finish line
|
* 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
|
* The purpose of this is to record the time when yachts cross marks
|
||||||
|
* @param ackNumber ackNumber
|
||||||
|
* @param raceId raceId
|
||||||
|
* @param sourceId boatSourceId
|
||||||
|
* @param roundingBoatStatus roundingBoatStatus
|
||||||
|
* @param roundingSide roundingSide
|
||||||
|
* @param markId markId
|
||||||
*/
|
*/
|
||||||
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||||
RoundingSide roundingSide, int markId){
|
RoundingSide roundingSide, MarkType markType, int markId) {
|
||||||
this.time = System.currentTimeMillis() / 1000L;
|
this.time = System.currentTimeMillis();
|
||||||
this.ackNumber = ackNumber;
|
this.ackNumber = ackNumber;
|
||||||
this.raceId = raceId;
|
this.raceId = raceId;
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
@@ -33,15 +34,6 @@ public class MarkRoundingMessage extends Message{
|
|||||||
this.markId = markId;
|
this.markId = markId;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
|
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -52,11 +44,15 @@ public class MarkRoundingMessage extends Message{
|
|||||||
putInt((int) sourceId, 4);
|
putInt((int) sourceId, 4);
|
||||||
putByte((byte) boatStatus.getCode());
|
putByte((byte) boatStatus.getCode());
|
||||||
putByte((byte) roundingSide.getCode());
|
putByte((byte) roundingSide.getCode());
|
||||||
|
putByte((byte) markType.getCode());
|
||||||
putByte((byte) markId);
|
putByte((byte) markId);
|
||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types of marks boats can round
|
* Types of marks boats can round
|
||||||
+26
-12
@@ -1,9 +1,7 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
@@ -33,11 +31,6 @@ public abstract class Message {
|
|||||||
*/
|
*/
|
||||||
public abstract int getSize();
|
public abstract int getSize();
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the message as through the outputStream
|
|
||||||
*/
|
|
||||||
public abstract void send(SocketChannel outputStream) throws IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate byte buffer to correct size
|
* Allocate byte buffer to correct size
|
||||||
*/
|
*/
|
||||||
@@ -45,6 +38,7 @@ public abstract class Message {
|
|||||||
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
bufferPosition = 0;
|
bufferPosition = 0;
|
||||||
|
buffer.position(bufferPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,10 +155,10 @@ public abstract class Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current buffer
|
* @return The current buffer as a byte array
|
||||||
*/
|
*/
|
||||||
public ByteBuffer getBuffer(){
|
public byte[] getBuffer(){
|
||||||
return buffer;
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +172,7 @@ public abstract class Message {
|
|||||||
* Convert an integer to an array of bytes
|
* Convert an integer to an array of bytes
|
||||||
* @param val The value to add
|
* @param val The value to add
|
||||||
* @param len The width of the integer in the buffer
|
* @param len The width of the integer in the buffer
|
||||||
* @return
|
* @return A byte array to be sent
|
||||||
*/
|
*/
|
||||||
public static byte[] intToByteArray(long val, int len){
|
public static byte[] intToByteArray(long val, int len){
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -193,6 +187,26 @@ public abstract class Message {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* takes an array of up to 7 bytes in little endian format and
|
||||||
|
* returns a positive long constructed from the input bytes
|
||||||
|
*
|
||||||
|
* @param bytes the bytes to be converted to long
|
||||||
|
* @return a positive long if there is less than 8 bytes -1 otherwise
|
||||||
|
*/
|
||||||
|
public static long bytesToLong(byte[] bytes){
|
||||||
|
long partialLong = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (byte b: bytes){
|
||||||
|
if (index > 6){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return partialLong;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse an array of bytes
|
* Reverse an array of bytes
|
||||||
* @param data The byte[] to reverse
|
* @param data The byte[] to reverse
|
||||||
+10
-2
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum containing the types of messages
|
* Enum containing the types of messages
|
||||||
@@ -16,7 +16,13 @@ public enum MessageType {
|
|||||||
BOAT_LOCATION(37),
|
BOAT_LOCATION(37),
|
||||||
MARK_ROUNDING(38),
|
MARK_ROUNDING(38),
|
||||||
COURSE_WIND(44),
|
COURSE_WIND(44),
|
||||||
AVERAGE_WIND(47);
|
AVERAGE_WIND(47),
|
||||||
|
BOAT_ACTION(100),
|
||||||
|
REGISTRATION_REQUEST(101),
|
||||||
|
REGISTRATION_RESPONSE(102),
|
||||||
|
CUSTOMIZATION_REQUEST(103),
|
||||||
|
CUSTOMIZATION_RESPONSE(104);
|
||||||
|
|
||||||
|
|
||||||
private int code;
|
private int code;
|
||||||
|
|
||||||
@@ -31,4 +37,6 @@ public enum MessageType {
|
|||||||
int getCode(){
|
int getCode(){
|
||||||
return this.code;
|
return this.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The types of race start status messages
|
* The types of race start status messages
|
||||||
+6
-17
@@ -1,10 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.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 {
|
public class RaceStartStatusMessage extends Message {
|
||||||
private final int MESSAGE_SIZE = 20;
|
private final int MESSAGE_SIZE = 20;
|
||||||
@@ -32,15 +26,6 @@ public class RaceStartStatusMessage extends Message {
|
|||||||
this.raceId = raceId;
|
this.raceId = raceId;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
|
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -53,7 +38,11 @@ public class RaceStartStatusMessage extends Message {
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current status of the race
|
* The current status of the race
|
||||||
+15
-24
@@ -1,7 +1,5 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
@@ -9,12 +7,14 @@ public class RaceStatusMessage extends Message{
|
|||||||
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
||||||
private final int MESSAGE_VERSION = 2; //Always set to 1
|
private final int MESSAGE_VERSION = 2; //Always set to 1
|
||||||
private final int MESSAGE_BASE_SIZE = 24;
|
private final int MESSAGE_BASE_SIZE = 24;
|
||||||
|
private final double windDirFactor = 0x4000 / 90;
|
||||||
|
|
||||||
|
|
||||||
private long currentTime;
|
private long currentTime;
|
||||||
private long raceId;
|
private long raceId;
|
||||||
private RaceStatus raceStatus;
|
private RaceStatus raceStatus;
|
||||||
private long expectedStartTime;
|
private long expectedStartTime;
|
||||||
private WindDirection raceWindDirection;
|
private double raceWindDirection;
|
||||||
private long windSpeed;
|
private long windSpeed;
|
||||||
private long numBoatsInRace;
|
private long numBoatsInRace;
|
||||||
private RaceType raceType;
|
private RaceType raceType;
|
||||||
@@ -33,13 +33,13 @@ public class RaceStatusMessage extends Message{
|
|||||||
* @param sourceId The source of this message
|
* @param sourceId The source of this message
|
||||||
* @param boats A list of boat status sub messages
|
* @param boats A list of boat status sub messages
|
||||||
*/
|
*/
|
||||||
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection,
|
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, double raceWindDirection,
|
||||||
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
||||||
currentTime = System.currentTimeMillis();
|
currentTime = System.currentTimeMillis();
|
||||||
this.raceId = raceId;
|
this.raceId = raceId;
|
||||||
this.raceStatus = raceStatus;
|
this.raceStatus = raceStatus;
|
||||||
this.expectedStartTime = expectedStartTime;
|
this.expectedStartTime = expectedStartTime;
|
||||||
this.raceWindDirection = raceWindDirection;
|
this.raceWindDirection = raceWindDirection * windDirFactor+100.0;
|
||||||
this.windSpeed = windSpeed;
|
this.windSpeed = windSpeed;
|
||||||
this.numBoatsInRace = numBoatsInRace;
|
this.numBoatsInRace = numBoatsInRace;
|
||||||
this.raceType = raceType;
|
this.raceType = raceType;
|
||||||
@@ -47,22 +47,6 @@ public class RaceStatusMessage extends Message{
|
|||||||
crc = new CRC32();
|
crc = new CRC32();
|
||||||
|
|
||||||
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
|
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(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -71,7 +55,7 @@ public class RaceStatusMessage extends Message{
|
|||||||
putInt((int) raceId, 4);
|
putInt((int) raceId, 4);
|
||||||
putByte((byte) raceStatus.getCode());
|
putByte((byte) raceStatus.getCode());
|
||||||
putInt(expectedStartTime, 6);
|
putInt(expectedStartTime, 6);
|
||||||
putInt((int) raceWindDirection.getCode(), 2);
|
putInt((int) this.raceWindDirection, 2);
|
||||||
putInt((int) windSpeed, 2);
|
putInt((int) windSpeed, 2);
|
||||||
putByte((byte) numBoatsInRace);
|
putByte((byte) numBoatsInRace);
|
||||||
putByte((byte) raceType.getCode());
|
putByte((byte) raceType.getCode());
|
||||||
@@ -82,7 +66,14 @@ public class RaceStatusMessage extends Message{
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
/**
|
||||||
|
* @return the size of this message in bytes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum containing the types of races
|
* Enum containing the types of races
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
|
||||||
|
public class RegistrationRequestMessage extends Message {
|
||||||
|
private static int MESSAGE_LENGTH = 2;
|
||||||
|
|
||||||
|
public RegistrationRequestMessage(ClientType type){
|
||||||
|
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
|
||||||
|
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putInt(type.getCode(), 2);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
public class RegistrationResponseMessage extends Message{
|
||||||
|
|
||||||
|
public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){
|
||||||
|
setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putInt(clientSourceID, 4);
|
||||||
|
putInt(status.getCode(), 1);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
public enum RegistrationResponseStatus {
|
||||||
|
SUCCESS_SPECTATING(0x00),
|
||||||
|
SUCCESS_PLAYING(0x01),
|
||||||
|
SUCCESS_TUTORIAL(0x02),
|
||||||
|
SUCCESS_GHOSTING(0x03),
|
||||||
|
|
||||||
|
FAILURE_GENERAL(0x10),
|
||||||
|
FAILURE_FULL(0x11);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
RegistrationResponseStatus(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message code (From the API Spec)
|
||||||
|
* @return the message code
|
||||||
|
*/
|
||||||
|
int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegistrationResponseStatus getResponseStatus(int typeCode){
|
||||||
|
switch (typeCode){
|
||||||
|
case 0x00:
|
||||||
|
return SUCCESS_SPECTATING;
|
||||||
|
case 0x01:
|
||||||
|
return SUCCESS_PLAYING;
|
||||||
|
case 0x02:
|
||||||
|
return SUCCESS_TUTORIAL;
|
||||||
|
case 0x03:
|
||||||
|
return SUCCESS_GHOSTING;
|
||||||
|
case 0x10:
|
||||||
|
return FAILURE_GENERAL;
|
||||||
|
case 0x11:
|
||||||
|
return FAILURE_FULL;
|
||||||
|
default:
|
||||||
|
return FAILURE_GENERAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The status of a boat rounding a mark
|
* The status of a boat rounding a mark
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The side the boat rounded the mark
|
||||||
|
*/
|
||||||
|
public enum RoundingSide {
|
||||||
|
UNKNOWN(0, "Unknown"),
|
||||||
|
PORT(1, "Port"),
|
||||||
|
STARBOARD(2, "Stbd"),
|
||||||
|
SP(3, "SP"),
|
||||||
|
PS(4, "PS");
|
||||||
|
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
RoundingSide(long code, String name) {
|
||||||
|
this.code = code;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoundingSide getRoundingSide(String identifier) {
|
||||||
|
RoundingSide roundingSide = UNKNOWN;
|
||||||
|
switch (identifier) {
|
||||||
|
case "Unknown":
|
||||||
|
roundingSide = UNKNOWN;
|
||||||
|
break;
|
||||||
|
case "Port":
|
||||||
|
roundingSide = PORT;
|
||||||
|
break;
|
||||||
|
case "Stbd":
|
||||||
|
roundingSide = STARBOARD;
|
||||||
|
break;
|
||||||
|
case "SP":
|
||||||
|
roundingSide = SP;
|
||||||
|
break;
|
||||||
|
case "PS":
|
||||||
|
roundingSide = PS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return roundingSide;
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-24
@@ -1,12 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.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{
|
public class XMLMessage extends Message{
|
||||||
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
||||||
@@ -25,6 +17,7 @@ public class XMLMessage extends Message{
|
|||||||
* XML Message from the AC35 Streaming data spec
|
* XML Message from the AC35 Streaming data spec
|
||||||
* @param content The XML content
|
* @param content The XML content
|
||||||
* @param type The XML Message Sub Type
|
* @param type The XML Message Sub Type
|
||||||
|
* @param sequenceNum sequenceNum
|
||||||
*/
|
*/
|
||||||
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
||||||
this.content = content;
|
this.content = content;
|
||||||
@@ -35,20 +28,6 @@ public class XMLMessage extends Message{
|
|||||||
sequence = sequenceNum;
|
sequence = sequenceNum;
|
||||||
|
|
||||||
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
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(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -63,7 +42,12 @@ public class XMLMessage extends Message{
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
/**
|
||||||
|
* @return The length of this message
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE + content.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum containing the types of XML messages
|
* Enum containing the types of XML messages
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by zyt10 on 10/08/17.
|
||||||
|
*/
|
||||||
|
public class YachtEventCodeMessage extends Message {
|
||||||
|
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.YACHT_EVENT_CODE;
|
||||||
|
private final int MESSAGE_VERSION = 1; //Always set to 1
|
||||||
|
private final int MESSAGE_SIZE = 22;
|
||||||
|
|
||||||
|
// Message fields
|
||||||
|
private long timeStamp;
|
||||||
|
private long ack = 0x00; //Unused
|
||||||
|
private int raceId;
|
||||||
|
private int destSourceId;
|
||||||
|
private int incidentId;
|
||||||
|
private int eventId;
|
||||||
|
|
||||||
|
|
||||||
|
public YachtEventCodeMessage(Integer subjectId) {
|
||||||
|
timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
ack = 0;
|
||||||
|
raceId = 1;
|
||||||
|
destSourceId = subjectId; // collision boat source id
|
||||||
|
incidentId = 0;
|
||||||
|
eventId = 33;
|
||||||
|
|
||||||
|
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
// Write message fields
|
||||||
|
putUnsignedByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putInt((int) ack, 2);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putInt((int) destSourceId, 4);
|
||||||
|
putInt((int) incidentId, 4);
|
||||||
|
putInt((int) eventId, 1);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The length of this message
|
||||||
|
*/
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.Observer;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
|
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||||
|
import javafx.beans.property.ReadOnlyLongProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.model.mark.CompoundMark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||||
|
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
||||||
|
* not used anymore.
|
||||||
|
*/
|
||||||
|
public class ClientYacht extends Observable {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface YachtLocationListener {
|
||||||
|
void notifyLocation(ClientYacht clientYacht, double lat, double lon, double heading,
|
||||||
|
Boolean sailsIn, double velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface MarkRoundingListener {
|
||||||
|
void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||||
|
|
||||||
|
|
||||||
|
private String boatType;
|
||||||
|
private Integer sourceId;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
private String boatName;
|
||||||
|
private String country;
|
||||||
|
private Integer position;
|
||||||
|
|
||||||
|
private Long estimateTimeAtFinish;
|
||||||
|
private Boolean sailIn = true;
|
||||||
|
private Integer currentMarkSeqID = 0;
|
||||||
|
private Long markRoundTime;
|
||||||
|
private Long timeTillNext;
|
||||||
|
private Double heading;
|
||||||
|
private Integer legNumber = 0;
|
||||||
|
private GeoPoint location;
|
||||||
|
private Integer boatStatus;
|
||||||
|
private Double currentVelocity;
|
||||||
|
|
||||||
|
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
||||||
|
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
|
||||||
|
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
||||||
|
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||||
|
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||||
|
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
||||||
|
private CompoundMark lastMarkRounded;
|
||||||
|
private Color colour;
|
||||||
|
|
||||||
|
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||||
|
String boatName, String country) {
|
||||||
|
this.boatType = boatType;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.hullID = hullID;
|
||||||
|
this.shortName = shortName;
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.country = country;
|
||||||
|
this.location = new GeoPoint(57.670341, 11.826856);
|
||||||
|
this.heading = 120.0; //In degrees
|
||||||
|
this.currentVelocity = 0d;
|
||||||
|
this.boatStatus = 1;
|
||||||
|
this.colour = Color.rgb(0, 0, 0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
|
||||||
|
* rounding package.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addObserver(Observer o) {
|
||||||
|
super.addObserver(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatType() {
|
||||||
|
return boatType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSourceId() {
|
||||||
|
//@TODO Remove and merge with Creating Game Loop
|
||||||
|
if (sourceId == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHullID() {
|
||||||
|
if (hullID == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return hullID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortName() {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatName() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCountry() {
|
||||||
|
if (country == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getBoatStatus() {
|
||||||
|
return boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatStatus(Integer boatStatus) {
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLegNumber() {
|
||||||
|
return legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLegNumber(Integer legNumber) {
|
||||||
|
this.legNumber = legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
|
||||||
|
timeTillNext = estimateTimeTillNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEstimateTimeAtFinish() {
|
||||||
|
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
return format.format(estimateTimeAtFinish);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
|
||||||
|
this.estimateTimeAtFinish = estimateTimeAtFinish;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPlacing() {
|
||||||
|
return placingProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlacing(Integer position) {
|
||||||
|
placingProperty.set(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyIntegerProperty placingProperty() {
|
||||||
|
return placingProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateVelocityProperty(double velocity) {
|
||||||
|
this.velocityProperty.set(velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkRoundingTime(Long markRoundingTime) {
|
||||||
|
this.markRoundTime = markRoundingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty getVelocityProperty() {
|
||||||
|
return velocityProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyLongProperty timeTillNextProperty() {
|
||||||
|
return timeTillNextProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimeTillNext() {
|
||||||
|
return timeTillNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMarkRoundTime() {
|
||||||
|
return markRoundTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark getLastMarkRounded() {
|
||||||
|
return lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
|
||||||
|
this.lastMarkRounded = lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeoPoint getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(Integer position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleSail() {
|
||||||
|
sailIn = !sailIn;
|
||||||
|
}
|
||||||
|
//// TODO: 15/08/17 asd
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current location of the boat in lat and long whilst preserving the last location
|
||||||
|
*
|
||||||
|
* @param lat Latitude
|
||||||
|
* @param lng Longitude
|
||||||
|
*/
|
||||||
|
public void setLocation(Double lat, Double lng) {
|
||||||
|
location.setLat(lat);
|
||||||
|
location.setLng(lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeading(Double heading) {
|
||||||
|
this.heading = heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
|
||||||
|
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyLongProperty timeSinceLastMarkProperty() {
|
||||||
|
return timeSinceLastMarkProperty.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeTillNext(Long timeTillNext) {
|
||||||
|
this.timeTillNext = timeTillNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Color getColour() {
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColour(Color colour) {
|
||||||
|
this.colour = colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||||
|
setLocation(lat, lng);
|
||||||
|
this.heading = heading;
|
||||||
|
// this.currentVelocity = velocity;
|
||||||
|
updateVelocityProperty(velocity);
|
||||||
|
for (YachtLocationListener yll : locationListeners) {
|
||||||
|
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addLocationListener(YachtLocationListener listener) {
|
||||||
|
locationListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMarkRoundingListener(MarkRoundingListener listener) {
|
||||||
|
markRoundingListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
||||||
|
markRoundingListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSailIn () {
|
||||||
|
return sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void roundMark(CompoundMark mark, long markRoundTime, long timeSinceLastMark) {
|
||||||
|
this.markRoundTime = markRoundTime;
|
||||||
|
timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||||
|
lastMarkRounded = mark;
|
||||||
|
legNumber++;
|
||||||
|
for (MarkRoundingListener listener : markRoundingListeners) {
|
||||||
|
listener.notifyRounding(this, lastMarkRounded, legNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for generating colours.
|
||||||
|
*/
|
||||||
|
public enum Colors {
|
||||||
|
RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY;
|
||||||
|
|
||||||
|
public static Color getColor(Integer index) {
|
||||||
|
return Color.valueOf(values()[index].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-8
@@ -1,18 +1,18 @@
|
|||||||
package seng302.server.simulator.mark;
|
package seng302.model;
|
||||||
|
|
||||||
public class Position {
|
/**
|
||||||
|
* A class represent Geo location (latitude, lnggitude).
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class GeoPoint {
|
||||||
|
|
||||||
double lat, lng;
|
private double lat, lng;
|
||||||
|
|
||||||
public Position(double lat, double lng) {
|
public GeoPoint(double lat, double lng) {
|
||||||
this.lat = lat;
|
this.lat = lat;
|
||||||
this.lng = lng;
|
this.lng = lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return String.format("Position at lat:%f lng:%f.", lat, lng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLat() {
|
public double getLat() {
|
||||||
return lat;
|
return lat;
|
||||||
}
|
}
|
||||||
@@ -28,4 +28,9 @@ public class Position {
|
|||||||
public void setLng(double lng) {
|
public void setLng(double lng) {
|
||||||
this.lng = lng;
|
this.lng = lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "lat: " + lat + " lng: " + lng;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores data on the border of a race
|
||||||
|
*/
|
||||||
|
public class Limit extends GeoPoint {
|
||||||
|
|
||||||
|
private Integer seqID;
|
||||||
|
|
||||||
|
public Limit(Integer seqID, Double lat, Double lng) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class defining a player and their respective details in the game as held by the model
|
||||||
|
* Created by wmu16 on 10/07/17.
|
||||||
|
*/
|
||||||
|
public class Player {
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private ServerYacht yacht;
|
||||||
|
private Integer lastMarkPassed;
|
||||||
|
|
||||||
|
|
||||||
|
public Player(Socket socket, ServerYacht yacht) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.yacht = yacht;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLastMarkPassed() {
|
||||||
|
return lastMarkPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMarkPassed(Integer lastMarkPassed) {
|
||||||
|
this.lastMarkPassed = lastMarkPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerYacht getYacht() {
|
||||||
|
return yacht;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String playerAddress = null;
|
||||||
|
|
||||||
|
if (socket == null){
|
||||||
|
return "Disconnected Player";
|
||||||
|
}
|
||||||
|
|
||||||
|
playerAddress = socket.getRemoteSocketAddress().toString();
|
||||||
|
|
||||||
|
|
||||||
|
return playerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(obj instanceof Player)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((Player) obj).socket.equals(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode(){
|
||||||
|
return socket.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
-23
@@ -1,7 +1,9 @@
|
|||||||
package seng302.models;
|
package seng302.model;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
import java.util.ArrayList;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,9 +26,9 @@ public final class PolarTable {
|
|||||||
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
|
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
|
||||||
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
|
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
|
||||||
* as a value
|
* as a value
|
||||||
* @param file containing the polar csv information
|
* @param polarFile polarFile to be parsed
|
||||||
*/
|
*/
|
||||||
public static void parsePolarFile(String file) {
|
public static void parsePolarFile(InputStream polarFile) {
|
||||||
polarTable = new HashMap<>();
|
polarTable = new HashMap<>();
|
||||||
upwindOptimal = new HashMap<>();
|
upwindOptimal = new HashMap<>();
|
||||||
downwindOptimal = new HashMap<>();
|
downwindOptimal = new HashMap<>();
|
||||||
@@ -34,7 +36,7 @@ public final class PolarTable {
|
|||||||
String line;
|
String line;
|
||||||
Boolean isHeaderLine = true;
|
Boolean isHeaderLine = true;
|
||||||
|
|
||||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
String[] thisLine = line.split(",");
|
String[] thisLine = line.split(",");
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ public final class PolarTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
System.out.println("[PolarTable] IO exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ public final class PolarTable {
|
|||||||
*/
|
*/
|
||||||
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
return upwindOptimal.get(polarWindSpeed);
|
return upwindOptimal.get(polarWindSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,30 +136,47 @@ public final class PolarTable {
|
|||||||
*/
|
*/
|
||||||
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
return downwindOptimal.get(polarWindSpeed);
|
return downwindOptimal.get(polarWindSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Double getClosestMatch(Double thisWindSpeed) {
|
public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
|
||||||
|
|
||||||
ArrayList<Double> windValues = new ArrayList<>(polarTable.keySet());
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
|
||||||
|
|
||||||
Double lowerVal = windValues.get(0);
|
return polarTable.get(polarWindSpeed).get(polarAngle);
|
||||||
Double upperVal = windValues.get(1);
|
|
||||||
|
|
||||||
for(int i = 0; i < windValues.size() - 1; i++) {
|
|
||||||
lowerVal = windValues.get(i);
|
|
||||||
upperVal = windValues.get(i+1);
|
|
||||||
if (thisWindSpeed <= upperVal) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Double lowerDiff = Math.abs(lowerVal - thisWindSpeed);
|
|
||||||
Double upperDiff = Math.abs(upperVal - thisWindSpeed);
|
|
||||||
|
|
||||||
return (lowerDiff <= upperDiff) ? lowerVal : upperVal;
|
public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
|
||||||
|
Double smallestDif = Double.POSITIVE_INFINITY;
|
||||||
|
Double closestWind = 0d;
|
||||||
|
for (Double polarWindSpeed : polarTable.keySet()) {
|
||||||
|
Double difference = Math.abs(polarWindSpeed - thisWindSpeed);
|
||||||
|
if (difference < smallestDif) {
|
||||||
|
smallestDif = difference;
|
||||||
|
closestWind = polarWindSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestWind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getClosestAngleInPolar(HashMap<Double, Double> thisWindSpeedPolar, Double thisHeading) {
|
||||||
|
Double smallestDif = Double.POSITIVE_INFINITY;
|
||||||
|
Double closestAngle = 0d;
|
||||||
|
|
||||||
|
for (Double polarAngle : thisWindSpeedPolar.keySet()) {
|
||||||
|
Double difference = Math.abs(polarAngle - thisHeading);
|
||||||
|
if (difference < smallestDif) {
|
||||||
|
smallestDif = difference;
|
||||||
|
closestAngle = polarAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import seng302.model.stream.parser.RaceStartData;
|
||||||
|
import seng302.model.stream.parser.RaceStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
|
||||||
|
* Calculates the state of critical race attributes when relevant data is added.
|
||||||
|
*/
|
||||||
|
public class RaceState {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CollisionListener {
|
||||||
|
void notifyCollision(GeoPoint location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
private ReadOnlyDoubleWrapper windSpeed = new ReadOnlyDoubleWrapper();
|
||||||
|
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
||||||
|
private long serverSystemTime;
|
||||||
|
private long expectedStartTime;
|
||||||
|
private boolean isRaceStarted = false;
|
||||||
|
long timeTillStart;
|
||||||
|
private ObservableList<ClientYacht> playerPositions;
|
||||||
|
private List<ClientYacht> collisions = new ArrayList<>();
|
||||||
|
private List<CollisionListener> collisionListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
public RaceState() {
|
||||||
|
playerPositions = FXCollections.observableArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState (RaceStatusData data) {
|
||||||
|
this.windSpeed.set(data.getWindSpeed());
|
||||||
|
this.windDirection.set(data.getWindDirection());
|
||||||
|
this.serverSystemTime = data.getCurrentTime();
|
||||||
|
this.expectedStartTime = data.getExpectedStartTime();
|
||||||
|
this.isRaceStarted = data.isRaceStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone (TimeZone timeZone) {
|
||||||
|
DATE_TIME_FORMAT.setTimeZone(timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState (RaceStartData data) {
|
||||||
|
this.timeTillStart = data.getRaceStartTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRaceTimeStr () {
|
||||||
|
long raceTime = serverSystemTime - expectedStartTime;
|
||||||
|
if (raceTime < 0) {
|
||||||
|
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
|
||||||
|
} else {
|
||||||
|
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeTillStart () {
|
||||||
|
return (expectedStartTime - serverSystemTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getWindSpeed() {
|
||||||
|
return windSpeed.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty windSpeedProperty() {
|
||||||
|
return windSpeed.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty windDirectionProperty() {
|
||||||
|
return windDirection.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRaceTime() {
|
||||||
|
return serverSystemTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRaceStarted () {
|
||||||
|
return isRaceStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoats(Collection<ClientYacht> clientYachts) {
|
||||||
|
playerPositions.setAll(clientYachts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sortPlayers() {
|
||||||
|
playerPositions.sort((yacht1, yacht2) -> Integer.compare(yacht2.getLegNumber(),
|
||||||
|
yacht1.getLegNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<ClientYacht> getPlayerPositions() {
|
||||||
|
return playerPositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeCollision(ClientYacht yacht) {
|
||||||
|
collisions.add(yacht);
|
||||||
|
for (CollisionListener collisionListener : collisionListeners) {
|
||||||
|
collisionListener.notifyCollision(yacht.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCollisionListener(CollisionListener collisionListener) {
|
||||||
|
collisionListeners.add(collisionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCollisionListener(CollisionListener collisionListener) {
|
||||||
|
collisionListeners.remove(collisionListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.Observer;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.gameServer.messages.BoatStatus;
|
||||||
|
import seng302.model.mark.Mark;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||||
|
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
||||||
|
* not used anymore.
|
||||||
|
*/
|
||||||
|
public class ServerYacht extends Observable {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||||
|
|
||||||
|
public static final Double TURN_STEP = 5.0;
|
||||||
|
|
||||||
|
//Boat info
|
||||||
|
private String boatType;
|
||||||
|
private Integer sourceId;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
private String boatName;
|
||||||
|
private String country;
|
||||||
|
private BoatStatus boatStatus;
|
||||||
|
|
||||||
|
private Color boatColor;
|
||||||
|
|
||||||
|
|
||||||
|
//Location
|
||||||
|
private Double lastHeading;
|
||||||
|
private Boolean sailIn;
|
||||||
|
private Double heading;
|
||||||
|
private GeoPoint lastLocation;
|
||||||
|
private GeoPoint location;
|
||||||
|
private Double currentVelocity;
|
||||||
|
private Boolean isAuto;
|
||||||
|
private Double autoHeading;
|
||||||
|
private Integer legNumber;
|
||||||
|
|
||||||
|
//Mark Rounding
|
||||||
|
private Integer currentMarkSeqID;
|
||||||
|
private Boolean hasEnteredRoundingZone;
|
||||||
|
private Mark closestCurrentMark;
|
||||||
|
private Boolean hasPassedLine;
|
||||||
|
private Boolean hasPassedThroughGate;
|
||||||
|
|
||||||
|
|
||||||
|
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||||
|
String boatName, String country) {
|
||||||
|
this.boatType = boatType;
|
||||||
|
this.boatStatus = BoatStatus.PRESTART;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.hullID = hullID;
|
||||||
|
this.shortName = shortName;
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.country = country;
|
||||||
|
this.sailIn = false;
|
||||||
|
this.isAuto = false;
|
||||||
|
this.location = new GeoPoint(57.67046, 11.83751);
|
||||||
|
this.lastLocation = location;
|
||||||
|
this.heading = 120.0; //In degrees
|
||||||
|
this.currentVelocity = 0d; //in mms-1
|
||||||
|
this.currentMarkSeqID = 0;
|
||||||
|
this.legNumber = 0;
|
||||||
|
this.boatColor = Colors.getColor(sourceId - 1);
|
||||||
|
|
||||||
|
this.hasEnteredRoundingZone = false;
|
||||||
|
this.hasPassedLine = false;
|
||||||
|
this.hasPassedThroughGate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the boats current currentVelocity by a set amount, positive or negative
|
||||||
|
*
|
||||||
|
* @param velocityChange The ammount to change the currentVelocity by, in mms-1
|
||||||
|
*/
|
||||||
|
public void changeVelocity(Double velocityChange) {
|
||||||
|
currentVelocity += velocityChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the boat to a new GeoPoint whilst preserving the last location
|
||||||
|
*
|
||||||
|
* @param secondsElapsed The seconds elapsed since the last update of this yacht
|
||||||
|
*/
|
||||||
|
public void updateLocation(Double secondsElapsed) {
|
||||||
|
lastLocation = location;
|
||||||
|
location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocation(GeoPoint geoPoint) {
|
||||||
|
location = geoPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
|
||||||
|
* rounding package.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addObserver(Observer o) {
|
||||||
|
super.addObserver(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the heading of the boat by a given amount, while recording the boats last heading.
|
||||||
|
*
|
||||||
|
* @param amount the amount by which to adjust the boat heading.
|
||||||
|
*/
|
||||||
|
public void adjustHeading(Double amount) {
|
||||||
|
Double newVal = heading + amount;
|
||||||
|
lastHeading = heading;
|
||||||
|
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the boats direction from one side of the wind to the other.
|
||||||
|
*/
|
||||||
|
public void tackGybe(Double windDirection) {
|
||||||
|
if (isAuto) {
|
||||||
|
disableAutoPilot();
|
||||||
|
} else {
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
Double newVal = (-2 * normalizedHeading) + heading;
|
||||||
|
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||||
|
setAutoPilot(newHeading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
|
||||||
|
*
|
||||||
|
* @param thisHeading The heading to move the boat towards.
|
||||||
|
*/
|
||||||
|
private void setAutoPilot(Double thisHeading) {
|
||||||
|
isAuto = true;
|
||||||
|
autoHeading = thisHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the auto pilot function.
|
||||||
|
*/
|
||||||
|
public void disableAutoPilot() {
|
||||||
|
isAuto = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
|
||||||
|
* in the event that the boat is within the range of 1 turn step of its goal.
|
||||||
|
*/
|
||||||
|
public void runAutoPilot() {
|
||||||
|
if (isAuto) {
|
||||||
|
turnTowardsHeading(autoHeading);
|
||||||
|
if (Math.abs(heading - autoHeading)
|
||||||
|
<= TURN_STEP) { //Cancel when within 1 turn step of target.
|
||||||
|
isAuto = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleSailIn() {
|
||||||
|
sailIn = !sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void turnUpwind() {
|
||||||
|
disableAutoPilot();
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
if (normalizedHeading == 0) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading == 180) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading < 180) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void turnDownwind() {
|
||||||
|
disableAutoPilot();
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
if (normalizedHeading == 0) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading == 180) {
|
||||||
|
if (lastHeading < 180) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
}
|
||||||
|
} else if (normalizedHeading < 180) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
|
||||||
|
* and uses this to calculate a heading to move the yacht towards.
|
||||||
|
*/
|
||||||
|
public void turnToVMG() {
|
||||||
|
if (isAuto) {
|
||||||
|
disableAutoPilot();
|
||||||
|
} else {
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
Double optimalHeading;
|
||||||
|
HashMap<Double, Double> optimalPolarMap;
|
||||||
|
|
||||||
|
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
|
||||||
|
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
|
||||||
|
} else {
|
||||||
|
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
|
||||||
|
}
|
||||||
|
optimalHeading = optimalPolarMap.keySet().iterator().next();
|
||||||
|
|
||||||
|
if (normalizedHeading > 180) {
|
||||||
|
optimalHeading = 360 - optimalHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take optimal heading and turn into a boat heading rather than a wind heading.
|
||||||
|
optimalHeading =
|
||||||
|
optimalHeading + GameState.getWindDirection();
|
||||||
|
|
||||||
|
setAutoPilot(optimalHeading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a given heading and rotates the boat towards that heading. This does not care about
|
||||||
|
* being upwind or downwind, just which direction will reach a given heading faster.
|
||||||
|
*
|
||||||
|
* @param newHeading The heading to turn the yacht towards.
|
||||||
|
*/
|
||||||
|
private void turnTowardsHeading(Double newHeading) {
|
||||||
|
Double newVal = heading - newHeading;
|
||||||
|
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
|
||||||
|
adjustHeading(TURN_STEP / 5);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP / 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a heading normalized for the wind direction. Heading direction into the wind is 0,
|
||||||
|
* directly away is 180.
|
||||||
|
*
|
||||||
|
* @return The normalized heading accounting for wind direction.
|
||||||
|
*/
|
||||||
|
private Double normalizeHeading() {
|
||||||
|
Double normalizedHeading = heading - GameState.windDirection;
|
||||||
|
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||||
|
return normalizedHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSourceId() {
|
||||||
|
//@TODO Remove and merge with Creating Game Loop
|
||||||
|
if (sourceId == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
|
||||||
|
public String getHullID() {
|
||||||
|
if (hullID == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return hullID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
|
||||||
|
public String getShortName() {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatName() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCountry() {
|
||||||
|
if (country == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatName(String name) {
|
||||||
|
boatName = name;
|
||||||
|
shortName = name.split(" ")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeoPoint getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeading(Double heading) {
|
||||||
|
this.heading = heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSailIn() {
|
||||||
|
return sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCurrentVelocity() {
|
||||||
|
return currentVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentVelocity(Double currentVelocity) {
|
||||||
|
this.currentVelocity = currentVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCurrentMarkSeqID() {
|
||||||
|
return currentMarkSeqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeoPoint getLastLocation() {
|
||||||
|
return lastLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getClosestCurrentMark() {
|
||||||
|
return closestCurrentMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClosestCurrentMark(Mark closestCurrentMark) {
|
||||||
|
this.closestCurrentMark = closestCurrentMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasEnteredRoundingZone(Boolean hasEnteredRoundingZone) {
|
||||||
|
this.hasEnteredRoundingZone = hasEnteredRoundingZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasPassedLine(Boolean hasPassedLine) {
|
||||||
|
this.hasPassedLine = hasPassedLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasPassedThroughGate(Boolean hasPassedThroughGate) {
|
||||||
|
this.hasPassedThroughGate = hasPassedThroughGate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoatStatus getBoatStatus() {
|
||||||
|
return boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatStatus(BoatStatus boatStatus) {
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incrementMarkSeqID() {
|
||||||
|
currentMarkSeqID++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasEnteredRoundingZone() {
|
||||||
|
return hasEnteredRoundingZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasPassedThroughGate() {
|
||||||
|
return hasPassedThroughGate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasPassedLine() {
|
||||||
|
return hasPassedLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incrementLegNumber() {
|
||||||
|
legNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLegNumber() {
|
||||||
|
return legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatColor(Color color) {
|
||||||
|
this.boatColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getBoatColor() {
|
||||||
|
return boatColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
public class CompoundMark {
|
||||||
|
|
||||||
|
private int compoundMarkId;
|
||||||
|
private String name;
|
||||||
|
private List<Mark> marks = new ArrayList<>();
|
||||||
|
private GeoPoint midPoint;
|
||||||
|
|
||||||
|
public CompoundMark(int markID, String name, List<Mark> marks) {
|
||||||
|
this.compoundMarkId = markID;
|
||||||
|
this.name = name;
|
||||||
|
this.marks.addAll(marks);
|
||||||
|
if (marks.size() > 1) {
|
||||||
|
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
|
||||||
|
} else {
|
||||||
|
this.midPoint = marks.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out compoundMark's info and its marks, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
String info = String.format(
|
||||||
|
"CompoundMark: %d (%s), [%s", compoundMarkId, name, marks.get(0).toString()
|
||||||
|
);
|
||||||
|
if (marks.size() > 1) {
|
||||||
|
info += String.format(", %s", marks.get(1).toString());
|
||||||
|
}
|
||||||
|
return info + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return compoundMarkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId (int markID) {
|
||||||
|
this.compoundMarkId = markID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingSide(RoundingSide roundingSide) {;
|
||||||
|
switch (roundingSide) {
|
||||||
|
case SP:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||||
|
getSubMark(2).setRoundingSide(RoundingSide.PORT);
|
||||||
|
break;
|
||||||
|
case PS:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.PORT);
|
||||||
|
getSubMark(2).setRoundingSide(RoundingSide.STARBOARD);
|
||||||
|
break;
|
||||||
|
case PORT:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.PORT);
|
||||||
|
break;
|
||||||
|
case STARBOARD:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mark contained in the compound mark. Marks are numbered 1 to n;
|
||||||
|
* @param singleMarkId the id of the desired mark contained in this compound mark.
|
||||||
|
* @return the desired mark. Returns null if the ID is not in range (1, NUM_MARKS)
|
||||||
|
*/
|
||||||
|
public Mark getSubMark(int singleMarkId) {
|
||||||
|
try {
|
||||||
|
return marks.get(singleMarkId - 1);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
|
||||||
|
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
|
||||||
|
*
|
||||||
|
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
|
||||||
|
*/
|
||||||
|
public GeoPoint getMidPoint() {
|
||||||
|
return midPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
|
||||||
|
* specific singleMark or the list of marks.
|
||||||
|
*
|
||||||
|
* @return True if the compound mark is a gate, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isGate () {
|
||||||
|
return marks.size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of marks in the compoundMark
|
||||||
|
*
|
||||||
|
* @return All marks contained in this mark.
|
||||||
|
*/
|
||||||
|
public List<Mark> getMarks () {
|
||||||
|
return marks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 0;
|
||||||
|
for (Mark mark : marks) {
|
||||||
|
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
|
||||||
|
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
|
||||||
|
}
|
||||||
|
return hash + getName().hashCode() + Integer.hashCode(getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the data for the cornering of a mark.
|
||||||
|
*/
|
||||||
|
public class Corner {
|
||||||
|
|
||||||
|
private Integer seqID;
|
||||||
|
private Integer compoundMarkID;
|
||||||
|
private String rounding;
|
||||||
|
private Integer zoneSize;
|
||||||
|
|
||||||
|
public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
this.compoundMarkID = compoundMarkID;
|
||||||
|
this.rounding = rounding;
|
||||||
|
this.zoneSize = zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCompoundMarkID() {
|
||||||
|
return compoundMarkID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRounding() {
|
||||||
|
return rounding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getZoneSize() {
|
||||||
|
return zoneSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to represent general marks
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public class Mark extends GeoPoint {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PositionListener {
|
||||||
|
void notifyPositionChange(Mark mark, double lat, double lon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int seqID;
|
||||||
|
private String name;
|
||||||
|
private int sourceID;
|
||||||
|
private List<PositionListener> positionListeners = new ArrayList<>();
|
||||||
|
private RoundingSide roundingSide;
|
||||||
|
|
||||||
|
public Mark(String name, int seqID, double lat, double lng, int sourceID) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.name = name;
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, source: %d, lat: %f, lng: %f", seqID, name, sourceID, getLat(), getLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundingSide getRoundingSide() {
|
||||||
|
return roundingSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingSide(RoundingSide roundingSide) {
|
||||||
|
this.roundingSide = roundingSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceID(int sourceID) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePosition (double lat, double lon) {
|
||||||
|
this.setLat(lat);
|
||||||
|
this.setLng(lon);
|
||||||
|
for (PositionListener listener : positionListeners) {
|
||||||
|
listener.notifyPositionChange(this, lat, lon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPositionListener (PositionListener listener) {
|
||||||
|
positionListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePositionListener (PositionListener listener) {
|
||||||
|
positionListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.stream.xml.generator.Race;
|
||||||
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
import seng302.utilities.XMLParser;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to hold the order of the marks in the race.
|
||||||
|
*/
|
||||||
|
public class MarkOrder {
|
||||||
|
private List<CompoundMark> raceMarkOrder;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
|
||||||
|
private Set<Mark> allMarks;
|
||||||
|
|
||||||
|
public MarkOrder(){
|
||||||
|
loadRaceProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return An ordered list of marks in the race
|
||||||
|
* OR null if the mark order could not be loaded
|
||||||
|
*/
|
||||||
|
public List<CompoundMark> getMarkOrder() {
|
||||||
|
if (raceMarkOrder == null){
|
||||||
|
logger.warn("Race order accessed but not instantiated");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(raceMarkOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param seqID The seqID of the current mark the boat is heading to
|
||||||
|
* @return A Boolean indicating if this coming mark is the last one (finish line)
|
||||||
|
*/
|
||||||
|
public Boolean isLastMark(Integer seqID) {
|
||||||
|
return seqID == raceMarkOrder.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param currentSeqID The seqID of the current mark the boat is heading to
|
||||||
|
* @return The mark last passed
|
||||||
|
* @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
|
||||||
|
*/
|
||||||
|
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
||||||
|
return raceMarkOrder.get(currentSeqID - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark getCurrentMark(Integer currentSeqID) {
|
||||||
|
return raceMarkOrder.get(currentSeqID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param currentSeqID The seqID of the current mark the boat is heading to
|
||||||
|
* @return The mark following the mark that the boat is heading to
|
||||||
|
* @throws IndexOutOfBoundsException if there is no next mark. Check using {@link
|
||||||
|
* #isLastMark(Integer)}
|
||||||
|
*/
|
||||||
|
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
||||||
|
return raceMarkOrder.get(currentSeqID + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Mark> getAllMarks(){
|
||||||
|
return Collections.unmodifiableSet(allMarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the race order from an XML string
|
||||||
|
* @param xml An AC35 RaceXML
|
||||||
|
* @return An ordered list of marks in the race
|
||||||
|
*/
|
||||||
|
private List<CompoundMark> loadRaceOrderFromXML(String xml) {
|
||||||
|
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder db;
|
||||||
|
Document doc;
|
||||||
|
allMarks = new HashSet<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
db = dbf.newDocumentBuilder();
|
||||||
|
doc = db.parse(new InputSource(new StringReader(xml)));
|
||||||
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
|
logger.error("Failed to read generated race XML");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RaceXMLData data = XMLParser.parseRace(doc);
|
||||||
|
|
||||||
|
if (data != null){
|
||||||
|
logger.debug("Loaded RaceXML for mark order");
|
||||||
|
List<Corner> corners = data.getMarkSequence();
|
||||||
|
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
|
||||||
|
List<CompoundMark> course = new ArrayList<>();
|
||||||
|
for (Corner corner : corners){
|
||||||
|
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
|
||||||
|
compoundMark.setRoundingSide(
|
||||||
|
RoundingSide.getRoundingSide(corner.getRounding())
|
||||||
|
);
|
||||||
|
course.add(compoundMark);
|
||||||
|
allMarks.addAll(compoundMark.getMarks());
|
||||||
|
}
|
||||||
|
|
||||||
|
return course;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the raceXML and mark order
|
||||||
|
*/
|
||||||
|
private void loadRaceProperties(){
|
||||||
|
XMLGenerator generator = new XMLGenerator();
|
||||||
|
|
||||||
|
generator.setRace(new Race());
|
||||||
|
|
||||||
|
String raceXML = generator.getRaceAsXml();
|
||||||
|
|
||||||
|
if (raceXML == null){
|
||||||
|
logger.error("Failed to generate raceXML (for race properties)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
raceMarkOrder = loadRaceOrderFromXML(raceXML);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-8
@@ -1,13 +1,12 @@
|
|||||||
package seng302.models.stream.packets;
|
package seng302.model.stream.packets;
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Kusal on 4/24/2017.
|
|
||||||
*/
|
|
||||||
public enum PacketType {
|
public enum PacketType {
|
||||||
HEARTBEAT,
|
HEARTBEAT,
|
||||||
RACE_STATUS,
|
RACE_STATUS,
|
||||||
DISPLAY_TEXT_MESSAGE,
|
DISPLAY_TEXT_MESSAGE,
|
||||||
XML_MESSAGE,
|
RACE_XML,
|
||||||
|
REGATTA_XML,
|
||||||
|
BOAT_XML,
|
||||||
RACE_START_STATUS,
|
RACE_START_STATUS,
|
||||||
YACHT_EVENT_CODE,
|
YACHT_EVENT_CODE,
|
||||||
YACHT_ACTION_CODE,
|
YACHT_ACTION_CODE,
|
||||||
@@ -16,9 +15,14 @@ public enum PacketType {
|
|||||||
MARK_ROUNDING,
|
MARK_ROUNDING,
|
||||||
COURSE_WIND,
|
COURSE_WIND,
|
||||||
AVG_WIND,
|
AVG_WIND,
|
||||||
OTHER;
|
BOAT_ACTION,
|
||||||
|
OTHER,
|
||||||
|
RACE_REGISTRATION_REQUEST,
|
||||||
|
RACE_REGISTRATION_RESPONSE,
|
||||||
|
RACE_CUSTOMIZATION_REQUEST,
|
||||||
|
RACE_CUSTOMIZATION_RESPONSE;
|
||||||
|
|
||||||
public static PacketType assignPacketType(int packetType){
|
public static PacketType assignPacketType(int packetType, byte[] payload){
|
||||||
switch(packetType){
|
switch(packetType){
|
||||||
case 1:
|
case 1:
|
||||||
return HEARTBEAT;
|
return HEARTBEAT;
|
||||||
@@ -27,7 +31,14 @@ public enum PacketType {
|
|||||||
case 20:
|
case 20:
|
||||||
return DISPLAY_TEXT_MESSAGE;
|
return DISPLAY_TEXT_MESSAGE;
|
||||||
case 26:
|
case 26:
|
||||||
return XML_MESSAGE;
|
switch (payload[9]) { //The type of XML message
|
||||||
|
case 5:
|
||||||
|
return REGATTA_XML;
|
||||||
|
case 6:
|
||||||
|
return RACE_XML;
|
||||||
|
case 7:
|
||||||
|
return BOAT_XML;
|
||||||
|
}
|
||||||
case 27:
|
case 27:
|
||||||
return RACE_START_STATUS;
|
return RACE_START_STATUS;
|
||||||
case 29:
|
case 29:
|
||||||
@@ -44,6 +55,16 @@ public enum PacketType {
|
|||||||
return COURSE_WIND;
|
return COURSE_WIND;
|
||||||
case 47:
|
case 47:
|
||||||
return AVG_WIND;
|
return AVG_WIND;
|
||||||
|
case 100:
|
||||||
|
return BOAT_ACTION;
|
||||||
|
case 101:
|
||||||
|
return RACE_REGISTRATION_REQUEST;
|
||||||
|
case 102:
|
||||||
|
return RACE_REGISTRATION_RESPONSE;
|
||||||
|
case 103:
|
||||||
|
return RACE_CUSTOMIZATION_REQUEST;
|
||||||
|
case 104:
|
||||||
|
return RACE_CUSTOMIZATION_RESPONSE;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return OTHER;
|
return OTHER;
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package seng302.models.stream.packets;
|
package seng302.model.stream.packets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by kre39 on 23/04/17.
|
* Created by kre39 on 23/04/17.
|
||||||
@@ -13,7 +13,7 @@ public class StreamPacket {
|
|||||||
private byte[] payload;
|
private byte[] payload;
|
||||||
|
|
||||||
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
|
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
|
||||||
this.type = PacketType.assignPacketType(type);
|
this.type = PacketType.assignPacketType(type, payload);
|
||||||
this.messageLength = messageLength;
|
this.messageLength = messageLength;
|
||||||
this.timeStamp = timeStamp;
|
this.timeStamp = timeStamp;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package seng302.model.stream.parser;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple data wrapper for mark rounding data packet.
|
||||||
|
*/
|
||||||
|
public class MarkRoundingData {
|
||||||
|
|
||||||
|
private int boatId;
|
||||||
|
private int markId;
|
||||||
|
private int roundingSide;
|
||||||
|
private long timeStamp;
|
||||||
|
|
||||||
|
public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
|
||||||
|
this.boatId = boatId;
|
||||||
|
this.markId = markId;
|
||||||
|
this.roundingSide = roundingSide;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBoatId() {
|
||||||
|
return boatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMarkId() {
|
||||||
|
return markId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoundingSide() {
|
||||||
|
return roundingSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package seng302.model.stream.parser;
|
||||||
|
|
||||||
|
public class PositionUpdateData {
|
||||||
|
|
||||||
|
public enum DeviceType {
|
||||||
|
YACHT_TYPE,
|
||||||
|
MARK_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
private int deviceId;
|
||||||
|
private DeviceType type;
|
||||||
|
private double lat;
|
||||||
|
private double lon;
|
||||||
|
private double heading;
|
||||||
|
private double groundSpeed;
|
||||||
|
|
||||||
|
public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
|
||||||
|
double heading, double groundSpeed) {
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.type = type;
|
||||||
|
this.lat = lat;
|
||||||
|
this.lon = lon;
|
||||||
|
this.heading = heading;
|
||||||
|
this.groundSpeed = groundSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeviceId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLon() {
|
||||||
|
return lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getGroundSpeed() {
|
||||||
|
return groundSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package seng302.model.stream.parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for storing data parsed from race start status packet
|
||||||
|
*/
|
||||||
|
public class RaceStartData {
|
||||||
|
|
||||||
|
private long raceId;
|
||||||
|
private long raceStartTime;
|
||||||
|
private int notificationType;
|
||||||
|
private long timeStamp;
|
||||||
|
|
||||||
|
public RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) {
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.raceStartTime = raceStartTime;
|
||||||
|
this.notificationType = notificationType;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRaceId() {
|
||||||
|
return raceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRaceStartTime() {
|
||||||
|
return raceStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNotificationType() {
|
||||||
|
return notificationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package seng302.model.stream.parser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores parsed data from race status packets
|
||||||
|
*/
|
||||||
|
public class RaceStatusData {
|
||||||
|
|
||||||
|
//CONVERSION CONSTANTS
|
||||||
|
private static final double WIND_DIR_FACTOR = 0x4000 / 90; //0x4000 is 90 degrees
|
||||||
|
private static final double MS_TO_KNOTS = 1.94384;
|
||||||
|
|
||||||
|
private double windDirection;
|
||||||
|
private double windSpeed;
|
||||||
|
private boolean raceStarted = false;
|
||||||
|
private long currentTime;
|
||||||
|
private long expectedStartTime;
|
||||||
|
private List<long[]> boatData = new ArrayList<>();
|
||||||
|
|
||||||
|
public RaceStatusData(
|
||||||
|
long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) {
|
||||||
|
|
||||||
|
windDirection = windDir / WIND_DIR_FACTOR;
|
||||||
|
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
|
||||||
|
raceStarted = raceStatus == 3;
|
||||||
|
this.currentTime = currentTime;
|
||||||
|
this.expectedStartTime = expectedStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg, int boatStatus) {
|
||||||
|
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg, boatStatus});
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getWindSpeed() {
|
||||||
|
return windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRaceStarted() {
|
||||||
|
return raceStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCurrentTime() {
|
||||||
|
return currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpectedStartTime() {
|
||||||
|
return expectedStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data for boats collected form race status packets.
|
||||||
|
*
|
||||||
|
* @return A list of boat data. Boat data is in the form
|
||||||
|
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
|
||||||
|
*/
|
||||||
|
public List<long[]> getBoatData () {
|
||||||
|
return boatData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package seng302.model.stream.parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores parsed data from yacht event code packet
|
||||||
|
*/
|
||||||
|
public class YachtEventData {
|
||||||
|
private Long subjectId;
|
||||||
|
private Long incidentId;
|
||||||
|
private Integer eventId;
|
||||||
|
private Long timeStamp;
|
||||||
|
|
||||||
|
public YachtEventData(Long subjectId, Long incidentId, Integer eventId, Long timeStamp) {
|
||||||
|
this.subjectId = subjectId;
|
||||||
|
this.incidentId = incidentId;
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSubjectId() {
|
||||||
|
return subjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getIncidentId() {
|
||||||
|
return incidentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getEventId() {
|
||||||
|
return eventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.model.stream.xml.generator;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Race object that can be parsed into XML
|
||||||
|
*/
|
||||||
|
public class Race {
|
||||||
|
|
||||||
|
private List<ServerYacht> yachts;
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
public Race(){
|
||||||
|
yachts = new ArrayList<>();
|
||||||
|
startTime = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a boat to the race
|
||||||
|
* @param yacht The boat to add
|
||||||
|
*/
|
||||||
|
public void addBoat(ServerYacht yacht) {
|
||||||
|
yachts.add(yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of boats in the race
|
||||||
|
* @return A List of boats
|
||||||
|
*/
|
||||||
|
public List<ServerYacht> getBoats() {
|
||||||
|
return Collections.unmodifiableList(yachts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time until the race starts
|
||||||
|
* @param seconds The time in seconds until the race starts
|
||||||
|
*/
|
||||||
|
public void setRaceStartDelay(Integer seconds){
|
||||||
|
startTime = startTime.plusMinutes(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time the race starts
|
||||||
|
* @return The time the race starts
|
||||||
|
*/
|
||||||
|
public String getRaceStartTime(){
|
||||||
|
return startTime.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package seng302.model.stream.xml.generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Race regatta that can be parsed into XML
|
||||||
|
*/
|
||||||
|
public class Regatta {
|
||||||
|
private final Double DEFAULT_ALTITUDE = 0d;
|
||||||
|
private final Integer DEFAULT_REGATTA_ID = 0;
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private String courseName;
|
||||||
|
|
||||||
|
private Double latitude;
|
||||||
|
private Double longitude;
|
||||||
|
private Double altitude;
|
||||||
|
|
||||||
|
private Integer utcOffset;
|
||||||
|
private Double magneticVariation;
|
||||||
|
|
||||||
|
public Regatta(String name, String courseName, Double latitude, Double longitude) {
|
||||||
|
this.name = name;
|
||||||
|
this.id = DEFAULT_REGATTA_ID;
|
||||||
|
this.courseName = courseName;
|
||||||
|
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
this.altitude = DEFAULT_ALTITUDE;
|
||||||
|
|
||||||
|
this.utcOffset = 0;
|
||||||
|
this.magneticVariation = 0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMagneticVariation(Double magneticVariation){
|
||||||
|
this.magneticVariation = magneticVariation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUtcOffset(Integer offset){
|
||||||
|
this.utcOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NOTE!! The following getters must follow the JavaBean standard (getPropertyName()), and must be public.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public String getName(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCourseName(){
|
||||||
|
return courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRegattaId(){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getLatitude() {
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getLongitude() {
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getAltitude() {
|
||||||
|
return altitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUtcOffset(){
|
||||||
|
return utcOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getMagneticVariation(){
|
||||||
|
return magneticVariation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package seng302.model.stream.xml.parser;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import seng302.model.Limit;
|
||||||
|
import seng302.model.mark.CompoundMark;
|
||||||
|
import seng302.model.mark.Corner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a Document object containing race data in XML format and stores the data.
|
||||||
|
*/
|
||||||
|
public class RaceXMLData {
|
||||||
|
|
||||||
|
private List<Integer> participants;
|
||||||
|
private Map<Integer, CompoundMark> compoundMarks;
|
||||||
|
private List<Corner> markSequence;
|
||||||
|
private List<Limit> courseLimit;
|
||||||
|
|
||||||
|
public RaceXMLData(List<Integer> participants, List<CompoundMark> compoundMarks,
|
||||||
|
List<Corner> markSequence, List<Limit> courseLimit) {
|
||||||
|
this.participants = participants;
|
||||||
|
this.markSequence = markSequence;
|
||||||
|
this.courseLimit = courseLimit;
|
||||||
|
this.compoundMarks = new HashMap<>();
|
||||||
|
for (CompoundMark cMark : compoundMarks) {
|
||||||
|
this.compoundMarks.put(cMark.getId(), cMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getParticipants() {
|
||||||
|
return participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, CompoundMark> getCompoundMarks() {
|
||||||
|
return compoundMarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Corner> getMarkSequence() {
|
||||||
|
return markSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Limit> getCourseLimit() {
|
||||||
|
return courseLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package seng302.model.stream.xml.parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores data from regatta xml packet.
|
||||||
|
*/
|
||||||
|
public class RegattaXMLData {
|
||||||
|
//Regatta Info
|
||||||
|
private Integer regattaID;
|
||||||
|
private String regattaName;
|
||||||
|
private String courseName;
|
||||||
|
private Double centralLat;
|
||||||
|
private Double centralLng;
|
||||||
|
private Integer utcOffset;
|
||||||
|
|
||||||
|
public RegattaXMLData (Integer regattaID, String regattaName, String courseName,
|
||||||
|
Double centralLat, Double centralLng, Integer utcOffset) {
|
||||||
|
this.regattaID = regattaID;
|
||||||
|
this.regattaName = regattaName;
|
||||||
|
this.courseName = courseName;
|
||||||
|
this.centralLat = centralLat;
|
||||||
|
this.centralLng = centralLng;
|
||||||
|
this.utcOffset = utcOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRegattaID() {
|
||||||
|
return regattaID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegattaName() {
|
||||||
|
return regattaName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCourseName() {
|
||||||
|
return courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCentralLat() {
|
||||||
|
return centralLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCentralLng() {
|
||||||
|
return centralLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUtcOffset() {
|
||||||
|
return utcOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,509 +0,0 @@
|
|||||||
package seng302.models;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import javafx.event.EventHandler;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.CacheHint;
|
|
||||||
import javafx.scene.Group;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.shape.Line;
|
|
||||||
import javafx.scene.shape.Polygon;
|
|
||||||
import javafx.scene.text.Text;
|
|
||||||
import javafx.scene.transform.Rotate;
|
|
||||||
import seng302.GeometryUtils;
|
|
||||||
import seng302.controllers.CanvasController;
|
|
||||||
import seng302.models.mark.GateMark;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
|
|
||||||
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
|
|
||||||
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
|
|
||||||
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
|
|
||||||
* minimized in which case it attempts to store animations and apply them when the window is
|
|
||||||
* maximised.
|
|
||||||
*/
|
|
||||||
public class BoatGroup extends Group {
|
|
||||||
|
|
||||||
//Constants for drawing
|
|
||||||
private static final double TEAMNAME_X_OFFSET = 10d;
|
|
||||||
private static final double TEAMNAME_Y_OFFSET = -29d;
|
|
||||||
private static final double VELOCITY_X_OFFSET = 10d;
|
|
||||||
private static final double VELOCITY_Y_OFFSET = -17d;
|
|
||||||
private static final double ESTTIMETONEXTMARK_X_OFFSET = 10d;
|
|
||||||
private static final double ESTTIMETONEXTMARK_Y_OFFSET = -5d;
|
|
||||||
private static final double LEGTIME_X_OFFSET = 10d;
|
|
||||||
private static final double LEGTIME_Y_OFFSET = 7d;
|
|
||||||
private static final double BOAT_HEIGHT = 15d;
|
|
||||||
private static final double BOAT_WIDTH = 10d;
|
|
||||||
//Variables for boat logic.
|
|
||||||
private boolean isStopped = true;
|
|
||||||
private double xIncrement;
|
|
||||||
private double yIncrement;
|
|
||||||
private long lastTimeValid = 0;
|
|
||||||
private Double lastRotation = 0.0;
|
|
||||||
private long framesToMove;
|
|
||||||
//Graphical objects
|
|
||||||
private Yacht boat;
|
|
||||||
private Group lineGroup = new Group();
|
|
||||||
private Polygon boatPoly;
|
|
||||||
private Text teamNameObject;
|
|
||||||
private Text velocityObject;
|
|
||||||
private Text estTimeToNextMarkObject;
|
|
||||||
private Text legTimeObject;
|
|
||||||
private Wake wake;
|
|
||||||
private Line leftLayLine;
|
|
||||||
private Line rightLayline;
|
|
||||||
private Double distanceTravelled = 0.0;
|
|
||||||
private Point2D lastPoint;
|
|
||||||
private boolean destinationSet;
|
|
||||||
private Color textColor = Color.RED;
|
|
||||||
|
|
||||||
private Boolean isSelected = true; //All boats are initalised as selected
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a BoatGroup with the default triangular boat polygon.
|
|
||||||
*
|
|
||||||
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
|
|
||||||
* to tell which BoatGroup to update.
|
|
||||||
* @param color The colour of the boat polygon and the trailing line.
|
|
||||||
*/
|
|
||||||
public BoatGroup(Yacht boat, Color color) {
|
|
||||||
this.boat = boat;
|
|
||||||
initChildren(color);
|
|
||||||
this.textColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
|
|
||||||
* at point (0,0).
|
|
||||||
*
|
|
||||||
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
|
|
||||||
* to tell which BoatGroup to update.
|
|
||||||
* @param color The colour of the boat polygon and the trailing line.
|
|
||||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
|
||||||
* polygon.
|
|
||||||
*/
|
|
||||||
public BoatGroup(Yacht boat, Color color, double... points) {
|
|
||||||
this.boat = boat;
|
|
||||||
initChildren(color, points);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a text object with caching and a color applied
|
|
||||||
*
|
|
||||||
* @param defaultText The default text to display
|
|
||||||
* @param fill The text fill color
|
|
||||||
* @return The text object
|
|
||||||
*/
|
|
||||||
private Text getTextObject(String defaultText, Color fill) {
|
|
||||||
Text text = new Text(defaultText);
|
|
||||||
|
|
||||||
text.setFill(fill);
|
|
||||||
text.setCacheHint(CacheHint.SPEED);
|
|
||||||
text.setCache(true);
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the javafx objects that will be the in the group by default.
|
|
||||||
*
|
|
||||||
* @param color The colour of the boat polygon and the trailing line.
|
|
||||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
|
||||||
* polygon.
|
|
||||||
*/
|
|
||||||
private void initChildren(Color color, double... points) {
|
|
||||||
textColor = color;
|
|
||||||
destinationSet = false;
|
|
||||||
|
|
||||||
boatPoly = new Polygon(points);
|
|
||||||
boatPoly.setFill(color);
|
|
||||||
boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE));
|
|
||||||
boatPoly.setOnMouseExited(event -> boatPoly.setFill(color));
|
|
||||||
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
|
||||||
boatPoly.setCache(true);
|
|
||||||
boatPoly.setCacheHint(CacheHint.SPEED);
|
|
||||||
|
|
||||||
teamNameObject = getTextObject(boat.getShortName(), textColor);
|
|
||||||
velocityObject = getTextObject(boat.getVelocity().toString(), textColor);
|
|
||||||
|
|
||||||
teamNameObject.setX(TEAMNAME_X_OFFSET);
|
|
||||||
teamNameObject.setY(TEAMNAME_Y_OFFSET);
|
|
||||||
teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY());
|
|
||||||
|
|
||||||
velocityObject.setX(VELOCITY_X_OFFSET);
|
|
||||||
velocityObject.setY(VELOCITY_Y_OFFSET);
|
|
||||||
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
|
|
||||||
|
|
||||||
updateLastMarkRoundingTime();
|
|
||||||
updateTimeTillNextMark();
|
|
||||||
|
|
||||||
if (estTimeToNextMarkObject != null) {
|
|
||||||
estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET);
|
|
||||||
estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET);
|
|
||||||
estTimeToNextMarkObject
|
|
||||||
.relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (legTimeObject != null) {
|
|
||||||
legTimeObject.setX(LEGTIME_X_OFFSET);
|
|
||||||
legTimeObject.setY(LEGTIME_Y_OFFSET);
|
|
||||||
legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
leftLayLine = new Line();
|
|
||||||
rightLayline = new Line();
|
|
||||||
|
|
||||||
wake = new Wake(0, -BOAT_HEIGHT);
|
|
||||||
super.getChildren()
|
|
||||||
.addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject,
|
|
||||||
legTimeObject, leftLayLine, rightLayline);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the javafx objects that will be the in the group by default.
|
|
||||||
*
|
|
||||||
* @param color The colour of the boat polygon and the trailing line.
|
|
||||||
*/
|
|
||||||
private void initChildren(Color color) {
|
|
||||||
initChildren(color,
|
|
||||||
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
|
||||||
0.0, -BOAT_HEIGHT / 2,
|
|
||||||
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the boat and its children annotations from its current coordinates by specified
|
|
||||||
* amounts.
|
|
||||||
*
|
|
||||||
* @param dx The amount to move the X coordinate by
|
|
||||||
* @param dy The amount to move the Y coordinate by
|
|
||||||
*/
|
|
||||||
private void moveGroupBy(double dx, double dy) {
|
|
||||||
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
|
|
||||||
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
|
|
||||||
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
|
|
||||||
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
|
|
||||||
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
|
|
||||||
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
|
|
||||||
estTimeToNextMarkObject.setLayoutX(estTimeToNextMarkObject.getLayoutX() + dx);
|
|
||||||
estTimeToNextMarkObject.setLayoutY(estTimeToNextMarkObject.getLayoutY() + dy);
|
|
||||||
legTimeObject.setLayoutX(legTimeObject.getLayoutX() + dx);
|
|
||||||
legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy);
|
|
||||||
wake.setLayoutX(wake.getLayoutX() + dx);
|
|
||||||
wake.setLayoutY(wake.getLayoutY() + dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
private void moveTo(double x, double y, double rotation) {
|
|
||||||
rotateTo(rotation);
|
|
||||||
boatPoly.setLayoutX(x);
|
|
||||||
boatPoly.setLayoutY(y);
|
|
||||||
teamNameObject.setLayoutX(x);
|
|
||||||
teamNameObject.setLayoutY(y);
|
|
||||||
velocityObject.setLayoutX(x);
|
|
||||||
velocityObject.setLayoutY(y);
|
|
||||||
estTimeToNextMarkObject.setLayoutX(x);
|
|
||||||
estTimeToNextMarkObject.setLayoutY(y);
|
|
||||||
legTimeObject.setLayoutX(x);
|
|
||||||
legTimeObject.setLayoutY(y);
|
|
||||||
wake.setLayoutX(x);
|
|
||||||
wake.setLayoutY(y);
|
|
||||||
wake.rotate(rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rotateTo(double rotation) {
|
|
||||||
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the time until next mark label, will create a label if one doesn't exist
|
|
||||||
*/
|
|
||||||
private void updateTimeTillNextMark() {
|
|
||||||
if (estTimeToNextMarkObject == null) {
|
|
||||||
estTimeToNextMarkObject = getTextObject("Next mark: -", textColor);
|
|
||||||
}
|
|
||||||
if (boat.getEstimateTimeAtNextMark() != null) {
|
|
||||||
DateFormat format = new SimpleDateFormat("mm:ss");
|
|
||||||
String timeToNextMark = format
|
|
||||||
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
|
|
||||||
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
|
|
||||||
} else {
|
|
||||||
estTimeToNextMarkObject.setText("Next mark: -");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the time since last mark rounding, will create a label if one doesn't exist
|
|
||||||
*/
|
|
||||||
private void updateLastMarkRoundingTime() {
|
|
||||||
if (legTimeObject == null) {
|
|
||||||
legTimeObject = getTextObject("Last mark: -", textColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boat.getMarkRoundingTime() != null) {
|
|
||||||
DateFormat format = new SimpleDateFormat("mm:ss");
|
|
||||||
String elapsedTime = format
|
|
||||||
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
|
|
||||||
legTimeObject.setText("Last mark: " + elapsedTime);
|
|
||||||
} else {
|
|
||||||
legTimeObject.setText("Last mark: -");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void move() {
|
|
||||||
double dx = xIncrement * framesToMove;
|
|
||||||
double dy = yIncrement * framesToMove;
|
|
||||||
|
|
||||||
distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
|
||||||
moveGroupBy(xIncrement, yIncrement);
|
|
||||||
framesToMove = framesToMove - 1;
|
|
||||||
|
|
||||||
if (framesToMove <= 0) {
|
|
||||||
isStopped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distanceTravelled > 70) {
|
|
||||||
distanceTravelled = 0d;
|
|
||||||
|
|
||||||
if (lastPoint != null) {
|
|
||||||
Line l = new Line(
|
|
||||||
lastPoint.getX(),
|
|
||||||
lastPoint.getY(),
|
|
||||||
boatPoly.getLayoutX(),
|
|
||||||
boatPoly.getLayoutY()
|
|
||||||
);
|
|
||||||
l.getStrokeDashArray().setAll(3d, 7d);
|
|
||||||
l.setStroke(boat.getColour());
|
|
||||||
l.setCache(true);
|
|
||||||
l.setCacheHint(CacheHint.SPEED);
|
|
||||||
lineGroup.getChildren().add(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destinationSet) {
|
|
||||||
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wake.updatePosition(1000 / 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the rotational velocity required to reach the rotationalGoal from the
|
|
||||||
* currentRotation.
|
|
||||||
*/
|
|
||||||
protected Double calculateRotationalVelocity(Double rotationalGoal) {
|
|
||||||
Double rotationalVelocity = 0.0;
|
|
||||||
|
|
||||||
if (Math.abs(rotationalGoal - lastRotation) > 180) {
|
|
||||||
if (rotationalGoal - lastRotation >= 0.0) {
|
|
||||||
rotationalVelocity = ((rotationalGoal - lastRotation) - 360) / 200;
|
|
||||||
} else {
|
|
||||||
rotationalVelocity = (360 + (rotationalGoal - lastRotation)) / 200;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rotationalVelocity = (rotationalGoal - lastRotation) / 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
|
|
||||||
if (Math.abs(rotationalVelocity) > 1) {
|
|
||||||
rotationalVelocity = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rotationalVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the destination of the boat and the headng it should have once it reaches
|
|
||||||
*
|
|
||||||
* @param newXValue The X co-ordinate the boat needs to move to.
|
|
||||||
* @param newYValue The Y co-ordinate the boat needs to move to.
|
|
||||||
* @param rotation Rotation to move graphics to.
|
|
||||||
* @param timeValid the time the position values are valid for
|
|
||||||
*/
|
|
||||||
public void setDestination(double newXValue, double newYValue, double rotation,
|
|
||||||
double groundSpeed, long timeValid, double frameRate, long id) {
|
|
||||||
if (lastTimeValid == 0) {
|
|
||||||
lastTimeValid = timeValid - 200;
|
|
||||||
moveTo(newXValue, newYValue, rotation);
|
|
||||||
}
|
|
||||||
framesToMove = Math.round((frameRate / (1000.0f / (timeValid - lastTimeValid))));
|
|
||||||
double dx = newXValue - boatPoly.getLayoutX();
|
|
||||||
double dy = newYValue - boatPoly.getLayoutY();
|
|
||||||
|
|
||||||
xIncrement = dx / framesToMove;
|
|
||||||
yIncrement = dy / framesToMove;
|
|
||||||
|
|
||||||
destinationSet = true;
|
|
||||||
|
|
||||||
Double rotationalVelocity = calculateRotationalVelocity(rotation);
|
|
||||||
|
|
||||||
updateTimeTillNextMark();
|
|
||||||
updateLastMarkRoundingTime();
|
|
||||||
|
|
||||||
if (Math.abs(rotationalVelocity) > 0.075) {
|
|
||||||
rotationalVelocity = 0.0;
|
|
||||||
wake.rotate(rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
rotateTo(rotation);
|
|
||||||
wake.setRotationalVelocity(rotationalVelocity, groundSpeed);
|
|
||||||
|
|
||||||
velocityObject.setText(String.format("%.2f m/s", groundSpeed));
|
|
||||||
lastTimeValid = timeValid;
|
|
||||||
isStopped = false;
|
|
||||||
|
|
||||||
lastRotation = rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
|
|
||||||
* gates position and the current wind
|
|
||||||
* If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
|
|
||||||
* going up wind, if they are on different sides of the gate, then the boat is going downwind
|
|
||||||
* @param canvasController
|
|
||||||
*/
|
|
||||||
public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) {
|
|
||||||
|
|
||||||
Double windAngle = StreamParser.getWindDirection();
|
|
||||||
GateMark thisGateMark = (GateMark) nextMark;
|
|
||||||
SingleMark nextMark1 = thisGateMark.getSingleMark1();
|
|
||||||
SingleMark nextMark2 = thisGateMark.getSingleMark2();
|
|
||||||
Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
|
|
||||||
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
|
|
||||||
|
|
||||||
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
|
||||||
Point2D windTestPoint = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
|
||||||
|
|
||||||
|
|
||||||
Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
|
||||||
Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
|
|
||||||
boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
|
|
||||||
with the wind.
|
|
||||||
*/
|
|
||||||
if (boatLineFuncResult == windLineFuncResult) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setIsSelected(Boolean isSelected) {
|
|
||||||
this.isSelected = isSelected;
|
|
||||||
setTeamNameObjectVisible(isSelected);
|
|
||||||
setVelocityObjectVisible(isSelected);
|
|
||||||
setLineGroupVisible(isSelected);
|
|
||||||
setWakeVisible(isSelected);
|
|
||||||
setEstTimeToNextMarkObjectVisible(isSelected);
|
|
||||||
setLegTimeObjectVisible(isSelected);
|
|
||||||
setLayLinesVisible(isSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setTeamNameObjectVisible(Boolean visible) {
|
|
||||||
teamNameObject.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVelocityObjectVisible(Boolean visible) {
|
|
||||||
velocityObject.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEstTimeToNextMarkObjectVisible(Boolean visible) {
|
|
||||||
estTimeToNextMarkObject.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLegTimeObjectVisible(Boolean visible) {
|
|
||||||
legTimeObject.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLineGroupVisible(Boolean visible) {
|
|
||||||
lineGroup.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWakeVisible(Boolean visible) {
|
|
||||||
wake.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayLinesVisible(Boolean visible) {
|
|
||||||
leftLayLine.setVisible(visible);
|
|
||||||
rightLayline.setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLaylines(Line line1, Line line2) {
|
|
||||||
this.leftLayLine = line1;
|
|
||||||
this.rightLayline = line2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Line> getLaylines() {
|
|
||||||
ArrayList<Line> laylines = new ArrayList<>();
|
|
||||||
laylines.add(leftLayLine);
|
|
||||||
laylines.add(rightLayline);
|
|
||||||
return laylines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Yacht getBoat() {
|
|
||||||
return boat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
|
|
||||||
*
|
|
||||||
* @return An array containing all ID's associated with this RaceObject.
|
|
||||||
*/
|
|
||||||
public long getRaceId() {
|
|
||||||
return boat.getSourceID();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Due to javaFX limitations annotations associated with a boat that you want to appear below
|
|
||||||
* all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group
|
|
||||||
* of the BoatGroups. This function returns these annotations as a group.
|
|
||||||
*
|
|
||||||
* @return A group containing low priority annotations.
|
|
||||||
*/
|
|
||||||
public Group getLowPriorityAnnotations() {
|
|
||||||
Group group = new Group();
|
|
||||||
group.getChildren().addAll(wake, lineGroup);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getBoatLayoutX() {
|
|
||||||
return boatPoly.getLayoutX();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getBoatLayoutY() {
|
|
||||||
return boatPoly.getLayoutY();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStopped() {
|
|
||||||
return isStopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return boat.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package seng302.models;
|
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for randomly generating colours.
|
|
||||||
*/
|
|
||||||
public enum Colors {
|
|
||||||
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
|
|
||||||
|
|
||||||
static Integer index = 0;
|
|
||||||
|
|
||||||
public static Color getColor() {
|
|
||||||
if (index == 6) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
return Color.valueOf(values()[index++].toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
package seng302.models;
|
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
import seng302.controllers.RaceViewController;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Corner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Yacht class for the racing boat.
|
|
||||||
*
|
|
||||||
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
|
|
||||||
* also done outside Boat class because some old variables are not used anymore.
|
|
||||||
*/
|
|
||||||
public class Yacht {
|
|
||||||
|
|
||||||
// Used in boat group
|
|
||||||
private Color colour;
|
|
||||||
private double velocity;
|
|
||||||
|
|
||||||
private String boatType;
|
|
||||||
private Integer sourceID;
|
|
||||||
private String hullID; //matches HullNum in the XML spec.
|
|
||||||
private String shortName;
|
|
||||||
private String boatName;
|
|
||||||
private String country;
|
|
||||||
// Boat status
|
|
||||||
private Integer boatStatus;
|
|
||||||
private Integer legNumber;
|
|
||||||
private Integer penaltiesAwarded;
|
|
||||||
private Integer penaltiesServed;
|
|
||||||
private Long estimateTimeAtNextMark;
|
|
||||||
private Long estimateTimeAtFinish;
|
|
||||||
private String position;
|
|
||||||
// Mark rounding
|
|
||||||
private Long markRoundingTime;
|
|
||||||
private Mark lastMarkRounded;
|
|
||||||
private Mark nextMark;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in EventTest and RaceTest.
|
|
||||||
*
|
|
||||||
* @param boatName Create a yacht object with name.
|
|
||||||
*/
|
|
||||||
public Yacht(String boatName) {
|
|
||||||
this.boatName = boatName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in BoatGroupTest.
|
|
||||||
*
|
|
||||||
* @param boatName 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 Yacht(String boatName, double boatVelocity, String shortName, int id) {
|
|
||||||
this.boatName = boatName;
|
|
||||||
this.velocity = boatVelocity;
|
|
||||||
this.shortName = shortName;
|
|
||||||
this.sourceID = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Yacht(String boatType, Integer sourceID, String hullID, String shortName,
|
|
||||||
String boatName, String country) {
|
|
||||||
this.boatType = boatType;
|
|
||||||
this.sourceID = sourceID;
|
|
||||||
this.hullID = hullID;
|
|
||||||
this.shortName = shortName;
|
|
||||||
this.boatName = boatName;
|
|
||||||
this.country = country;
|
|
||||||
this.position = "-";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBoatType() {
|
|
||||||
return boatType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSourceID() {
|
|
||||||
return sourceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHullID() {
|
|
||||||
return hullID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShortName() {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBoatName() {
|
|
||||||
return boatName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCountry() {
|
|
||||||
return country;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getBoatStatus() {
|
|
||||||
return boatStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoatStatus(Integer boatStatus) {
|
|
||||||
this.boatStatus = boatStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getLegNumber() {
|
|
||||||
return legNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLegNumber(Integer legNumber) {
|
|
||||||
if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) {
|
|
||||||
RaceViewController.updateYachtPositionSparkline(this, legNumber);
|
|
||||||
}
|
|
||||||
this.legNumber = legNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPenaltiesAwarded() {
|
|
||||||
return penaltiesAwarded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPenaltiesAwarded(Integer penaltiesAwarded) {
|
|
||||||
this.penaltiesAwarded = penaltiesAwarded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPenaltiesServed() {
|
|
||||||
return penaltiesServed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPenaltiesServed(Integer penaltiesServed) {
|
|
||||||
this.penaltiesServed = penaltiesServed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getEstimateTimeAtNextMark() {
|
|
||||||
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
|
||||||
// return format.format(estimateTimeAtNextMark);
|
|
||||||
return estimateTimeAtNextMark;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) {
|
|
||||||
this.estimateTimeAtNextMark = estimateTimeAtNextMark;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEstimateTimeAtFinish() {
|
|
||||||
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
|
||||||
return format.format(estimateTimeAtFinish);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
|
|
||||||
this.estimateTimeAtFinish = estimateTimeAtFinish;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPosition(String position) {
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getColour() {
|
|
||||||
return colour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColour(Color colour) {
|
|
||||||
this.colour = colour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getVelocity() {
|
|
||||||
return velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVelocity(double velocity) {
|
|
||||||
this.velocity = velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getMarkRoundingTime() {
|
|
||||||
return markRoundingTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMarkRoundingTime(Long markRoundingTime) {
|
|
||||||
this.markRoundingTime = markRoundingTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mark getLastMarkRounded() {
|
|
||||||
return lastMarkRounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastMarkRounded(Mark lastMarkRounded) {
|
|
||||||
this.lastMarkRounded = lastMarkRounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return boatName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextMark(Mark nextMark) {
|
|
||||||
this.nextMark = nextMark;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mark getNextMark(){
|
|
||||||
return nextMark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package seng302.models.map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class represent Geo location (latitude, longitude).
|
|
||||||
* Created by Haoming on 15/5/2017
|
|
||||||
*/
|
|
||||||
class MapGeo {
|
|
||||||
|
|
||||||
private double lat, lng;
|
|
||||||
|
|
||||||
MapGeo(double lat, double lng) {
|
|
||||||
this.lat = lat;
|
|
||||||
this.lng = lng;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getLat() {
|
|
||||||
return lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLat(double lat) {
|
|
||||||
this.lat = lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getLng() {
|
|
||||||
return lng;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLng(double lng) {
|
|
||||||
this.lng = lng;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package seng302.models.map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class represent euclidean planar point (x, y)
|
|
||||||
* Created by Haoming on 15/5/2017
|
|
||||||
*/
|
|
||||||
class MapPoint {
|
|
||||||
|
|
||||||
private double x, y;
|
|
||||||
|
|
||||||
MapPoint(double x, double y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getX() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setX(double x) {
|
|
||||||
this.x = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getY() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setY(double y) {
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +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, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude, int compoundMarkID) {
|
|
||||||
super(name, type, latitude, longitude, compoundMarkID);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLongitude(){
|
|
||||||
return (this.getSingleMark1().getLongitude());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,148 +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;
|
|
||||||
private long id;
|
|
||||||
private int compoundMarkID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, int sourceID, int compoundMarkID) {
|
|
||||||
this.name = name;
|
|
||||||
this.markType = markType;
|
|
||||||
this.id = sourceID;
|
|
||||||
this.compoundMarkID = compoundMarkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mark(String name, MarkType markType, double latitude, double longitude, int compoundMarkID) {
|
|
||||||
this.name = name;
|
|
||||||
this.markType = markType;
|
|
||||||
this.latitude = latitude;
|
|
||||||
this.longitude = longitude;
|
|
||||||
this.id = 0;
|
|
||||||
this.compoundMarkID = compoundMarkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculated the heading in radians from first Mark to the second Mark.
|
|
||||||
*
|
|
||||||
* @param pointOne First Mark
|
|
||||||
* @param pointTwo Second Mark
|
|
||||||
* @return Heading in radians
|
|
||||||
*/
|
|
||||||
public static Double calculateHeadingRad(Mark pointOne, Mark pointTwo) {
|
|
||||||
Double longitude1 = pointOne.getLongitude();
|
|
||||||
Double longitude2 = pointTwo.getLongitude();
|
|
||||||
Double latitude1 = pointOne.getLatitude();
|
|
||||||
Double latitude2 = pointTwo.getLatitude();
|
|
||||||
return calculateHeadingRad(latitude1, longitude1, latitude2, longitude2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to
|
|
||||||
* geographical latitude2, longitude 2
|
|
||||||
*
|
|
||||||
* @param longitude1 Longitude of first point in degrees
|
|
||||||
* @param longitude2 Longitude of second point in degrees
|
|
||||||
* @param latitude1 Latitude of first point in degrees
|
|
||||||
* @param latitude2 Latitude of first point in degrees
|
|
||||||
* @return Heading in radians
|
|
||||||
*/
|
|
||||||
public static double calculateHeadingRad(Double latitude1, Double longitude1, Double latitude2,
|
|
||||||
Double longitude2) {
|
|
||||||
latitude1 = Math.toRadians(latitude1);
|
|
||||||
latitude2 = Math.toRadians(latitude2);
|
|
||||||
Double longDiff = Math.toRadians(longitude2 - longitude1);
|
|
||||||
Double y = Math.sin(longDiff) * Math.cos(latitude2);
|
|
||||||
Double x =
|
|
||||||
Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2)
|
|
||||||
* Math.cos(longDiff);
|
|
||||||
return Math.atan2(y, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the distance in meters from the first Mark to a second Mark
|
|
||||||
*
|
|
||||||
* @param pointOne First Mark
|
|
||||||
* @param pointTwo Second Mark
|
|
||||||
* @return Distance in meters
|
|
||||||
*/
|
|
||||||
public static Double calculateDistance(Mark pointOne, Mark pointTwo) {
|
|
||||||
Double longitude1 = pointOne.getLongitude();
|
|
||||||
Double longitude2 = pointTwo.getLongitude();
|
|
||||||
Double latitude1 = pointOne.getLatitude();
|
|
||||||
Double latitude2 = pointTwo.getLatitude();
|
|
||||||
return calculateDistance(latitude1, longitude1, latitude2, longitude2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to
|
|
||||||
* geographical latitude2, longitude 2
|
|
||||||
*
|
|
||||||
* @param longitude1 Longitude of first point in degrees
|
|
||||||
* @param longitude2 Longitude of second point in degrees
|
|
||||||
* @param latitude1 Latitude of first point in degrees
|
|
||||||
* @param latitude2 Latitude of first point in degrees
|
|
||||||
* @return Distance in meters
|
|
||||||
*/
|
|
||||||
public static Double calculateDistance(Double latitude1, Double longitude1, Double latitude2,
|
|
||||||
Double longitude2) {
|
|
||||||
Double theta = longitude1 - longitude2;
|
|
||||||
Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) +
|
|
||||||
Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) *
|
|
||||||
Math.cos(Math.toRadians(theta));
|
|
||||||
dist = Math.acos(dist);
|
|
||||||
dist = Math.toDegrees(dist);
|
|
||||||
dist = dist * 60
|
|
||||||
* 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
|
|
||||||
dist = dist * 1609.344; //ratio of miles to metres
|
|
||||||
return dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCompoundMarkID() {
|
|
||||||
return compoundMarkID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
package seng302.models.mark;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.Group;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.shape.Circle;
|
|
||||||
import javafx.scene.shape.Line;
|
|
||||||
import seng302.GeometryUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grouping of javaFX objects needed to represent a Mark on screen.
|
|
||||||
*/
|
|
||||||
public class MarkGroup extends Group {
|
|
||||||
|
|
||||||
private static int MARK_RADIUS = 5;
|
|
||||||
private static int LINE_THICKNESS = 2;
|
|
||||||
private static double DASHED_GAP_LEN = 2d;
|
|
||||||
private static double DASHED_LINE_LEN = 5d;
|
|
||||||
|
|
||||||
private List<Mark> marks = new ArrayList<>();
|
|
||||||
private Mark mainMark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for singleMark groups
|
|
||||||
* @param mark
|
|
||||||
* @param points
|
|
||||||
*/
|
|
||||||
public MarkGroup (SingleMark mark, Point2D points) {
|
|
||||||
marks.add(mark);
|
|
||||||
mainMark = mark;
|
|
||||||
Color color = Color.BLACK;
|
|
||||||
if (mark.getName().equals("Start")){
|
|
||||||
color = Color.GREEN;
|
|
||||||
} else if (mark.getName().equals("Finish")){
|
|
||||||
color = Color.RED;
|
|
||||||
}
|
|
||||||
Circle markCircle;
|
|
||||||
markCircle = new Circle(
|
|
||||||
points.getX(),
|
|
||||||
points.getY(),
|
|
||||||
MARK_RADIUS,
|
|
||||||
color
|
|
||||||
);
|
|
||||||
super.getChildren().add(markCircle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLaylines(Line line1, Line line2) {
|
|
||||||
|
|
||||||
super.getChildren().addAll(line1, line2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removeLaylines() {
|
|
||||||
ArrayList<Node> toRemove = new ArrayList<>();
|
|
||||||
for(Node node : super.getChildren()) {
|
|
||||||
if (node instanceof Line) {
|
|
||||||
Line layLine = (Line) node;
|
|
||||||
|
|
||||||
/***
|
|
||||||
* OOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHhhh
|
|
||||||
*/
|
|
||||||
if (layLine.getStrokeWidth() == 0.5){
|
|
||||||
toRemove.add(layLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.getChildren().removeAll(toRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MarkGroup(GateMark mark, Point2D points1, Point2D points2) {
|
|
||||||
marks.add(mark.getSingleMark1());
|
|
||||||
marks.add(mark.getSingleMark2());
|
|
||||||
mainMark = mark;
|
|
||||||
Color color = Color.BLACK;
|
|
||||||
if (mark.getName().equals("Start")){
|
|
||||||
color = Color.GREEN;
|
|
||||||
} else if (mark.getName().equals("Finish")){
|
|
||||||
color = Color.RED;
|
|
||||||
}
|
|
||||||
Circle markCircle;
|
|
||||||
markCircle = new Circle(
|
|
||||||
points1.getX(),
|
|
||||||
points1.getY(),
|
|
||||||
MARK_RADIUS,
|
|
||||||
color
|
|
||||||
);
|
|
||||||
super.getChildren().add(markCircle);
|
|
||||||
|
|
||||||
markCircle = new Circle(
|
|
||||||
points2.getX(),
|
|
||||||
points2.getY(),
|
|
||||||
MARK_RADIUS,
|
|
||||||
color
|
|
||||||
);
|
|
||||||
super.getChildren().add(markCircle);
|
|
||||||
Line line = new Line(
|
|
||||||
points1.getX(),
|
|
||||||
points1.getY(),
|
|
||||||
points2.getX(),
|
|
||||||
points2.getY()
|
|
||||||
);
|
|
||||||
line.setStrokeWidth(LINE_THICKNESS);
|
|
||||||
line.setStroke(color);
|
|
||||||
if (mark.getMarkType() == MarkType.OPEN_GATE) {
|
|
||||||
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
|
|
||||||
}
|
|
||||||
super.getChildren().add(line);
|
|
||||||
|
|
||||||
//Laylines
|
|
||||||
// if (mark.)
|
|
||||||
|
|
||||||
// addLayLine(points1, 12.0, 90.0);
|
|
||||||
// addLayLine(points2, 12.0, 90.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void moveMarkTo (double x, double y, long raceId)
|
|
||||||
{
|
|
||||||
if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
|
|
||||||
Circle markCircle = (Circle) super.getChildren().get(0);
|
|
||||||
|
|
||||||
markCircle.setCenterX(x);
|
|
||||||
markCircle.setCenterY(y);
|
|
||||||
} else {
|
|
||||||
Circle markCircle1 = (Circle) super.getChildren().get(0);
|
|
||||||
Circle markCircle2 = (Circle) super.getChildren().get(1);
|
|
||||||
Line connectingLine = (Line) super.getChildren().get(2);
|
|
||||||
if (marks.get(0).getId() == raceId) {
|
|
||||||
markCircle1.setCenterX(x);
|
|
||||||
markCircle1.setCenterY(y);
|
|
||||||
connectingLine.setStartX(markCircle1.getCenterX());
|
|
||||||
connectingLine.setStartY(markCircle1.getCenterY());
|
|
||||||
} else if (marks.get(1).getId() == raceId) {
|
|
||||||
markCircle2.setCenterX(x);
|
|
||||||
markCircle2.setCenterY(y);
|
|
||||||
connectingLine.setEndX(markCircle2.getCenterX());
|
|
||||||
connectingLine.setEndY(markCircle2.getCenterY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasRaceId (int... raceIds) {
|
|
||||||
for (int id : raceIds)
|
|
||||||
for (Mark mark : marks)
|
|
||||||
if (id == mark.getId())
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getRaceIds () {
|
|
||||||
long[] idArray = new long[marks.size()];
|
|
||||||
int i = 0;
|
|
||||||
for (Mark mark : marks)
|
|
||||||
idArray[i++] = mark.getId();
|
|
||||||
return idArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mark getMainMark() {
|
|
||||||
return mainMark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package seng302.models.mark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To represent two types of mark
|
|
||||||
* Created by Haoming Yin (hyi25) on 17/3/17.
|
|
||||||
*/
|
|
||||||
public enum MarkType {
|
|
||||||
SINGLE_MARK, OPEN_GATE
|
|
||||||
}
|
|
||||||
@@ -1,34 +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, int sourceID, int compoundMarkID) {
|
|
||||||
super(name, MarkType.SINGLE_MARK, sourceID, compoundMarkID);
|
|
||||||
this.lat = lat;
|
|
||||||
this.lon = lon;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public double getLatitude() {
|
|
||||||
return this.lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLongitude() {
|
|
||||||
return this.lon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,668 +0,0 @@
|
|||||||
package seng302.models.stream;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
import seng302.models.stream.packets.BoatPositionPacket;
|
|
||||||
import seng302.models.stream.packets.StreamPacket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The purpose of this class is to take in the stream of divided packets so they can be read
|
|
||||||
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
|
|
||||||
* that are threadsafe so the visualiser can always access the latest speed and position available
|
|
||||||
* Created by kre39 on 23/04/17.
|
|
||||||
*/
|
|
||||||
public class StreamParser extends Thread {
|
|
||||||
|
|
||||||
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
|
|
||||||
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
|
|
||||||
private String threadName;
|
|
||||||
private Thread t;
|
|
||||||
private static boolean newRaceXmlReceived = false;
|
|
||||||
private static boolean raceStarted = false;
|
|
||||||
private static XMLParser xmlObject;
|
|
||||||
private static boolean raceFinished = false;
|
|
||||||
private static boolean streamStatus = false;
|
|
||||||
private static long timeSinceStart = -1;
|
|
||||||
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
|
|
||||||
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
|
|
||||||
private static double windDirection = 0;
|
|
||||||
private static Double windSpeed = 0d;
|
|
||||||
private static Long currentTimeLong;
|
|
||||||
private static String currentTimeString;
|
|
||||||
private static boolean appRunning;
|
|
||||||
|
|
||||||
|
|
||||||
//CONVERSION CONSTANTS
|
|
||||||
private static final Double MS_TO_KNOTS = 1.94384;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to initialise the thread name and stream parser object so a thread can be executed
|
|
||||||
*
|
|
||||||
* @param threadName name of the thread
|
|
||||||
*/
|
|
||||||
public StreamParser(String threadName) {
|
|
||||||
this.threadName = threadName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to within threading so when the stream parser thread runs, it will keep looking for a
|
|
||||||
* packet to process until it is unable to find anymore packets
|
|
||||||
*/
|
|
||||||
public void run() {
|
|
||||||
appRunning = true;
|
|
||||||
try {
|
|
||||||
streamStatus = true;
|
|
||||||
xmlObject = new XMLParser();
|
|
||||||
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
|
|
||||||
Thread.sleep(1);
|
|
||||||
}
|
|
||||||
while (appRunning) {
|
|
||||||
StreamPacket packet = StreamReceiver.packetBuffer.take();
|
|
||||||
parsePacket(packet);
|
|
||||||
Thread.sleep(1);
|
|
||||||
while (StreamReceiver.packetBuffer.peek() == null) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to start the stream parser thread when multithreading
|
|
||||||
*/
|
|
||||||
public void start() {
|
|
||||||
if (t == null) {
|
|
||||||
t = new Thread(this, threadName);
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks at the type of the packet then sends it to the appropriate parser to extract the
|
|
||||||
* specific data associated with that packet type
|
|
||||||
*
|
|
||||||
* @param packet the packet to be looked at and processed
|
|
||||||
*/
|
|
||||||
private static void parsePacket(StreamPacket packet) {
|
|
||||||
try {
|
|
||||||
switch (packet.getType()) {
|
|
||||||
case HEARTBEAT:
|
|
||||||
extractHeartBeat(packet);
|
|
||||||
break;
|
|
||||||
case RACE_STATUS:
|
|
||||||
extractRaceStatus(packet);
|
|
||||||
break;
|
|
||||||
case DISPLAY_TEXT_MESSAGE:
|
|
||||||
extractDisplayMessage(packet);
|
|
||||||
break;
|
|
||||||
case XML_MESSAGE:
|
|
||||||
newRaceXmlReceived = true;
|
|
||||||
extractXmlMessage(packet);
|
|
||||||
break;
|
|
||||||
case RACE_START_STATUS:
|
|
||||||
extractRaceStartStatus(packet);
|
|
||||||
break;
|
|
||||||
case YACHT_EVENT_CODE:
|
|
||||||
extractYachtEventCode(packet);
|
|
||||||
break;
|
|
||||||
case YACHT_ACTION_CODE:
|
|
||||||
extractYachtActionCode(packet);
|
|
||||||
break;
|
|
||||||
case CHATTER_TEXT:
|
|
||||||
extractChatterText(packet);
|
|
||||||
break;
|
|
||||||
case BOAT_LOCATION:
|
|
||||||
extractBoatLocation(packet);
|
|
||||||
break;
|
|
||||||
case MARK_ROUNDING:
|
|
||||||
extractMarkRounding(packet);
|
|
||||||
break;
|
|
||||||
case COURSE_WIND:
|
|
||||||
extractCourseWind(packet);
|
|
||||||
break;
|
|
||||||
case AVG_WIND:
|
|
||||||
extractAvgWind(packet);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
System.out.println("Error parsing packet");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the seq num used in the heartbeat packet
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractHeartBeat(StreamPacket packet) {
|
|
||||||
long heartbeat = bytesToLong(packet.getPayload());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getTimeZoneString() {
|
|
||||||
|
|
||||||
Integer offset = xmlObject.getRegattaXML().getUtcOffset();
|
|
||||||
StringBuilder utcOffset = new StringBuilder();
|
|
||||||
utcOffset.append("GMT");
|
|
||||||
if (offset > 0) {
|
|
||||||
utcOffset.append("+");
|
|
||||||
utcOffset.append(offset);
|
|
||||||
} else if (offset < 0) {
|
|
||||||
utcOffset.append("-");
|
|
||||||
utcOffset.append(offset);
|
|
||||||
}
|
|
||||||
return utcOffset.toString();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the useful race status data from race status type packets. This method will also
|
|
||||||
* print to the console the current state of the race (if it has started/finished or is about to
|
|
||||||
* start), along side this it'll also display the amount of time since the race has started or
|
|
||||||
* time till it starts
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractRaceStatus(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
|
|
||||||
int raceStatus = payload[11];
|
|
||||||
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
|
|
||||||
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
|
|
||||||
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
|
|
||||||
|
|
||||||
currentTimeLong = currentTime;
|
|
||||||
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
|
||||||
if (xmlObject.getRegattaXML() != null) {
|
|
||||||
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
|
|
||||||
currentTimeString = format.format((new Date(currentTime)).getTime());
|
|
||||||
}
|
|
||||||
long timeTillStart =
|
|
||||||
((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
|
|
||||||
|
|
||||||
if (timeTillStart > 0) {
|
|
||||||
timeSinceStart = timeTillStart;
|
|
||||||
} else {
|
|
||||||
if (raceStatus == 4 || raceStatus == 8) {
|
|
||||||
raceFinished = true;
|
|
||||||
raceStarted = false;
|
|
||||||
} else if (!raceStarted) {
|
|
||||||
raceStarted = true;
|
|
||||||
raceFinished = false;
|
|
||||||
}
|
|
||||||
timeSinceStart = timeTillStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
|
|
||||||
windDirection = windDir / windDirFactor;
|
|
||||||
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
|
|
||||||
|
|
||||||
int noBoats = payload[22];
|
|
||||||
int raceType = payload[23];
|
|
||||||
for (int i = 0; i < noBoats; i++) {
|
|
||||||
long boatStatusSourceID = bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
|
|
||||||
Yacht boat = boats.get((int) boatStatusSourceID);
|
|
||||||
boat.setBoatStatus((int) payload[28 + (i * 20)]);
|
|
||||||
setBoatLegPosition(boat, (int) payload[29 + (i * 20)]);
|
|
||||||
boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]);
|
|
||||||
boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
|
|
||||||
Long estTimeAtNextMark = bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
|
|
||||||
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
|
||||||
Long estTimeAtFinish = bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
|
|
||||||
boat.setEstimateTimeAtFinish(estTimeAtFinish);
|
|
||||||
// boatsPos.put(estTimeAtFinish, boat);
|
|
||||||
// String boatStatus = "SourceID: " + boatStatusSourceID;
|
|
||||||
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
|
|
||||||
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
|
|
||||||
// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
|
|
||||||
// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
|
|
||||||
// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
|
|
||||||
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
|
|
||||||
// boatStatuses.add(boatStatus);
|
|
||||||
}
|
|
||||||
// if (isRaceStarted()) {
|
|
||||||
// int pos = 1;
|
|
||||||
// for (Yacht yacht : boatsPos.values()) {
|
|
||||||
// yacht.setPosition(String.valueOf(pos));
|
|
||||||
// pos++;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// for (Yacht yacht : boatsPos.values()) {
|
|
||||||
// yacht.setPosition("-");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
|
|
||||||
Integer placing = 1;
|
|
||||||
if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
|
|
||||||
for (Yacht boat : boats.values()) {
|
|
||||||
if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
|
|
||||||
placing += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatingBoat.setPosition(placing.toString());
|
|
||||||
updatingBoat.setLegNumber(leg);
|
|
||||||
boatsPos.putIfAbsent(placing, updatingBoat);
|
|
||||||
boatsPos.replace(placing, updatingBoat);
|
|
||||||
} else if(updatingBoat.getLegNumber() == null){
|
|
||||||
updatingBoat.setPosition("1");
|
|
||||||
updatingBoat.setLegNumber(leg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to extract the messages passed through with the display message packet
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractDisplayMessage(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
int numOfLines = payload[3];
|
|
||||||
int totalLen = 0;
|
|
||||||
for (int i = 0; i < numOfLines; i++) {
|
|
||||||
int lineNum = payload[4 + totalLen];
|
|
||||||
int textLength = payload[5 + totalLen];
|
|
||||||
byte[] messageTextBytes = Arrays
|
|
||||||
.copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen);
|
|
||||||
String messageText = new String(messageTextBytes);
|
|
||||||
totalLen += 2 + textLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to read in the xml data. Will call the specific methods to create the course and boats
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractXmlMessage(StreamPacket packet) {
|
|
||||||
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
|
|
||||||
int messageType = payload[9];
|
|
||||||
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
|
|
||||||
String xmlMessage = new String(
|
|
||||||
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
|
|
||||||
|
|
||||||
//Create XML document Object
|
|
||||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
|
||||||
DocumentBuilder db = null;
|
|
||||||
Document doc = null;
|
|
||||||
try {
|
|
||||||
db = dbf.newDocumentBuilder();
|
|
||||||
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlObject.constructXML(doc, messageType);
|
|
||||||
if (messageType == 7) { //7 is the boat XML
|
|
||||||
boats = xmlObject.getBoatXML().getCompetingBoats();
|
|
||||||
}
|
|
||||||
if (messageType == 6) { //6 is race info xml
|
|
||||||
|
|
||||||
newRaceXmlReceived = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the race start status from the packet, currently is unused within the app but
|
|
||||||
* is here for potential future use
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractRaceStartStatus(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15));
|
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19));
|
|
||||||
int notificationType = payload[19];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a yacht event occurs this will parse the byte array to retrieve the necessary info,
|
|
||||||
* currently unused
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractYachtEventCode(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
|
|
||||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
|
|
||||||
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21));
|
|
||||||
int eventId = payload[21];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary
|
|
||||||
* info, currently unused
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractYachtActionCode(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
|
|
||||||
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
|
|
||||||
int eventId = payload[17];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strips the message from the chatter text type packets, currently the message is unused
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractChatterText(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
int messageType = payload[1];
|
|
||||||
int length = payload[2];
|
|
||||||
String message = new String(Arrays.copyOfRange(payload, 3, 3 + length));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are
|
|
||||||
* all used All the other extra data is still being read and translated however is unused.
|
|
||||||
*
|
|
||||||
* @param packet Packet parsed in to use the payload
|
|
||||||
*/
|
|
||||||
private static void extractBoatLocation(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
|
|
||||||
int deviceType = (int) payload[15];
|
|
||||||
long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long seq = bytesToLong(Arrays.copyOfRange(payload, 11, 15));
|
|
||||||
long boatId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
|
|
||||||
long rawLat = bytesToLong(Arrays.copyOfRange(payload, 16, 20));
|
|
||||||
long rawLon = bytesToLong(Arrays.copyOfRange(payload, 20, 24));
|
|
||||||
//Converts the double to a usable lat/lon
|
|
||||||
double lat = ((180d * (double) rawLat) / Math.pow(2, 31));
|
|
||||||
double lon = ((180d * (double) rawLon) / Math.pow(2, 31));
|
|
||||||
long heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
|
|
||||||
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
|
|
||||||
|
|
||||||
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
|
|
||||||
if (deviceType == 1) {
|
|
||||||
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
|
|
||||||
heading, groundSpeed);
|
|
||||||
|
|
||||||
//add a new priority que to the boatLocations HashMap
|
|
||||||
if (!boatLocations.containsKey(boatId)) {
|
|
||||||
boatLocations.put(boatId,
|
|
||||||
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
|
||||||
@Override
|
|
||||||
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
|
||||||
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
boatLocations.get(boatId).put(boatPacket);
|
|
||||||
} else if (deviceType == 3) {
|
|
||||||
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
|
|
||||||
heading, groundSpeed);
|
|
||||||
|
|
||||||
//add a new priority que to the boatLocations HashMap
|
|
||||||
if (!markLocations.containsKey(boatId)) {
|
|
||||||
markLocations.put(boatId,
|
|
||||||
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
|
||||||
@Override
|
|
||||||
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
|
||||||
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
markLocations.get(boatId).put(markPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This packet type is received when a mark or gate is rounded by a boat
|
|
||||||
*
|
|
||||||
* @param packet The packet containing the payload
|
|
||||||
*/
|
|
||||||
private static void extractMarkRounding(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
|
|
||||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
|
|
||||||
int boatStatus = payload[17];
|
|
||||||
int roundingSide = payload[18];
|
|
||||||
int markType = payload[19];
|
|
||||||
int markId = payload[20];
|
|
||||||
|
|
||||||
// assign mark rounding time to boat
|
|
||||||
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
|
|
||||||
|
|
||||||
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
|
|
||||||
if (mark.getCompoundMarkID() == markId) {
|
|
||||||
boats.get((int)subjectId).setLastMarkRounded(mark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This packet type contains periodic data on the state of the wind
|
|
||||||
*
|
|
||||||
* @param packet The packet containing the payload
|
|
||||||
*/
|
|
||||||
private static void extractCourseWind(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
int selectedWindId = payload[1];
|
|
||||||
int loopCount = payload[2];
|
|
||||||
ArrayList<String> windInfo = new ArrayList<>();
|
|
||||||
for (int i = 0; i < loopCount; i++) {
|
|
||||||
String wind = "WindId: " + payload[3 + (20 * i)];
|
|
||||||
wind +=
|
|
||||||
"\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i)));
|
|
||||||
wind += "\nRaceId: " + bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i)));
|
|
||||||
wind += "\nWindDirection: " + bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i)));
|
|
||||||
wind += "\nWindSpeed: " + bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i)));
|
|
||||||
wind += "\nBestUpWindAngle: " + bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i)));
|
|
||||||
wind += "\nBestDownWindAngle: " + bytesToLong(
|
|
||||||
Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i)));
|
|
||||||
wind += "\nFlags: " + String
|
|
||||||
.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF))
|
|
||||||
.replace(' ', '0');
|
|
||||||
windInfo.add(wind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This packet conatins the average wind to ground speed
|
|
||||||
*
|
|
||||||
* @param packet The packet containing the paylaod
|
|
||||||
*/
|
|
||||||
private static void extractAvgWind(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
|
||||||
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload, 7, 9));
|
|
||||||
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload, 9, 11));
|
|
||||||
long period2 = bytesToLong(Arrays.copyOfRange(payload, 11, 13));
|
|
||||||
long speed2 = bytesToLong(Arrays.copyOfRange(payload, 13, 15));
|
|
||||||
long period3 = bytesToLong(Arrays.copyOfRange(payload, 15, 17));
|
|
||||||
long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19));
|
|
||||||
long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21));
|
|
||||||
long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* takes an array of up to 7 bytes and returns a positive
|
|
||||||
* long constructed from the input bytes
|
|
||||||
*
|
|
||||||
* @return a positive long if there is less than 7 bytes -1 otherwise
|
|
||||||
*/
|
|
||||||
private static long bytesToLong(byte[] bytes) {
|
|
||||||
long partialLong = 0;
|
|
||||||
int index = 0;
|
|
||||||
for (byte b : bytes) {
|
|
||||||
if (index > 6) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return partialLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns false if race not started, true otherwise
|
|
||||||
*
|
|
||||||
* @return race started status
|
|
||||||
*/
|
|
||||||
public static boolean isRaceStarted() {
|
|
||||||
return raceStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns false if stream not connected, true otherwise
|
|
||||||
*
|
|
||||||
* @return stream started status
|
|
||||||
*/
|
|
||||||
public static boolean isStreamStatus() {
|
|
||||||
return streamStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns race timer
|
|
||||||
*
|
|
||||||
* @return race timer in long
|
|
||||||
*/
|
|
||||||
public static long getTimeSinceStart() {
|
|
||||||
return timeSinceStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return false if race not finished, true otherwise
|
|
||||||
*
|
|
||||||
* @return race finished status
|
|
||||||
*/
|
|
||||||
public static boolean isRaceFinished() {
|
|
||||||
return raceFinished;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return a map of boats with sourceID and the boat
|
|
||||||
*
|
|
||||||
* @return map of boats
|
|
||||||
*/
|
|
||||||
public static Map<Integer, Yacht> getBoats() {
|
|
||||||
return boats;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the latest updated object from xml parser
|
|
||||||
*
|
|
||||||
* @return the latest xml object
|
|
||||||
*/
|
|
||||||
public static XMLParser getXmlObject() {
|
|
||||||
return xmlObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the wind direction in degrees
|
|
||||||
*
|
|
||||||
* @return a double wind direction value
|
|
||||||
*/
|
|
||||||
public static double getWindDirection() {
|
|
||||||
return windDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the wind speed in knots
|
|
||||||
* @return A double indicating the wind speed in knots
|
|
||||||
*/
|
|
||||||
public static Double getWindSpeed() {
|
|
||||||
return windSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns stream time in formatted string format
|
|
||||||
*
|
|
||||||
* @return String of stream time
|
|
||||||
*/
|
|
||||||
public static String getCurrentTimeString() {
|
|
||||||
return currentTimeString;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* used in boat position since tree map can sort position efficiently.
|
|
||||||
*
|
|
||||||
* @return a map of time to finish and boat.
|
|
||||||
*/
|
|
||||||
public static Map<Integer, Yacht> getBoatsPos() {
|
|
||||||
|
|
||||||
return boatsPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns current time in stream in long
|
|
||||||
*
|
|
||||||
* @return a long value of current time
|
|
||||||
*/
|
|
||||||
public static Long getCurrentTimeLong() {
|
|
||||||
return currentTimeLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void appClose() {
|
|
||||||
appRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to check if a new un-processed xml has been found, if so will return true before
|
|
||||||
* toggling off so that the next check will return false.
|
|
||||||
*
|
|
||||||
* @return the status of if new xml has been received
|
|
||||||
*/
|
|
||||||
public static boolean isNewRaceXmlReceived() {
|
|
||||||
if (newRaceXmlReceived) {
|
|
||||||
newRaceXmlReceived = false;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package seng302.models.stream;
|
|
||||||
|
|
||||||
import seng302.models.stream.packets.StreamPacket;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
|
||||||
import java.util.zip.CRC32;
|
|
||||||
import java.util.zip.Checksum;
|
|
||||||
|
|
||||||
|
|
||||||
public class StreamReceiver extends Thread {
|
|
||||||
private InputStream stream;
|
|
||||||
private Socket host;
|
|
||||||
private ByteArrayOutputStream crcBuffer;
|
|
||||||
private Thread t;
|
|
||||||
private String threadName;
|
|
||||||
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
|
|
||||||
private static boolean moreBytes;
|
|
||||||
|
|
||||||
public StreamReceiver(String hostAddress, int hostPort, String threadName) {
|
|
||||||
this.threadName = threadName;
|
|
||||||
this.setDaemon(true);
|
|
||||||
try {
|
|
||||||
host = new Socket(hostAddress, hostPort);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(){
|
|
||||||
PriorityBlockingQueue<StreamPacket> pq = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
|
|
||||||
@Override
|
|
||||||
public int compare(StreamPacket s1, StreamPacket s2) {
|
|
||||||
return (int) (s1.getTimeStamp() - s2.getTimeStamp());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
packetBuffer = pq;
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start () {
|
|
||||||
if (t == null) {
|
|
||||||
t = new Thread (this, threadName);
|
|
||||||
t.start ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){
|
|
||||||
this.host=host;
|
|
||||||
this.packetBuffer = packetBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void connect(){
|
|
||||||
try {
|
|
||||||
stream = host.getInputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sync1;
|
|
||||||
int sync2;
|
|
||||||
moreBytes = true;
|
|
||||||
while(moreBytes) {
|
|
||||||
try {
|
|
||||||
crcBuffer = new ByteArrayOutputStream();
|
|
||||||
sync1 = readByte();
|
|
||||||
sync2 = readByte();
|
|
||||||
//checking if it is the start of the packet
|
|
||||||
if(sync1 == 0x47 && sync2 == 0x83) {
|
|
||||||
int type = readByte();
|
|
||||||
//No. of milliseconds since Jan 1st 1970
|
|
||||||
long timeStamp = bytesToLong(getBytes(6));
|
|
||||||
skipBytes(4);
|
|
||||||
long payloadLength = bytesToLong(getBytes(2));
|
|
||||||
byte[] payload = getBytes((int) payloadLength);
|
|
||||||
Checksum checksum = new CRC32();
|
|
||||||
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
|
|
||||||
long computedCrc = checksum.getValue();
|
|
||||||
long packetCrc = bytesToLong(getBytes(4));
|
|
||||||
if (computedCrc == packetCrc) {
|
|
||||||
packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
||||||
} else {
|
|
||||||
System.err.println("Packet has been dropped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
moreBytes = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int readByte() throws Exception {
|
|
||||||
int currentByte = -1;
|
|
||||||
try {
|
|
||||||
currentByte = stream.read();
|
|
||||||
crcBuffer.write(currentByte);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
if (currentByte == -1){
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
return currentByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getBytes(int n) throws Exception{
|
|
||||||
byte[] bytes = new byte[n];
|
|
||||||
for (int i = 0; i < n; i++){
|
|
||||||
bytes[i] = (byte) readByte();
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void skipBytes(long n) throws Exception{
|
|
||||||
for (int i=0; i < n; i++){
|
|
||||||
readByte();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* takes an array of up to 7 bytes in little endian format and
|
|
||||||
* returns a positive long constructed from the input bytes
|
|
||||||
*
|
|
||||||
* @return a positive long if there is less than 8 bytes -1 otherwise
|
|
||||||
*/
|
|
||||||
private long bytesToLong(byte[] bytes){
|
|
||||||
long partialLong = 0;
|
|
||||||
int index = 0;
|
|
||||||
for (byte b: bytes){
|
|
||||||
if (index > 6){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return partialLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
|
|
||||||
StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
|
|
||||||
//StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread2");
|
|
||||||
sr.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void noMoreBytes(){
|
|
||||||
moreBytes = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,610 +0,0 @@
|
|||||||
package seng302.models.stream;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.mark.GateMark;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
import seng302.models.mark.MarkType;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to create an XML object from the XML Packet Messages.
|
|
||||||
*
|
|
||||||
* Example usage:
|
|
||||||
*
|
|
||||||
* Document doc; // some xml document
|
|
||||||
* Integer xmlMessageType; // an Integer of value 5, 6, 7
|
|
||||||
*
|
|
||||||
* xmlP = new XMLParser(doc, xmlMessageType);
|
|
||||||
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
|
|
||||||
*/
|
|
||||||
public class XMLParser {
|
|
||||||
|
|
||||||
private Document xmlDoc;
|
|
||||||
|
|
||||||
private RaceXMLObject raceXML;
|
|
||||||
private RegattaXMLObject regattaXML;
|
|
||||||
private BoatXMLObject boatXML;
|
|
||||||
|
|
||||||
public XMLParser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for XMLParser
|
|
||||||
*
|
|
||||||
* @param doc Document to create XML object.
|
|
||||||
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
|
|
||||||
*/
|
|
||||||
public void constructXML(Document doc, Integer messageType) {
|
|
||||||
this.xmlDoc = doc;
|
|
||||||
switch (messageType) {
|
|
||||||
case 5:
|
|
||||||
regattaXML = new RegattaXMLObject(this.xmlDoc);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
raceXML = new RaceXMLObject(this.xmlDoc);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
boatXML = new BoatXMLObject(this.xmlDoc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RaceXMLObject getRaceXML() {
|
|
||||||
return raceXML;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegattaXMLObject getRegattaXML() {
|
|
||||||
return regattaXML;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BoatXMLObject getBoatXML() {
|
|
||||||
return boatXML;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
|
|
||||||
*
|
|
||||||
* @param ele Document Element with child elements.
|
|
||||||
* @param tag Tag to find in document elements child elements.
|
|
||||||
* @return Text content from tag if found, null otherwise.
|
|
||||||
*/
|
|
||||||
private static Integer getElementInt(Element ele, String tag) {
|
|
||||||
NodeList tagList = ele.getElementsByTagName(tag);
|
|
||||||
if (tagList.getLength() > 0) {
|
|
||||||
return Integer.parseInt(tagList.item(0).getTextContent());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the text content of a given child element tag, assuming it exists, as an String.
|
|
||||||
*
|
|
||||||
* @param ele Document Element with child elements.
|
|
||||||
* @param tag Tag to find in document elements child elements.
|
|
||||||
* @return Text content from tag if found, null otherwise.
|
|
||||||
*/
|
|
||||||
private static String getElementString(Element ele, String tag) {
|
|
||||||
NodeList tagList = ele.getElementsByTagName(tag);
|
|
||||||
if (tagList.getLength() > 0) {
|
|
||||||
return tagList.item(0).getTextContent();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the text content of a given child element tag, assuming it exists, as a Double.
|
|
||||||
*
|
|
||||||
* @param ele Document Element with child elements.
|
|
||||||
* @param tag Tag to find in document elements child elements.
|
|
||||||
* @return Text content from tag if found, null otherwise.
|
|
||||||
*/
|
|
||||||
private static Double getElementDouble(Element ele, String tag) {
|
|
||||||
NodeList tagList = ele.getElementsByTagName(tag);
|
|
||||||
if (tagList.getLength() > 0) {
|
|
||||||
return Double.parseDouble(tagList.item(0).getTextContent());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
|
|
||||||
*
|
|
||||||
* @param n A node object that should have some attributes
|
|
||||||
* @param attr The attribute you want to get from the given node.
|
|
||||||
* @return The String representation of the text content of an attribute in the given node, else
|
|
||||||
* returns null.
|
|
||||||
*/
|
|
||||||
private static String getNodeAttributeString(Node n, String attr) {
|
|
||||||
Node attrItem = n.getAttributes().getNamedItem(attr);
|
|
||||||
if (attrItem != null) {
|
|
||||||
return attrItem.getTextContent();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
|
|
||||||
*
|
|
||||||
* @param n A node object that should have some attributes
|
|
||||||
* @param attr The attribute you want to get from the given node.
|
|
||||||
* @return The Integer representation of the text content of an attribute in the given node,
|
|
||||||
* else returns null.
|
|
||||||
*/
|
|
||||||
private static Integer getNodeAttributeInt(Node n, String attr) {
|
|
||||||
Node attrItem = n.getAttributes().getNamedItem(attr);
|
|
||||||
if (attrItem != null) {
|
|
||||||
return Integer.parseInt(attrItem.getTextContent());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
|
|
||||||
*
|
|
||||||
* @param n A node object that should have some attributes
|
|
||||||
* @param attr The attribute you want to get from the given node.
|
|
||||||
* @return The Double representation of the text content of an attribute in the given node, else
|
|
||||||
* returns null.
|
|
||||||
*/
|
|
||||||
private static Double getNodeAttributeDouble(Node n, String attr) {
|
|
||||||
Node attrItem = n.getAttributes().getNamedItem(attr);
|
|
||||||
if (attrItem != null) {
|
|
||||||
return Double.parseDouble(attrItem.getTextContent());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RegattaXMLObject {
|
|
||||||
|
|
||||||
//Regatta Info
|
|
||||||
private Integer regattaID;
|
|
||||||
private String regattaName;
|
|
||||||
private String courseName;
|
|
||||||
private Double centralLat;
|
|
||||||
private Double centralLng;
|
|
||||||
private Integer utcOffset;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for a RegattaXMLObject.
|
|
||||||
* Takes the information from a Document object and creates a more usable format.
|
|
||||||
*
|
|
||||||
* @param doc XML Document Object
|
|
||||||
*/
|
|
||||||
RegattaXMLObject(Document doc) {
|
|
||||||
Element docEle = doc.getDocumentElement();
|
|
||||||
|
|
||||||
this.regattaID = getElementInt(docEle, "RegattaID");
|
|
||||||
this.regattaName = getElementString(docEle, "RegattaName");
|
|
||||||
this.courseName = getElementString(docEle, "CourseName");
|
|
||||||
this.centralLat = getElementDouble(docEle, "CentralLatitude");
|
|
||||||
this.centralLng = getElementDouble(docEle, "CentralLongitude");
|
|
||||||
this.utcOffset = getElementInt(docEle, "UtcOffset");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getRegattaID() {
|
|
||||||
return regattaID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegattaName() {
|
|
||||||
return regattaName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCourseName() {
|
|
||||||
return courseName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getCentralLat() {
|
|
||||||
return centralLat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getCentralLng() {
|
|
||||||
return centralLng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getUtcOffset() {
|
|
||||||
return utcOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RaceXMLObject {
|
|
||||||
|
|
||||||
// Race Info
|
|
||||||
private Integer raceID;
|
|
||||||
private String raceType;
|
|
||||||
private String creationTimeDate; // XML Creation Time
|
|
||||||
|
|
||||||
//Race Start Details
|
|
||||||
private String raceStartTime;
|
|
||||||
private Boolean postponeStatus;
|
|
||||||
|
|
||||||
//Non atomic race attributes
|
|
||||||
private ArrayList<Participant> participants;
|
|
||||||
private ArrayList<Mark> allMarks;
|
|
||||||
private ArrayList<Mark> nonDuplicateMarks;
|
|
||||||
private ArrayList<Corner> compoundMarkSequence;
|
|
||||||
private ArrayList<Limit> courseLimit;
|
|
||||||
|
|
||||||
// ensures there's no duplicate marks.
|
|
||||||
private List<Long> seenSourceIDs = new ArrayList<Long>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for a RaceXMLObject.
|
|
||||||
* Takes the information from a Document object and creates a more usable format.
|
|
||||||
*
|
|
||||||
* @param doc XML Document Object
|
|
||||||
*/
|
|
||||||
RaceXMLObject(Document doc) {
|
|
||||||
Element docEle = doc.getDocumentElement();
|
|
||||||
|
|
||||||
//Atomic and Semi-Atomic Elements
|
|
||||||
this.raceID = getElementInt(docEle, "RaceID");
|
|
||||||
this.raceType = getElementString(docEle, "RaceType");
|
|
||||||
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
|
|
||||||
|
|
||||||
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
|
|
||||||
this.raceStartTime = getNodeAttributeString(raceStart, "Start");
|
|
||||||
this.postponeStatus = Boolean
|
|
||||||
.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
|
|
||||||
|
|
||||||
//Participants
|
|
||||||
participants = new ArrayList<>();
|
|
||||||
|
|
||||||
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
|
|
||||||
for (int i = 0; i < pList.getLength(); i++) {
|
|
||||||
Node pNode = pList.item(i);
|
|
||||||
String entry;
|
|
||||||
if (pNode.getNodeName().equals("Yacht")) {
|
|
||||||
Integer sourceID = getNodeAttributeInt(pNode, "SourceID");
|
|
||||||
|
|
||||||
if (pNode.getAttributes().getLength() == 2) {
|
|
||||||
entry = getNodeAttributeString(pNode, "Entry");
|
|
||||||
} else {
|
|
||||||
entry = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Participant pa = new Participant(sourceID, entry);
|
|
||||||
participants.add(pa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Course
|
|
||||||
allMarks = new ArrayList<>();
|
|
||||||
nonDuplicateMarks = new ArrayList<>();
|
|
||||||
createCompoundMarks(docEle);
|
|
||||||
|
|
||||||
//Course Mark Sequence
|
|
||||||
compoundMarkSequence = new ArrayList<>();
|
|
||||||
|
|
||||||
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0)
|
|
||||||
.getChildNodes();
|
|
||||||
for (int i = 0; i < cornerList.getLength(); i++) {
|
|
||||||
Node cornerNode = cornerList.item(i);
|
|
||||||
if (cornerNode.getNodeName().equals("Corner")) {
|
|
||||||
Corner corner = new Corner(cornerNode);
|
|
||||||
compoundMarkSequence.add(corner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Course Limits
|
|
||||||
courseLimit = new ArrayList<>();
|
|
||||||
|
|
||||||
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
|
|
||||||
for (int i = 0; i < limitList.getLength(); i++) {
|
|
||||||
Node limitNode = limitList.item(i);
|
|
||||||
if (limitNode.getNodeName().equals("Limit")) {
|
|
||||||
Limit limit = new Limit(limitNode);
|
|
||||||
courseLimit.add(limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void createCompoundMarks(Element docEle) {
|
|
||||||
|
|
||||||
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
|
|
||||||
for (int i = 0; i < cMarkList.getLength(); i++) {
|
|
||||||
Node cMarkNode = cMarkList.item(i);
|
|
||||||
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
|
||||||
createAndAddMark(cMarkNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void createAndAddMark(Node compoundMark) {
|
|
||||||
|
|
||||||
Boolean markSeen = false;
|
|
||||||
List<SingleMark> marksList = new ArrayList<>();
|
|
||||||
Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
|
|
||||||
String cMarkName = getNodeAttributeString(compoundMark, "Name");
|
|
||||||
|
|
||||||
NodeList childMarks = compoundMark.getChildNodes();
|
|
||||||
|
|
||||||
for (int i = 0; i < childMarks.getLength(); i++) {
|
|
||||||
Node markNode = childMarks.item(i);
|
|
||||||
if (markNode.getNodeName().equals("Mark")) {
|
|
||||||
|
|
||||||
Integer sourceID = getNodeAttributeInt(markNode, "SourceID");
|
|
||||||
String markName = getNodeAttributeString(markNode, "Name");
|
|
||||||
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
|
|
||||||
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng");
|
|
||||||
|
|
||||||
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID);
|
|
||||||
marksList.add(mark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SingleMark mark : marksList) {
|
|
||||||
if (seenSourceIDs.contains(mark.getId())) {
|
|
||||||
markSeen = true;
|
|
||||||
} else {
|
|
||||||
seenSourceIDs.add(mark.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (marksList.size() == 1) {
|
|
||||||
if (!markSeen) {
|
|
||||||
nonDuplicateMarks.add(marksList.get(0));
|
|
||||||
}
|
|
||||||
allMarks.add(marksList.get(0));
|
|
||||||
} else if (marksList.size() == 2) {
|
|
||||||
GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
|
|
||||||
marksList.get(1), marksList.get(0).getLatitude(),
|
|
||||||
marksList.get(0).getLongitude(), compoundMarkID);
|
|
||||||
if(!markSeen) {
|
|
||||||
nonDuplicateMarks.add(thisGateMark);
|
|
||||||
}
|
|
||||||
allMarks.add(thisGateMark);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getRaceID() {
|
|
||||||
return raceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRaceType() {
|
|
||||||
return raceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCreationTimeDate() {
|
|
||||||
return creationTimeDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRaceStartTime() {
|
|
||||||
return raceStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getPostponeStatus() {
|
|
||||||
return postponeStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Participant> getParticipants() {
|
|
||||||
return participants;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS)
|
|
||||||
*/
|
|
||||||
public List<Mark> getAllCompoundMarks() {
|
|
||||||
return allMarks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Returns Marks from the race XML without any duplicates
|
|
||||||
*/
|
|
||||||
public List<Mark> getNonDupCompoundMarks() {
|
|
||||||
return nonDuplicateMarks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Corner> getCompoundMarkSequence() {
|
|
||||||
return compoundMarkSequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Limit> getCourseLimit() {
|
|
||||||
return courseLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Participant {
|
|
||||||
|
|
||||||
Integer sourceID;
|
|
||||||
String entry;
|
|
||||||
|
|
||||||
Participant(Integer sourceID, String entry) {
|
|
||||||
this.sourceID = sourceID;
|
|
||||||
this.entry = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getsourceID() {
|
|
||||||
return sourceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEntry() {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Corner {
|
|
||||||
|
|
||||||
private Integer seqID;
|
|
||||||
private Integer compoundMarkID;
|
|
||||||
private String rounding;
|
|
||||||
private Integer zoneSize;
|
|
||||||
|
|
||||||
Corner(Node cornerNode) {
|
|
||||||
this.seqID = getNodeAttributeInt(cornerNode, "SeqID");
|
|
||||||
this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID");
|
|
||||||
this.rounding = getNodeAttributeString(cornerNode, "Rounding");
|
|
||||||
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSeqID() {
|
|
||||||
return seqID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getCompoundMarkID() {
|
|
||||||
return compoundMarkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRounding() {
|
|
||||||
return rounding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getZoneSize() {
|
|
||||||
return zoneSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Limit {
|
|
||||||
|
|
||||||
private Integer seqID;
|
|
||||||
private Double lat;
|
|
||||||
private Double lng;
|
|
||||||
|
|
||||||
Limit(Node limitNode) {
|
|
||||||
this.seqID = getNodeAttributeInt(limitNode, "SeqID");
|
|
||||||
this.lat = getNodeAttributeDouble(limitNode, "Lat");
|
|
||||||
this.lng = getNodeAttributeDouble(limitNode, "Lon");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSeqID() {
|
|
||||||
return seqID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getLat() {
|
|
||||||
return lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getLng() {
|
|
||||||
return lng;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoatXMLObject {
|
|
||||||
|
|
||||||
private String lastModified;
|
|
||||||
private Integer version;
|
|
||||||
|
|
||||||
//Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete.
|
|
||||||
private String boatType;
|
|
||||||
private Double boatLength;
|
|
||||||
private Double hullLength;
|
|
||||||
private Double markZoneSize;
|
|
||||||
private Double courseZoneSize;
|
|
||||||
private ArrayList<Double> zoneLimits;// will only contain 5 elements. Limits 1-5
|
|
||||||
|
|
||||||
//Boats
|
|
||||||
ArrayList<Yacht> boats;
|
|
||||||
//Competing boats
|
|
||||||
Map<Integer, Yacht> competingBoats = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for a BoatXMLObject.
|
|
||||||
* Takes the information from a Document object and creates a more usable format.
|
|
||||||
*
|
|
||||||
* @param doc XML Document Object
|
|
||||||
*/
|
|
||||||
BoatXMLObject(Document doc) {
|
|
||||||
|
|
||||||
Element docEle = doc.getDocumentElement();
|
|
||||||
|
|
||||||
this.lastModified = getElementString(docEle, "Modified");
|
|
||||||
this.version = getElementInt(docEle, "Version");
|
|
||||||
|
|
||||||
NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes();
|
|
||||||
this.boatType = getNodeAttributeString(settingsList.item(1), "Type");
|
|
||||||
this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength");
|
|
||||||
this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength");
|
|
||||||
this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize");
|
|
||||||
this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize");
|
|
||||||
|
|
||||||
Node zoneLimitsList = settingsList.item(7);
|
|
||||||
this.zoneLimits = new ArrayList<>();
|
|
||||||
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
|
|
||||||
String tag = String.format("Limit%d", i + 1);
|
|
||||||
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.boats = new ArrayList<>();
|
|
||||||
NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes();
|
|
||||||
for (int i = 0; i < boatsList.getLength(); i++) {
|
|
||||||
Node currentBoat = boatsList.item(i);
|
|
||||||
if (currentBoat.getNodeName().equals("Boat")) {
|
|
||||||
// Boat boat = new Boat(currentBoat);
|
|
||||||
Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"),
|
|
||||||
getNodeAttributeInt(currentBoat, "SourceID"),
|
|
||||||
getNodeAttributeString(currentBoat, "HullNum"),
|
|
||||||
getNodeAttributeString(currentBoat, "ShortName"),
|
|
||||||
getNodeAttributeString(currentBoat, "BoatName"),
|
|
||||||
getNodeAttributeString(currentBoat, "Country"));
|
|
||||||
this.boats.add(boat);
|
|
||||||
if (boat.getBoatType().equals("Yacht")) {
|
|
||||||
competingBoats.put(boat.getSourceID(), boat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastModified() {
|
|
||||||
return lastModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBoatType() {
|
|
||||||
return boatType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getBoatLength() {
|
|
||||||
return boatLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getHullLength() {
|
|
||||||
return hullLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getMarkZoneSize() {
|
|
||||||
return markZoneSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getCourseZoneSize() {
|
|
||||||
return courseZoneSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Double> getZoneLimits() {
|
|
||||||
return zoneLimits;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Yacht> getBoats() {
|
|
||||||
return boats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Integer, Yacht> getCompetingBoats() {
|
|
||||||
return competingBoats;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package seng302.models.stream.packets;
|
|
||||||
|
|
||||||
public class BoatPositionPacket {
|
|
||||||
private long boatId;
|
|
||||||
private long timeValid;
|
|
||||||
private double lat;
|
|
||||||
private double lon;
|
|
||||||
private double heading;
|
|
||||||
private double groundSpeed;
|
|
||||||
|
|
||||||
public BoatPositionPacket(long boatId, long timeValid, double lat, double lon, double heading, double groundSpeed) {
|
|
||||||
this.boatId = boatId;
|
|
||||||
this.timeValid = timeValid;
|
|
||||||
this.lat = lat;
|
|
||||||
this.lon = lon;
|
|
||||||
this.heading = heading;
|
|
||||||
this.groundSpeed = groundSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimeValid() {
|
|
||||||
return timeValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLat() {
|
|
||||||
return lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLon() {
|
|
||||||
return lon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getHeading() {
|
|
||||||
return heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getGroundSpeed() {
|
|
||||||
return groundSpeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
package seng302.server;
|
|
||||||
|
|
||||||
import seng302.server.messages.*;
|
|
||||||
import seng302.server.simulator.Boat;
|
|
||||||
import seng302.server.simulator.Simulator;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class ServerThread implements Runnable, Observer {
|
|
||||||
private StreamingServerSocket server;
|
|
||||||
private long startTime;
|
|
||||||
private boolean raceStarted = false;
|
|
||||||
private Map<Integer,Boolean> boatsFinished = new HashMap<>();
|
|
||||||
private List<Boat> boats;
|
|
||||||
private Simulator raceSimulator;
|
|
||||||
private boolean sendingRaceFinishedLocationMessages = true;
|
|
||||||
|
|
||||||
private final int HEARTBEAT_PERIOD = 5000;
|
|
||||||
private final int RACE_STATUS_PERIOD = 1000/2;
|
|
||||||
private final int RACE_START_STATUS_PERIOD = 1000;
|
|
||||||
private final int BOAT_LOCATION_PERIOD = 1000/5;
|
|
||||||
private final int PORT_NUMBER = 4949;
|
|
||||||
private final int TIME_TILL_RACE_START = 20*1000;
|
|
||||||
private static final int LOG_LEVEL = 1;
|
|
||||||
|
|
||||||
public ServerThread(String threadName){
|
|
||||||
Thread runner = new Thread(this, threadName);
|
|
||||||
runner.setDaemon(true);
|
|
||||||
|
|
||||||
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
|
|
||||||
raceSimulator.addObserver(this);
|
|
||||||
// run race simulator, so it can send boats' static location.
|
|
||||||
Thread raceSimulatorThread = new Thread(raceSimulator, "Race Simulator");
|
|
||||||
|
|
||||||
boats = raceSimulator.getBoats();
|
|
||||||
|
|
||||||
for (Boat b : boats){
|
|
||||||
boatsFinished.put(b.getSourceID(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
runner.start();
|
|
||||||
raceSimulatorThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
* @param type The XML Message type
|
|
||||||
* @return The XML Message
|
|
||||||
*/
|
|
||||||
private Message getXmlMessage(String fileName, XMLMessageSubType type){
|
|
||||||
String fileContents = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
InputStream thisStream = this.getClass().getResourceAsStream(fileName);
|
|
||||||
fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (NullPointerException e){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileContents != null){
|
|
||||||
return new XMLMessage(fileContents, type, server.getSequenceNumber());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Get a race status message for the current race
|
|
||||||
*/
|
|
||||||
private Message getRaceStatusMessage(){
|
|
||||||
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
|
||||||
BoatStatus boatStatus;
|
|
||||||
RaceStatus raceStatus;
|
|
||||||
boolean thereAreBoatsNotFinished = false;
|
|
||||||
|
|
||||||
for (Boat b : boats){
|
|
||||||
if (!raceStarted){
|
|
||||||
boatStatus = BoatStatus.PRESTART;
|
|
||||||
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, b.getEstimatedTimeTillFinish(), b.getEstimatedTimeTillFinish());
|
|
||||||
boatSubMessages.add(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thereAreBoatsNotFinished){
|
|
||||||
if (raceStarted){
|
|
||||||
raceStatus = RaceStatus.STARTED;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
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.TERMINATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH,
|
|
||||||
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an instance of the race simulator
|
|
||||||
*/
|
|
||||||
private void startRaceSim(){
|
|
||||||
// set race started to true, so the simulator will start moving boats
|
|
||||||
raceSimulator.setRaceStarted(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts sending heartbeat messages to the client
|
|
||||||
*/
|
|
||||||
private void startSendingHeartbeats() {
|
|
||||||
Timer t = new Timer();
|
|
||||||
|
|
||||||
t.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Message heartbeat = new Heartbeat(server.getSequenceNumber());
|
|
||||||
|
|
||||||
try {
|
|
||||||
server.send(heartbeat);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 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() && !raceStarted){
|
|
||||||
startRaceSim();
|
|
||||||
raceStarted = true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
server.send(raceStartStatusMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, RACE_START_STATUS_PERIOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start sending race start status messages until race starts
|
|
||||||
*/
|
|
||||||
private void startSendingRaceStatusMessages(){
|
|
||||||
Timer t = new Timer();
|
|
||||||
t.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Message raceStatusMessage = getRaceStatusMessage();
|
|
||||||
try {
|
|
||||||
server.send(raceStatusMessage);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, RACE_STATUS_PERIOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the race, boat, and regatta XML files to the client
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (raceData != null){
|
|
||||||
server.send(raceData);
|
|
||||||
}
|
|
||||||
if (boatData != null){
|
|
||||||
server.send(boatData);
|
|
||||||
}
|
|
||||||
if (regatta != null){
|
|
||||||
server.send(regatta);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the post-start race course information
|
|
||||||
*/
|
|
||||||
private void sendPostStartCourseXml(){
|
|
||||||
Timer t = new Timer();
|
|
||||||
t.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE);
|
|
||||||
if (raceData != null) {
|
|
||||||
server.send(raceData);
|
|
||||||
}
|
|
||||||
}catch (IOException e) {
|
|
||||||
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},25000);
|
|
||||||
//Delays the new course xml data for 25 seconds so the boats are able to pass the starting line
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
try{
|
|
||||||
server = new StreamingServerSocket(PORT_NUMBER);
|
|
||||||
}
|
|
||||||
catch (IOException e){
|
|
||||||
serverLog("Failed to bind socket: " + e.getMessage(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for client to connect
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
|
|
||||||
|
|
||||||
startSendingHeartbeats();
|
|
||||||
sendXml();
|
|
||||||
startSendingRaceStartStatusMessages();
|
|
||||||
startSendingRaceStatusMessages();
|
|
||||||
sendPostStartCourseXml();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start sending static boat position updates when race has finished
|
|
||||||
*/
|
|
||||||
private void startSendingRaceFinishedBoatPositions(){
|
|
||||||
Timer t = new Timer();
|
|
||||||
t.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
for (Boat b : raceSimulator.getBoats()){
|
|
||||||
Message m = new BoatLocationMessage(b.getSourceID(), server.getSequenceNumber(), b.getLat(),
|
|
||||||
b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(),
|
|
||||||
((long) 0));
|
|
||||||
|
|
||||||
server.send(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, BOAT_LOCATION_PERIOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a boat location message when they are updated by the simulator
|
|
||||||
* @param o .
|
|
||||||
* @param arg .
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void update(Observable o, Object arg) {
|
|
||||||
// Only send if server started
|
|
||||||
// TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17
|
|
||||||
if(server == null || !server.isStarted()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int numOfBoatsFinished = 0;
|
|
||||||
for (Boat boat : (List<Boat>) arg){
|
|
||||||
try {
|
|
||||||
if (boat.isFinished()) {
|
|
||||||
numOfBoatsFinished ++;
|
|
||||||
if (!boatsFinished.get(boat.getSourceID())) {
|
|
||||||
boatsFinished.put(boat.getSourceID(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
|
|
||||||
boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(),
|
|
||||||
((long) boat.getSpeed()));
|
|
||||||
server.send(m);
|
|
||||||
} catch (IOException e) {
|
|
||||||
serverLog("Couldn't send a boat status message", 3);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (NullPointerException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
|
|
||||||
startSendingRaceFinishedBoatPositions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package seng302.server;
|
|
||||||
|
|
||||||
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 ServerSocketChannel socket;
|
|
||||||
private SocketChannel client;
|
|
||||||
private short seqNum;
|
|
||||||
private boolean isServerStarted;
|
|
||||||
|
|
||||||
StreamingServerSocket(int port) throws IOException{
|
|
||||||
socket = ServerSocketChannel.open();
|
|
||||||
socket.socket().bind(new InetSocketAddress("localhost", port));
|
|
||||||
//socket.setSoTimeout(10000);
|
|
||||||
seqNum = 0;
|
|
||||||
isServerStarted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start(){
|
|
||||||
try {
|
|
||||||
client = socket.accept();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.getMessage();
|
|
||||||
}
|
|
||||||
if (client.socket() == null){
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
isServerStarted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(Message message) throws IOException{
|
|
||||||
if (client == null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.send(client);
|
|
||||||
|
|
||||||
seqNum++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getSequenceNumber(){
|
|
||||||
return seqNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStarted(){
|
|
||||||
return isServerStarted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
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;
|
|
||||||
private boolean isFinished;
|
|
||||||
private long estimatedTimeTillFinish;
|
|
||||||
|
|
||||||
private Corner lastPassedCorner, headingCorner;
|
|
||||||
|
|
||||||
public Boat(int sourceID, String boatName) {
|
|
||||||
this.sourceID = sourceID;
|
|
||||||
this.boatName = boatName;
|
|
||||||
this.isFinished = false;
|
|
||||||
estimatedTimeTillFinish = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFinished() {
|
|
||||||
return isFinished;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFinished(boolean finished) {
|
|
||||||
isFinished = finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getEstimatedTimeTillFinish(){
|
|
||||||
return (long) (-getSpeed()) + System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package seng302.server.simulator;
|
|
||||||
|
|
||||||
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 getDistance(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 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) {
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
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;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
public class Simulator extends Observable implements Runnable {
|
|
||||||
|
|
||||||
private List<Corner> course;
|
|
||||||
private List<Boat> boats;
|
|
||||||
private long lapse;
|
|
||||||
private boolean isRaceStarted;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
isRaceStarted = false;
|
|
||||||
|
|
||||||
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(ThreadLocalRandom.current().nextInt(40000, 60000 + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
int numOfFinishedBoats = 0;
|
|
||||||
|
|
||||||
while (numOfFinishedBoats < boats.size()) {
|
|
||||||
|
|
||||||
// if race has started, then boat should start to move.
|
|
||||||
if (isRaceStarted) {
|
|
||||||
for (Boat boat : boats) {
|
|
||||||
numOfFinishedBoats += moveBoat(boat, lapse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setChanged();
|
|
||||||
notifyObservers(boats);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(lapse);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves a boat with given time duration.
|
|
||||||
* @param boat the boat to be moved
|
|
||||||
* @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) {
|
|
||||||
|
|
||||||
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) {
|
|
||||||
boat.setFinished(true);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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++) {
|
|
||||||
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Boat> getBoats(){
|
|
||||||
return boats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRaceStarted(boolean raceStarted) {
|
|
||||||
isRaceStarted = raceStarted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
this.roundingType = roundingType;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package seng302.server.simulator.mark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract class to represent general marks
|
|
||||||
* Created by Haoming Yin (hyi25) on 17/3/17.
|
|
||||||
*/
|
|
||||||
public class Mark extends Position {
|
|
||||||
|
|
||||||
private int seqID;
|
|
||||||
private String name;
|
|
||||||
private int sourceID;
|
|
||||||
|
|
||||||
public Mark(String name, double lat, double lng, int sourceID) {
|
|
||||||
super(lat, lng);
|
|
||||||
this.name = name;
|
|
||||||
this.sourceID = sourceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, 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 int getSourceID() {
|
|
||||||
return sourceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceID(int sourceID) {
|
|
||||||
this.sourceID = sourceID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package seng302.server.simulator.mark;
|
|
||||||
|
|
||||||
public enum RoundingType {
|
|
||||||
|
|
||||||
// the mark should be rounded to port (boat's left)
|
|
||||||
PORT("Port"),
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the race xml file to get course details
|
|
||||||
* Created by Haoming Yin (hyi25) on 16/3/2017
|
|
||||||
*/
|
|
||||||
public class CourseParser extends FileParser {
|
|
||||||
|
|
||||||
private Document doc;
|
|
||||||
private Map<Integer, CompoundMark> compoundMarksMap;
|
|
||||||
|
|
||||||
public CourseParser(String path) {
|
|
||||||
super(path);
|
|
||||||
this.doc = this.parseFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should handle error / invalid file gracefully
|
|
||||||
protected List<Corner> getCourse() {
|
|
||||||
compoundMarksMap = getCompoundMarks(doc.getDocumentElement());
|
|
||||||
List<Corner> corners = new ArrayList<>();
|
|
||||||
NodeList cMarksSequence = doc.getElementsByTagName("Corner");
|
|
||||||
|
|
||||||
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.typeOf(e.getAttribute("Rounding"));
|
|
||||||
Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize"));
|
|
||||||
|
|
||||||
return new Corner(seqId, cMark, roundingType, zoneSize);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<Integer, CompoundMark> getCompoundMarks(Node node) {
|
|
||||||
Map<Integer, CompoundMark> compoundMarksMap = new HashMap<>();
|
|
||||||
|
|
||||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
|
||||||
Element element = (Element) node;
|
|
||||||
NodeList cMarks = element.getElementsByTagName("CompoundMark");
|
|
||||||
|
|
||||||
// loop through all compound marks who are the children of course node
|
|
||||||
for (int i = 0; i < cMarks.getLength(); i++) {
|
|
||||||
CompoundMark cMark = getCompoundMark(cMarks.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"));
|
|
||||||
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
|
|
||||||
|
|
||||||
Mark mark = new Mark(name, lat, lng, sourceId);
|
|
||||||
mark.setSeqID(seqId);
|
|
||||||
|
|
||||||
return mark;
|
|
||||||
}
|
|
||||||
System.out.println("Failed to create mark.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user