mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
599 Commits
sprint_1.2
...
sprint_5.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 25d8c8f9c4 | |||
| 1d9dd76356 | |||
| c2c34705d5 | |||
| 96ed5e445e | |||
| 870dc07fd2 | |||
| 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 | |||
| d99055901f | |||
| c77a48f589 | |||
| 0135426dfe | |||
| 37c745c139 | |||
| 7880039801 | |||
| a56dac1e87 | |||
| 4ae422b47b | |||
| c8a96dcce9 | |||
| 4f2dca7ecf | |||
| e569574c01 | |||
| f544734b4d | |||
| 80b439470b | |||
| 539197cef5 | |||
| 1a867be387 | |||
| 3785cd705f | |||
| 5d7a438080 | |||
| 2f12f3e34f | |||
| 52bfa3ad34 | |||
| d1d659b698 | |||
| cdb9337aed | |||
| 8b0af5bb62 | |||
| 83232a935e | |||
| a30a1aa7c7 | |||
| 07cebb6c5b | |||
| 45bf65a3d3 | |||
| 60f5a99b0c | |||
| 526c12127f | |||
| 1daac842f2 | |||
| 3e4a6f0f2e | |||
| c1e937049e | |||
| 8f8d5c7384 | |||
| 027c7a1480 | |||
| df2efa3329 | |||
| 9d754c8819 | |||
| e11ceed28c | |||
| 8b8b6e4afa | |||
| ed2a22b573 | |||
| 41851ee925 | |||
| ffc61942a9 | |||
| 2e4382bff6 | |||
| f542dbb61e | |||
| 2869d139a3 | |||
| 3ec930491f | |||
| a0005064ac | |||
| 33fae9d69a | |||
| 913e5fee7b | |||
| 3992073303 | |||
| 797a99f632 | |||
| e891ed8a64 | |||
| e8c2cf809b | |||
| ec761893c7 | |||
| 5df7efda03 | |||
| 2fff73c075 | |||
| d37cbd263e | |||
| 12c2f31af9 | |||
| 49c0c029c3 | |||
| da7a34fc55 | |||
| 322ff740e2 | |||
| b1575e57df | |||
| 82b219cdba | |||
| e317de7562 | |||
| 1e80d76acd | |||
| 176d65e0b2 | |||
| 0c08f5a03c | |||
| e257602b78 | |||
| 8f00f3a80c | |||
| 63d24c001f | |||
| 67668fe1fc | |||
| dbbb41e12f | |||
| 45053ba507 | |||
| 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 | |||
| fe76ef9cdc | |||
| acbaa838ec | |||
| f4134d83b5 | |||
| 24cc10e1cd | |||
| 20b79b40f2 | |||
| 72e2776b7e | |||
| 49e4c92da6 | |||
| ba761e4951 | |||
| c1aa1d8eae | |||
| 4231c3ccd8 | |||
| 65223ceaaf | |||
| ca22615c08 | |||
| 559a9f38c0 | |||
| 762829e5ff | |||
| 835f79b113 | |||
| e0854bc68c | |||
| cec7014856 | |||
| b73e4c89db | |||
| f1a9da83fc | |||
| 6e903bfbed | |||
| 23d62f552e | |||
| 53f6a6b8c5 | |||
| af81bf5891 | |||
| e72ac1def8 | |||
| f163dfdd11 | |||
| a1eda8d91d | |||
| 5ed02a1fe1 | |||
| 66d4a4b958 | |||
| adbb9ffe3b | |||
| 0cd2867ac0 | |||
| 4d29354797 | |||
| a6d9c66fc9 | |||
| 99588c7ff8 | |||
| 68c3e3e999 | |||
| 67b5650288 | |||
| 3085125f3e | |||
| 5e26ad7c36 | |||
| 765ea06c3b | |||
| ffe70a8313 | |||
| 331e0fc6ab | |||
| b3fd735f5c | |||
| 3cbbdb070f | |||
| d2e55bf964 | |||
| 05cdadac79 | |||
| e6aed88188 | |||
| d032314ddb | |||
| 6301fd2fb7 | |||
| 34c3899ec4 | |||
| 46f5fc5172 | |||
| 81c021b59a | |||
| 8ab57e4e61 | |||
| 14a7305a2d | |||
| 13ff179840 | |||
| e7060d4b6f | |||
| 641039720e | |||
| 76c0d34760 | |||
| 71637d7286 | |||
| 1cac7cc189 | |||
| c42942430f | |||
| 7abb36c362 | |||
| c449da2916 | |||
| 3fd0c0a2dd | |||
| 397f7d003a | |||
| f85d3bf5fe | |||
| d22d758757 | |||
| acbde5aad8 | |||
| 8f93956ff1 | |||
| 4fe4ac1079 | |||
| 8a2f0a9f45 | |||
| 5cc865f0af | |||
| 189ba93e64 | |||
| 89464e033e | |||
| 6d7c36e31f | |||
| ca8ea03870 | |||
| 3f57adb9cf | |||
| 2686dac62e | |||
| ffd40fef6d | |||
| e1b8e19966 | |||
| dec742cf54 | |||
| 6f1b0b06c3 | |||
| 408d70c420 | |||
| e51c966969 | |||
| 08eacacfd4 | |||
| 87150b3c72 | |||
| e385ac5c09 | |||
| c30629542b | |||
| 3f9fa24c69 | |||
| 78573fa837 | |||
| d4837cacda | |||
| 0367805f0f | |||
| e0750f5341 | |||
| 80528c9c42 | |||
| be633c0e60 | |||
| 97f1ccb6c1 | |||
| 68a243725b | |||
| 8f81060a18 | |||
| 07c76f12e1 | |||
| c6ab96a86f | |||
| 059c0de1fa | |||
| 8e147bd1bd | |||
| 4d3cfe71f7 | |||
| 5adb7c3762 | |||
| 937b309b07 | |||
| 48d58ea660 | |||
| aaf2e6a3f0 | |||
| 422dcd4501 | |||
| 951a726309 | |||
| b692ddcbe6 | |||
| 5d6b356602 | |||
| 08057edb28 | |||
| 390aabc78f | |||
| a2123df0c5 | |||
| b87008f590 | |||
| ecc0e722b5 | |||
| 6f9a8e5581 | |||
| b17bba3629 | |||
| cf4d7e03f5 | |||
| 73eeeb0ef9 | |||
| 0f79353936 | |||
| 38b44fa92b | |||
| 3fd13ddc0a | |||
| 2e375978bd | |||
| 45db731a60 | |||
| 95e353c14e | |||
| 8a3a41294a | |||
| f41858e2c7 | |||
| aaa3dc93f1 | |||
| e5eab0a6c8 | |||
| 7c39368126 | |||
| ade926e2f2 | |||
| c63c8e4d73 | |||
| 3c418b2aa4 | |||
| d34a158c34 | |||
| 6e3d037021 | |||
| 7d160eaf54 | |||
| 4fc99edbd6 | |||
| 4da8c1645e | |||
| 1c01aab1e7 | |||
| 6a85b0800f | |||
| 1acb0fbac4 | |||
| 2b294702a9 | |||
| afd97d6e05 | |||
| 2d5a7a8a49 | |||
| 9e3036e134 | |||
| 8dec458ba9 | |||
| da07d885da | |||
| a9de005e1a | |||
| e03e121da4 | |||
| fc3ca70e5d | |||
| 9c7144c918 | |||
| ed8d70c3b3 | |||
| fa501460cb | |||
| 110143ae6e | |||
| eda3d76077 | |||
| 51f090324a | |||
| 335540ff4a | |||
| 5fa47ff65b | |||
| 3a1c1a5e43 | |||
| 4c7f530458 | |||
| 2e914a7704 | |||
| 8fbb9d6d4e | |||
| 23bc643c91 | |||
| c4fe116267 | |||
| 081d7e3dcb | |||
| 764ae37ce4 | |||
| e62a609b6b | |||
| 4b1a4aae87 | |||
| ccda5f2a2e | |||
| 3fd8b1b855 | |||
| 94d1982670 | |||
| 39efafc75f | |||
| afe0c9f1a6 | |||
| 04b105d74b | |||
| 1ab6351d48 | |||
| 9c348df5a5 | |||
| 256ec046fc | |||
| 85d4d63287 | |||
| 213d36ed56 | |||
| ff6bfc9516 | |||
| ae5678482b | |||
| 63514cfafb | |||
| 03ca60f2e1 | |||
| 89ef6e5277 | |||
| 47880d09bc | |||
| 5472765b95 | |||
| 14d975dce4 | |||
| a23bdd0c53 | |||
| b0d8c3db0a | |||
| 4a75c062ce | |||
| fe90a3bf13 | |||
| 711f6f4c45 | |||
| 8fa7829a3c | |||
| 6d7697a0eb | |||
| cdd80af27b | |||
| 2cb09b81f8 | |||
| b529d621e9 | |||
| 24667991f1 | |||
| ac3f3bfd55 | |||
| f2c4929656 | |||
| 4c038a823a | |||
| 3080c1bf27 | |||
| 59809c39ea | |||
| 9b063190ce | |||
| 49f2398669 | |||
| d7a4d20ceb | |||
| 0855e268c8 | |||
| 49b8d75aea | |||
| b1a9a7845b | |||
| b1e749bafb | |||
| f6fc6c0693 | |||
| ad1371bce0 | |||
| e77df0a5dd | |||
| 6935bd514e | |||
| a4cc5f222c | |||
| a0bb7b85b4 | |||
| fd8ed92f88 | |||
| c7b6261602 | |||
| 3aefb14faf | |||
| 8521b68855 | |||
| e37b244f45 | |||
| 7f40fb6283 | |||
| 83316f7a17 | |||
| a56e55ae70 | |||
| 07234ee33a | |||
| 145d59df45 | |||
| 7e8c3af9ce | |||
| aa6ef72670 | |||
| ed8781b382 | |||
| 7a5f4e8f8c | |||
| d992422efd | |||
| f0d6312fa5 | |||
| 772ece25a0 | |||
| d063a41ad4 | |||
| 6d02f05f05 | |||
| 3a72409fb8 | |||
| b3a89279d6 | |||
| 4432ba26e5 | |||
| fe824a8f71 | |||
| 8233b75e05 | |||
| 3af15b2b95 | |||
| fd092bb7e1 | |||
| 9c60521d00 | |||
| a4dfcca302 | |||
| 04ce6f6103 | |||
| 178af141f0 | |||
| c5c2d4375d | |||
| 3aa183042f | |||
| 944755fde1 | |||
| cd78c35bf6 | |||
| f8d003002b | |||
| 56fab768f3 | |||
| c92744f21c | |||
| 1f71fd1967 | |||
| 33ae7beeb4 | |||
| 130efa3a51 | |||
| 7df55fc1a3 | |||
| 3adadcc1e1 | |||
| 5b027a29d8 | |||
| 2a9d0fb82c | |||
| ee6a543f8d | |||
| d927531354 | |||
| 1d47df09eb | |||
| a9709c4f84 | |||
| a06806c42d | |||
| 57de058582 | |||
| 978493853d | |||
| a5ca9218da | |||
| 5fe330bfbb | |||
| c80cff87f7 | |||
| 9a864cc2bd | |||
| c07f13180f | |||
| f672eafd6d | |||
| 6a361c0d4b | |||
| b597f010dc | |||
| a670f677e9 | |||
| a77423b937 | |||
| 50083a9297 | |||
| d5aa430d4a | |||
| e7f9954970 | |||
| ec57851de2 | |||
| 0eb767b615 | |||
| 45b77c05d4 | |||
| b9900925b8 | |||
| d94290c58d | |||
| f50aabff7b | |||
| d07c660eb9 | |||
| 6491efec4c | |||
| 25038da2a1 | |||
| 85f461c88c | |||
| 1cf55f3e96 | |||
| 9a995ddcc1 | |||
| 0b2ef3de00 | |||
| 99e50aa7ac | |||
| a898290c0b | |||
| 6cbff1097b | |||
| 246083460e | |||
| 1d28334346 | |||
| 3e97f016d5 | |||
| ab0d4634d6 | |||
| 1e1e482b79 | |||
| a0624cfef6 | |||
| 02a35b4c02 | |||
| 80409c08a6 | |||
| 6149f7be60 | |||
| 474f0ee427 | |||
| f3ee618900 | |||
| b939086e10 | |||
| ffdfc24e65 | |||
| 07bbd7e06d | |||
| 765f27f987 | |||
| d204bee55d | |||
| 0f4ad48de0 | |||
| fe480d5cb6 | |||
| 8a04a0e5b7 | |||
| 705a0a2eaf | |||
| d1289b0de1 | |||
| e1de5e0989 | |||
| f5b9160304 | |||
| 0a22812165 | |||
| 104fd86179 | |||
| 67a702ffcd | |||
| 65c0e6f77d | |||
| 245bd184b4 | |||
| 8c8f253233 | |||
| 8b8422de3a | |||
| 7bf2d4c40e | |||
| 2a67f04d15 | |||
| b2ea8196d5 | |||
| 7f38191d03 | |||
| f6b7a3042f | |||
| 3bdc6ce5cc | |||
| bc31987f96 | |||
| eaff4c5aac | |||
| 95bafdc0d1 | |||
| c776d22941 | |||
| 749c6b7fef | |||
| 912c081606 | |||
| c73bf7dd3e | |||
| a3ae015be8 | |||
| 1f8f1f0f86 | |||
| 5eebab2748 | |||
| 8cbd1cc4aa | |||
| 00c1a89f58 | |||
| d51825ffb7 | |||
| 42569e6ad7 | |||
| ef874b4245 | |||
| 46037b5aea | |||
| 6874f288ee | |||
| b6fd90e9d7 | |||
| f078c34bf9 | |||
| c1e4a6156c | |||
| 71e14259f6 | |||
| 403dc480c4 | |||
| 3dc1a7f9c0 | |||
| 672194adb4 | |||
| fdb84b6675 | |||
| ba352183bf | |||
| dd480080c9 | |||
| 4047978ea2 | |||
| a649b11bbf | |||
| 247560ee43 | |||
| 50e7ece477 | |||
| b5129c5c80 | |||
| 6a27dedd74 | |||
| edc306da22 | |||
| 15ded667fe | |||
| 34872a822b | |||
| 9817fc9093 | |||
| dde4b2fcba | |||
| 623600a8a9 | |||
| bff4986242 | |||
| c689530068 | |||
| 7022be1979 | |||
| 971a3920a3 | |||
| b252797e9b | |||
| c758afe3e3 | |||
| f8d3f53158 | |||
| 4a8672a20b | |||
| 32109e8565 | |||
| 5d6060c690 | |||
| a95d030817 | |||
| ffa84c6e87 | |||
| 4a6978ff79 | |||
| 1497858cc0 | |||
| 2c125d4ce0 | |||
| 8bf5455f42 | |||
| 48f7e41905 | |||
| b50ac62a4b | |||
| 6fc55bb82c | |||
| 65ac864bf2 | |||
| cf6bbdd1f1 | |||
| bbe7cbee8f | |||
| 55ba343426 | |||
| e6ace5fb2f | |||
| 550ab59231 | |||
| 213303d674 | |||
| 74c81eb7b3 | |||
| c33586e7f5 | |||
| 5dd5e50738 | |||
| 304f30ece6 | |||
| e8b1720fee | |||
| e0cea098c1 | |||
| 50ab481b18 | |||
| d39aacba83 | |||
| 798fe4da0e | |||
| e19f110f19 | |||
| 59a4a74a97 | |||
| 590ef557d3 | |||
| 7bc5c8f8a4 | |||
| bf8244ce49 | |||
| a860cc0ec1 | |||
| bb8c681270 | |||
| 9872095e50 | |||
| 403aaa76ae | |||
| 8578bc4a5b | |||
| ed004d1423 | |||
| a91b2b4f7e | |||
| 24f9607e5a | |||
| 2384013139 | |||
| 42ffd1b1f8 | |||
| a2d06909c9 | |||
| c8b7b95df8 | |||
| 9e22eac4d8 | |||
| a41f2e4bde | |||
| 039e61def6 | |||
| 54d329c5cf | |||
| f46a98fad9 | |||
| 6a6816dda1 | |||
| f6ea2953e9 | |||
| 7591a79323 | |||
| c12760b70a | |||
| a526b0d65a | |||
| ef098e63d7 | |||
| 29a0b23670 | |||
| 44b5b0b771 | |||
| 00f9cc4698 | |||
| 3d4d5a3dab | |||
| ed577fad6a | |||
| ae61260665 | |||
| 0e4bb0f942 | |||
| ee34e5028f | |||
| 3b8dd11758 | |||
| cc04e2dd6d | |||
| b88cf6a101 | |||
| d10985f890 | |||
| c08504293b | |||
| 4bc49da10d | |||
| 683f4ba94e | |||
| 8fd06c84ac | |||
| 23b163e6c1 | |||
| 6383b9a6f8 | |||
| 0b3ebf229f | |||
| d6fe155d4d | |||
| 8829728f19 | |||
| a6c9156ae5 | |||
| 121f996a43 | |||
| 44d4f25413 | |||
| 16abfcffda | |||
| e7ba9d962d | |||
| b7631c0b46 | |||
| 0039703f03 | |||
| d439c9673f | |||
| f255b0ea6d | |||
| 9d01ed8eb3 | |||
| 94e4e853c3 | |||
| ffa39e6a9c | |||
| 9e4ae60885 | |||
| 2cd4366d07 | |||
| 11c5e1e9ba | |||
| 550812d8e1 |
@@ -0,0 +1,17 @@
|
|||||||
|
engines:
|
||||||
|
pmd:
|
||||||
|
enabled: true
|
||||||
|
channel: "beta"
|
||||||
|
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
strings:
|
||||||
|
- FIXME
|
||||||
|
- TODO
|
||||||
|
- BUG
|
||||||
|
- FIX
|
||||||
|
|
||||||
|
ratings:
|
||||||
|
paths:
|
||||||
|
- "**.java"
|
||||||
@@ -84,6 +84,9 @@ nbactions.xml
|
|||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
# User-specific stuff:
|
# User-specific stuff:
|
||||||
|
# get rid of these annoying files
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
.idea/tasks.xml
|
.idea/tasks.xml
|
||||||
.idea/dictionaries
|
.idea/dictionaries
|
||||||
|
|||||||
@@ -16,4 +16,12 @@
|
|||||||
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
||||||
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
||||||
|
|
||||||
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
|
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
|
||||||
|
Michael Rausch <mra106@uclive.ac.nz> <michael@michaelrausch.net>
|
||||||
|
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
|
||||||
|
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
||||||
|
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> Ryan Tan <ryan_zhiyou@hotmail.com>
|
||||||
|
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com>
|
||||||
|
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
||||||
@@ -10,4 +10,4 @@ Remember to set up your GitLab CI server (refer to the student guide for instruc
|
|||||||
- `src/` Your application source
|
- `src/` Your application source
|
||||||
- `doc/` User and design documentation
|
- `doc/` User and design documentation
|
||||||
- `doc/examples/` Demo example files for use with your application
|
- `doc/examples/` Demo example files for use with your application
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"race-name": "AC35",
|
|
||||||
"time-scale": 2.0,
|
|
||||||
"race-size": 6,
|
|
||||||
"teams": [
|
|
||||||
{
|
|
||||||
"team-name": "Oracle Team USA",
|
|
||||||
"velocity": 20.9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"team-name": "Artemis Racing",
|
|
||||||
"velocity": 18.3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"team-name": "Emirates Team New Zealand",
|
|
||||||
"velocity": 21.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"team-name": "Groupama Team France",
|
|
||||||
"velocity": 19.9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"team-name": "Land Rover BAR",
|
|
||||||
"velocity": 17.6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"team-name": "SoftBank Team Japan",
|
|
||||||
"velocity": 16.6
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -20,11 +20,29 @@
|
|||||||
<version>4.12</version>
|
<version>4.12</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>1.3.2</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.googlecode.json-simple</groupId>
|
<groupId>com.googlecode.json-simple</groupId>
|
||||||
<artifactId>json-simple</artifactId>
|
<artifactId>json-simple</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>2.7.13</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.freemarker</groupId>
|
||||||
|
<artifactId>freemarker</artifactId>
|
||||||
|
<version>2.3.26-incubating</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+30
-100
@@ -1,111 +1,41 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import javafx.application.Application;
|
||||||
import java.util.ArrayList;
|
import javafx.fxml.FXMLLoader;
|
||||||
import java.util.Collections;
|
import javafx.scene.Parent;
|
||||||
import java.util.Map;
|
import javafx.scene.Scene;
|
||||||
import java.util.Random;
|
import javafx.scene.image.Image;
|
||||||
import java.io.FileNotFoundException;
|
import javafx.stage.Stage;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.client.ClientState;
|
||||||
|
import seng302.models.PolarTable;
|
||||||
|
|
||||||
public class App {
|
public class App extends Application {
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Builds a race object for the AC35 course
|
public void start(Stage primaryStage) throws Exception {
|
||||||
*
|
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
||||||
* @return a Race object for the AC35 course
|
|
||||||
*/
|
|
||||||
public static Race createRace(String configFile) throws Exception {
|
|
||||||
Race race = new Race();
|
|
||||||
FileParser fp;
|
|
||||||
|
|
||||||
// Read team names from file
|
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
||||||
try{
|
primaryStage.setTitle("RaceVision");
|
||||||
fp = new FileParser(configFile);
|
primaryStage.setScene(new Scene(root, 1530, 960));
|
||||||
}
|
primaryStage.setMaxWidth(1530);
|
||||||
catch (FileNotFoundException e){
|
primaryStage.setMaxHeight(960);
|
||||||
System.out.println("Config file does not exist");
|
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||||
return null;
|
// primaryStage.setMaximized(true);
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<String> boatNames = new ArrayList<>();
|
primaryStage.show();
|
||||||
ArrayList<Map<String, Object>> teams = fp.getTeams();
|
primaryStage.setOnCloseRequest(e -> {
|
||||||
|
ClientPacketParser.appClose();
|
||||||
|
System.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
//get race size
|
ClientState.primaryStage = primaryStage;
|
||||||
int numberOfBoats = (int) fp.getRaceSize();
|
|
||||||
|
|
||||||
//get time scale
|
|
||||||
double timeScale = fp.getTimeScale();
|
|
||||||
race.setTimeScale(timeScale);
|
|
||||||
|
|
||||||
for (Map<String, Object> team : teams) {
|
|
||||||
boatNames.add((String) team.get("team-name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle team names
|
|
||||||
long seed = System.nanoTime();
|
|
||||||
Collections.shuffle(boatNames, new Random(seed));
|
|
||||||
|
|
||||||
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add boats to the race
|
|
||||||
for (int i = 0; i < numberOfBoats; i++) {
|
|
||||||
race.addBoat(new Boat(boatNames.get(i), (Double) (teams.get(i).get("velocity"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
race.addLeg(new Leg(35, 100, "Start"));
|
|
||||||
race.addLeg(new Leg(10, 300, "Marker 1"));
|
|
||||||
race.addLeg(new Leg(350, 400, "Leeward Gate"));
|
|
||||||
race.addLeg(new Leg(10, 400, "Windward Gate"));
|
|
||||||
|
|
||||||
Leg finishingLeg = new Leg(10, 400, "Leeward Gate");
|
|
||||||
finishingLeg.setFinishingLeg(true);
|
|
||||||
|
|
||||||
race.addLeg(finishingLeg);
|
|
||||||
|
|
||||||
return race;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Race race = null;
|
launch(args);
|
||||||
String raceConfigFile;
|
|
||||||
|
|
||||||
if (args.length == 2 && args[0].equals("-f")){
|
|
||||||
raceConfigFile = args[1];
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// Use default config
|
|
||||||
raceConfigFile = "doc/examples/config.json";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
race = createRace(raceConfigFile);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("There was an error creating the race.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If race was created
|
|
||||||
if (race != null) {
|
|
||||||
race.displayStartingBoats();
|
|
||||||
|
|
||||||
System.out.println("\n\n");
|
|
||||||
System.out.println("######################");
|
|
||||||
System.out.println("# Live Race Updates ");
|
|
||||||
System.out.println("######################");
|
|
||||||
|
|
||||||
race.startRace();
|
|
||||||
|
|
||||||
System.out.println("\n\n");
|
|
||||||
System.out.println("######################");
|
|
||||||
System.out.println("# Race Results ");
|
|
||||||
System.out.println("######################");
|
|
||||||
|
|
||||||
race.showRaceMarkerResults();
|
|
||||||
race.displayFinishingOrder();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
System.out.println("There was an error creating the race. Exiting.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a boat in the race.
|
|
||||||
*/
|
|
||||||
public class Boat {
|
|
||||||
|
|
||||||
private String teamName; // The name of the team, this is also the name of the boat
|
|
||||||
private double velocity; // In meters/second
|
|
||||||
|
|
||||||
public Boat(String teamName) {
|
|
||||||
this.teamName = teamName;
|
|
||||||
this.velocity = 10; // Default velocity
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a boat in the race.
|
|
||||||
*
|
|
||||||
* @param teamName The name of the team sailing the boat
|
|
||||||
* @param boatVelocity The speed of the boat in meters/second
|
|
||||||
*/
|
|
||||||
public Boat(String teamName, double boatVelocity) {
|
|
||||||
this.teamName = teamName;
|
|
||||||
this.velocity = boatVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the team sailing the boat
|
|
||||||
*
|
|
||||||
* @return The name of the team
|
|
||||||
*/
|
|
||||||
public String getTeamName() {
|
|
||||||
return this.teamName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the name of the team sailing the boat
|
|
||||||
*
|
|
||||||
* @param teamName The name of the team
|
|
||||||
*/
|
|
||||||
public void setTeamName(String teamName) {
|
|
||||||
this.teamName = teamName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets velocity of the boat
|
|
||||||
*
|
|
||||||
* @return a float number of the boat velocity
|
|
||||||
*/
|
|
||||||
public double getVelocity() {
|
|
||||||
return this.velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets velocity of the boat
|
|
||||||
*
|
|
||||||
* @param velocity The velocity of boat
|
|
||||||
*/
|
|
||||||
public void setVelocity(double velocity) {
|
|
||||||
this.velocity = velocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event class containing the time of specific event, related team/boat, and
|
|
||||||
* event location such as leg.
|
|
||||||
*/
|
|
||||||
public class Event {
|
|
||||||
|
|
||||||
private long time; // Time the event occurs
|
|
||||||
private Boat boat;
|
|
||||||
private Leg leg; // Leg of the race the event occurs on
|
|
||||||
private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event class containing the time of specific event, related team/boat, and
|
|
||||||
* event location such as leg.
|
|
||||||
*
|
|
||||||
* @param eventTime, what time the event happens
|
|
||||||
* @param eventBoat, the boat that the event belongs to
|
|
||||||
* @param eventLeg, the leg the event happens on
|
|
||||||
*/
|
|
||||||
public Event(long eventTime, Boat eventBoat, Leg eventLeg) {
|
|
||||||
this.time = eventTime;
|
|
||||||
this.boat = eventBoat;
|
|
||||||
this.leg = eventLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event class containing the time of specific event, related team/boat, and
|
|
||||||
* event location such as leg.
|
|
||||||
*
|
|
||||||
* @param eventTime, what time the event happens
|
|
||||||
* @param eventBoat, the boat that the event belongs to
|
|
||||||
* @param eventLeg, the leg the event happens on
|
|
||||||
* @param isFinishingEvent, true if this event is the boat crossing the finishing line
|
|
||||||
*/
|
|
||||||
public Event(long eventTime, Boat eventBoat, Leg eventLeg, boolean isFinishingEvent) {
|
|
||||||
this.time = eventTime;
|
|
||||||
this.boat = eventBoat;
|
|
||||||
this.leg = eventLeg;
|
|
||||||
this.isFinishingEvent = isFinishingEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the time for the event
|
|
||||||
*
|
|
||||||
* @return the time for event in millisecond
|
|
||||||
*/
|
|
||||||
public long getTime() {
|
|
||||||
return this.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the time for the event
|
|
||||||
*
|
|
||||||
* @param eventTime the time for event in millisecond
|
|
||||||
*/
|
|
||||||
public void setTime(long eventTime) {
|
|
||||||
this.time = eventTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the time in a formatted string
|
|
||||||
*
|
|
||||||
* @return the string of time
|
|
||||||
*/
|
|
||||||
public String getTimeString() {
|
|
||||||
return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the involved boat
|
|
||||||
*
|
|
||||||
* @return the boat involved in the event
|
|
||||||
*/
|
|
||||||
public Boat getBoat() {
|
|
||||||
return this.boat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the involved boat
|
|
||||||
*
|
|
||||||
* @param eventBoat the involved boat
|
|
||||||
*/
|
|
||||||
public void setBoat(Boat eventBoat) {
|
|
||||||
this.boat = eventBoat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the involved location/leg
|
|
||||||
*
|
|
||||||
* @return the leg involved in the event
|
|
||||||
*/
|
|
||||||
public Leg getLeg() {
|
|
||||||
return this.leg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the involved location/leg
|
|
||||||
*
|
|
||||||
* @param eventLeg the involved leg
|
|
||||||
*/
|
|
||||||
public void setLeg(Leg eventLeg) {
|
|
||||||
this.leg = eventLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the boat in this event passes
|
|
||||||
* the marker.
|
|
||||||
*/
|
|
||||||
public void boatPassedMarker() {
|
|
||||||
this.leg.addBoatToMarker(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this event is the boat finishing the race
|
|
||||||
*/
|
|
||||||
public boolean getIsFinishingEvent() {
|
|
||||||
return this.isFinishingEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a string that contains the timestamp and course information for this event
|
|
||||||
*
|
|
||||||
* @return A string that details what happened in this event
|
|
||||||
*/
|
|
||||||
public String getEventString() {
|
|
||||||
String currentHeading = Integer.toString(this.getLeg().getHeading());
|
|
||||||
|
|
||||||
// This event is a boat finishing the race
|
|
||||||
if (this.isFinishingEvent) {
|
|
||||||
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.getLeg().getMarkerLabel() + " going heading " + currentHeading + "°");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read team name from a given Json file. So that user can extract information
|
|
||||||
* efficiently from external files.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FileParser {
|
|
||||||
|
|
||||||
private String filePath;
|
|
||||||
private JSONObject content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* used to construct an instance of file parser
|
|
||||||
*
|
|
||||||
* @param filePath a string like path to show location of desired file to
|
|
||||||
* be parsed
|
|
||||||
*/
|
|
||||||
public FileParser(String filePath) throws Exception {
|
|
||||||
this.filePath = filePath;
|
|
||||||
this.readFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads content from a given file, and return the content as JSONObject.
|
|
||||||
* Throws FileNotFoundException, if the given file cannot be found.
|
|
||||||
*/
|
|
||||||
private void readFile() throws FileNotFoundException {
|
|
||||||
JSONParser parser = new JSONParser();
|
|
||||||
try {
|
|
||||||
this.content = (JSONObject) parser.parse(new FileReader(filePath));
|
|
||||||
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (ParseException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets time scale setting parameter.
|
|
||||||
*
|
|
||||||
* @return long time scale. -1 if parameter is invalid (eg. scale is
|
|
||||||
* negative number, or containing non numeric character) or cannot be found.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public double getTimeScale() {
|
|
||||||
try {
|
|
||||||
double timeScale = (double) this.content.get("time-scale");
|
|
||||||
return timeScale >= 0 ? timeScale : -1;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets race name in the setting file.
|
|
||||||
*
|
|
||||||
* @return a string of race name. null if race name is invalid or cannot
|
|
||||||
* be found.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public String getRaceName() {
|
|
||||||
try {
|
|
||||||
return (String) this.content.get("race-name");
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an array of teams who participate the race.
|
|
||||||
*
|
|
||||||
* @return an ArrayList containing strings of team names. null if teams
|
|
||||||
* setting is invalid or there is no team.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ArrayList<Map<String, Object>> getTeams() {
|
|
||||||
try {
|
|
||||||
return (ArrayList<Map<String, Object>>) this.content.get("teams");
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the total number of teams.
|
|
||||||
*
|
|
||||||
* @return the number of teams. 0 if no teams or anything goes wrong.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public long getTotalNumberOfTeams() {
|
|
||||||
ArrayList<Map<String, Object>> teams = getTeams();
|
|
||||||
try {
|
|
||||||
return teams.size();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of boats that would compete during a race. Returns the
|
|
||||||
* total number of race size if parameter is invalid or cannot be found.
|
|
||||||
*
|
|
||||||
* @return an int of the race size.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public long getRaceSize() {
|
|
||||||
long totalTeams = this.getTotalNumberOfTeams();
|
|
||||||
try {
|
|
||||||
long raceSize = (long) this.content.get("race-size");
|
|
||||||
return raceSize >= 0 && raceSize <= totalTeams ? raceSize : totalTeams;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return totalTeams;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the leg of a race.
|
|
||||||
*/
|
|
||||||
public class Leg {
|
|
||||||
private int heading;
|
|
||||||
private int distance;
|
|
||||||
private boolean isFinishingLeg;
|
|
||||||
private Marker startingMarker;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new leg
|
|
||||||
*
|
|
||||||
* @param heading, the magnetic heading of this leg
|
|
||||||
* @param distance, the total distance of this leg in meters
|
|
||||||
* @param marker, the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public Leg(int heading, int distance, Marker marker) {
|
|
||||||
this.heading = heading;
|
|
||||||
this.distance = distance;
|
|
||||||
this.startingMarker = marker;
|
|
||||||
this.isFinishingLeg = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new leg
|
|
||||||
*
|
|
||||||
* @param heading, the magnetic heading of this leg
|
|
||||||
* @param distance, the total distance of this leg in meters
|
|
||||||
* @param markerName, the name of the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public Leg(int heading, int distance, String markerName) {
|
|
||||||
this.heading = heading;
|
|
||||||
this.distance = distance;
|
|
||||||
this.startingMarker = new Marker(markerName);
|
|
||||||
this.isFinishingLeg = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the heading of this leg
|
|
||||||
*/
|
|
||||||
public int getHeading() {
|
|
||||||
return this.heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the heading for this leg
|
|
||||||
*/
|
|
||||||
public void setHeading(int heading) {
|
|
||||||
this.heading = heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total distance of this leg in meters
|
|
||||||
*/
|
|
||||||
public int getDistance() {
|
|
||||||
return this.distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the distance of this leg in meters
|
|
||||||
*/
|
|
||||||
public void setDistance(int distance) {
|
|
||||||
this.distance = distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the marker this leg started on
|
|
||||||
*/
|
|
||||||
public Marker getMarker() {
|
|
||||||
return this.startingMarker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public void setMarker(Marker marker) {
|
|
||||||
this.startingMarker = marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the marker this leg started on
|
|
||||||
*/
|
|
||||||
public String getMarkerLabel() {
|
|
||||||
return this.startingMarker.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the marker that the boat has passed it
|
|
||||||
*/
|
|
||||||
public void addBoatToMarker(Boat boat) {
|
|
||||||
this.startingMarker.addBoat(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify whether or not the race finishes on this leg
|
|
||||||
*
|
|
||||||
* @param isFinishingLeg whether or not the race finishes on this leg
|
|
||||||
*/
|
|
||||||
public void setFinishingLeg(boolean isFinishingLeg) {
|
|
||||||
this.isFinishingLeg = isFinishingLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the race finishes after this leg
|
|
||||||
* @return true if this the race finishes after this leg
|
|
||||||
*/
|
|
||||||
public boolean getIsFinishingLeg() {
|
|
||||||
return this.isFinishingLeg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the marker at the beginning of a leg
|
|
||||||
*/
|
|
||||||
class Marker{
|
|
||||||
private String name;
|
|
||||||
private ArrayList<Boat> boatOrder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the marker at the beginning of a leg
|
|
||||||
*
|
|
||||||
* @param name, the name of the marker
|
|
||||||
*/
|
|
||||||
public Marker(String name){
|
|
||||||
this.name = name;
|
|
||||||
this.boatOrder = new ArrayList<Boat>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the name of the marker
|
|
||||||
*
|
|
||||||
* @param name, the name of the marker
|
|
||||||
*/
|
|
||||||
public void setName(String name){
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the name of the marker
|
|
||||||
*
|
|
||||||
* @return the name of the marker
|
|
||||||
*/
|
|
||||||
public String getName(){
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a boat as they pass the marker
|
|
||||||
*
|
|
||||||
* @param boat, the boat that passed the marker
|
|
||||||
*/
|
|
||||||
public void addBoat(Boat boat){
|
|
||||||
this.boatOrder.add(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of boats in the order they passed the marker
|
|
||||||
*
|
|
||||||
* @return An array of boats in the order they passed the marker
|
|
||||||
*/
|
|
||||||
public Boat[] getBoats(){
|
|
||||||
return this.boatOrder.toArray(new Boat[this.boatOrder.size()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Race class containing the boats and legs in the race
|
|
||||||
*/
|
|
||||||
public class Race {
|
|
||||||
private ArrayList<Boat> boats; // The boats in the race
|
|
||||||
private ArrayList<Leg> legs; // The legs in the race
|
|
||||||
private ArrayList<Boat> finishingOrder; // The order in which the boats finish the race
|
|
||||||
private PriorityQueue<Event> events; // The events that occur in the race
|
|
||||||
private int numberOfBoats = 0;
|
|
||||||
private long startTime = 0;
|
|
||||||
private double timeScale = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Race class containing the boats and legs in the race
|
|
||||||
*/
|
|
||||||
public Race() {
|
|
||||||
this.boats = new ArrayList<Boat>();
|
|
||||||
this.legs = new ArrayList<Leg>();
|
|
||||||
this.finishingOrder = new ArrayList<Boat>();
|
|
||||||
|
|
||||||
// create a priority queue with a custom Comparator to order events
|
|
||||||
this.events = new PriorityQueue<Event>(new Comparator<Event>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Event o1, Event o2) {
|
|
||||||
Long time1 = o1.getTime();
|
|
||||||
Long time2 = o2.getTime();
|
|
||||||
|
|
||||||
// order event asc. by time. if tie appears, then order team
|
|
||||||
// name alphabetically.
|
|
||||||
if (time1 != time2) {
|
|
||||||
return time1.compareTo(time2);
|
|
||||||
} else {
|
|
||||||
return o1.getBoat().getTeamName().compareTo(o2.getBoat().getTeamName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a boat to the race
|
|
||||||
*
|
|
||||||
* @param boat, the boat to add
|
|
||||||
*/
|
|
||||||
public void addBoat(Boat boat) {
|
|
||||||
boats.add(boat);
|
|
||||||
numberOfBoats += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of boats in a random order
|
|
||||||
*
|
|
||||||
* @returns a list of boats
|
|
||||||
*/
|
|
||||||
public Boat[] getShuffledBoats() {
|
|
||||||
// Shuffle the list of boats
|
|
||||||
long seed = System.nanoTime();
|
|
||||||
Collections.shuffle(this.boats, new Random(seed));
|
|
||||||
|
|
||||||
return boats.toArray(new Boat[boats.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of boats in the order that they
|
|
||||||
* finished the race (position 0 is first place)
|
|
||||||
*
|
|
||||||
* @returns a list of boats
|
|
||||||
*/
|
|
||||||
public Boat[] getFinishedBoats() {
|
|
||||||
return this.finishingOrder.toArray(new Boat[this.finishingOrder.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of boats in the race
|
|
||||||
*
|
|
||||||
* @returns the number of boats in the race
|
|
||||||
*/
|
|
||||||
public int getNumberOfBoats() {
|
|
||||||
return numberOfBoats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of boats in the race
|
|
||||||
*
|
|
||||||
* @return a list of the boats competing in the race
|
|
||||||
*/
|
|
||||||
public Boat[] getBoats() {
|
|
||||||
return boats.toArray(new Boat[boats.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints the order in which the boats finished the race
|
|
||||||
*/
|
|
||||||
public void displayFinishingOrder() {
|
|
||||||
int numberOfBoats = this.getNumberOfBoats();
|
|
||||||
Boat[] boats = this.getFinishedBoats();
|
|
||||||
|
|
||||||
System.out.println("--- Finishing Order ---");
|
|
||||||
|
|
||||||
for (int i = 0; i < Array.getLength(boats); i++) {
|
|
||||||
System.out.println("#" + Integer.toString(i + 1) + " - " + boats[i].getTeamName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints the list of boats competing in the race
|
|
||||||
*/
|
|
||||||
public void displayStartingBoats() {
|
|
||||||
int numberOfBoats = this.getNumberOfBoats();
|
|
||||||
Boat[] boats = this.getBoats();
|
|
||||||
|
|
||||||
System.out.println("######################");
|
|
||||||
System.out.println("# Competing Boats ");
|
|
||||||
System.out.println("######################");
|
|
||||||
|
|
||||||
for (int i = 0; i < numberOfBoats; i++) {
|
|
||||||
String velocityKnots = String.format("%1.2f", boats[i].getVelocity() * 1.943844492);
|
|
||||||
|
|
||||||
System.out.println(boats[i].getTeamName() + " Velocity: " + velocityKnots + " Knots/s");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a leg to the race
|
|
||||||
*
|
|
||||||
* @param leg, the leg to add to the race
|
|
||||||
*/
|
|
||||||
public void addLeg(Leg leg) {
|
|
||||||
this.legs.add(leg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets legs array
|
|
||||||
*
|
|
||||||
* @return an array of legs
|
|
||||||
*/
|
|
||||||
public ArrayList<Leg> getLegs() {
|
|
||||||
return this.legs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets time scale
|
|
||||||
*
|
|
||||||
* @param timeScale
|
|
||||||
*/
|
|
||||||
public void setTimeScale(double timeScale) {
|
|
||||||
this.timeScale = timeScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate all events that will happen during the race.
|
|
||||||
*/
|
|
||||||
private void generateEvents() {
|
|
||||||
//calculate the time every boat passes each leg, and create an event
|
|
||||||
for (Boat boat : this.boats) {
|
|
||||||
long totalDistance = 0;
|
|
||||||
for (Leg leg : this.legs) {
|
|
||||||
long time = (long) (1000 * totalDistance / boat.getVelocity());
|
|
||||||
Event event = new Event(time, boat, leg);
|
|
||||||
events.add(event);
|
|
||||||
totalDistance += leg.getDistance();
|
|
||||||
|
|
||||||
// If finishing leg, add another event for when the boat finishes the race
|
|
||||||
if (leg.getIsFinishingLeg()) {
|
|
||||||
time = (long) (1000 * totalDistance / boat.getVelocity());
|
|
||||||
event = new Event(time, boat, leg, true);
|
|
||||||
events.add(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates how far a boat has travelled in meters
|
|
||||||
*
|
|
||||||
* @param velocity the velocity of boat
|
|
||||||
* @return a float number of distance the boat has been travelled
|
|
||||||
*/
|
|
||||||
public float getDistanceTravelled(long velocity) {
|
|
||||||
long timeDiff = System.currentTimeMillis() - this.startTime;
|
|
||||||
long timeElapse = (long) (timeDiff / 1000 * this.timeScale);
|
|
||||||
return timeElapse * velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over events in the race and print the
|
|
||||||
* event string for each event
|
|
||||||
*/
|
|
||||||
public void iterateEvents() {
|
|
||||||
// iterates all events. ends when no event in events.
|
|
||||||
while (!events.isEmpty()) {
|
|
||||||
Event peekEvent = events.peek();
|
|
||||||
long currentTime = (long) ((System.currentTimeMillis() - this.startTime) * this.timeScale);
|
|
||||||
|
|
||||||
if (currentTime > peekEvent.getTime()) {
|
|
||||||
Event nextEvent = events.poll();
|
|
||||||
|
|
||||||
// Display a summary of the event
|
|
||||||
System.out.println(nextEvent.getEventString());
|
|
||||||
nextEvent.boatPassedMarker();
|
|
||||||
|
|
||||||
// If event is a boat finishing the race
|
|
||||||
if (nextEvent.getIsFinishingEvent()) {
|
|
||||||
this.finishingOrder.add(nextEvent.getBoat());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for 100ms to throttle the while loop
|
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (java.lang.InterruptedException e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the race and print each marker with the order
|
|
||||||
* in which the boats passed that marker
|
|
||||||
*/
|
|
||||||
public void startRace() {
|
|
||||||
// record start time.
|
|
||||||
generateEvents();
|
|
||||||
this.startTime = System.currentTimeMillis();
|
|
||||||
iterateEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print the order in which the boats passed each marker
|
|
||||||
*/
|
|
||||||
public void showRaceMarkerResults() {
|
|
||||||
for (Leg leg : this.legs) {
|
|
||||||
Boat[] boats = leg.getMarker().getBoats();
|
|
||||||
|
|
||||||
System.out.println("--- " + leg.getMarkerLabel() + " ---");
|
|
||||||
|
|
||||||
// Print the order in which the boats passed the marker
|
|
||||||
for (int i = 0; i < this.getNumberOfBoats(); i++) {
|
|
||||||
System.out.println("#" + Integer.toString(i + 1) + " - " + boats[i].getTeamName());
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,638 @@
|
|||||||
|
package seng302.client;
|
||||||
|
|
||||||
|
|
||||||
|
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.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.XMLParser;
|
||||||
|
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 ClientPacketParser {
|
||||||
|
|
||||||
|
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
|
||||||
|
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
|
||||||
|
private static boolean newRaceXmlReceived = false;
|
||||||
|
private static boolean raceStarted = false;
|
||||||
|
private static XMLParser xmlObject = new XMLParser();
|
||||||
|
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;
|
||||||
|
private static Map<Integer, Yacht> clientStateBoats = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
//CONVERSION CONSTANTS
|
||||||
|
public static final Double MS_TO_KNOTS = 1.94384;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to initialise the thread name and stream parser object so a thread can be executed
|
||||||
|
*/
|
||||||
|
public ClientPacketParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public 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:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
System.out.println("Error parsing packet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
ClientState.setRaceStarted(false);
|
||||||
|
} else if (!raceStarted) {
|
||||||
|
raceStarted = true;
|
||||||
|
ClientState.setRaceStarted(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)));
|
||||||
|
int boatStatus = (int) payload[28 + (i * 20)];
|
||||||
|
int boatLegNumber = (int) payload[29 + (i * 20)];
|
||||||
|
int boatPenaltyAwarded = (int) payload[30 + (i * 20)];
|
||||||
|
int boatPenaltyServed = (int) payload[31 + (i * 20)];
|
||||||
|
long estTimeAtNextMark = bytesToLong(
|
||||||
|
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
|
||||||
|
long estTimeAtFinish = bytesToLong(
|
||||||
|
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
|
||||||
|
|
||||||
|
Yacht boat = boats.get((int) boatStatusSourceID);
|
||||||
|
boat.setBoatStatus((boatStatus));
|
||||||
|
setBoatLegPosition(boat, boatLegNumber);
|
||||||
|
boat.setPenaltiesAwarded(boatPenaltyAwarded);
|
||||||
|
boat.setPenaltiesServed(boatPenaltyServed);
|
||||||
|
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||||
|
boat.setEstimateTimeAtFinish(estTimeAtFinish);
|
||||||
|
|
||||||
|
// Update Client State boats when receive race status packet.
|
||||||
|
// Potentially could replace boats in ClientPacketParser.
|
||||||
|
Yacht clientBoat = ClientState.getBoats().get((int) boatStatusSourceID);
|
||||||
|
clientBoat.setBoatStatus((boatStatus));
|
||||||
|
setBoatLegPosition(clientBoat, boatLegNumber);
|
||||||
|
clientBoat.setPenaltiesAwarded(boatPenaltyAwarded);
|
||||||
|
clientBoat.setPenaltiesServed(boatPenaltyServed);
|
||||||
|
clientBoat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||||
|
clientBoat.setEstimateTimeAtFinish(estTimeAtFinish);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 is race started.
|
||||||
|
// ClientState race started flag will be set to true if race started, else set false.
|
||||||
|
if (raceStatus == 3) {
|
||||||
|
ClientState.setRaceStarted(true);
|
||||||
|
} else {
|
||||||
|
ClientState.setRaceStarted(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
|
||||||
|
Integer placing = 1;
|
||||||
|
|
||||||
|
if (/* TODO implement when we are getting this data /TODO leg != updatingBoat.getLegNumber() && */(raceStarted || raceFinished)) {
|
||||||
|
for (Yacht boat : boats.values()) {
|
||||||
|
placing = boat.getSourceId();
|
||||||
|
/* See above to-do
|
||||||
|
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("-");
|
||||||
|
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) {
|
||||||
|
xmlObject = new XMLParser();
|
||||||
|
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) {
|
||||||
|
System.out.println("[ClientPacketParser] ParserConfigurationException | IOException | SAXException");
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlObject.constructXML(doc, messageType);
|
||||||
|
|
||||||
|
if (messageType == 7) { //7 is the boat XML
|
||||||
|
boats = xmlObject.getBoatXML().getCompetingBoats();
|
||||||
|
// Set/Update the ClientState boats after receiving new boat xml.
|
||||||
|
// Flag boatsUpdated in ClientState to true.
|
||||||
|
ClientState.setBoats(xmlObject.getBoatXML().getCompetingBoats());
|
||||||
|
ClientState.setBoatsUpdated(true);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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));
|
||||||
|
// System.out.println("[CLIENT] Lat: " + lat + " Lon: " + lon);
|
||||||
|
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){
|
||||||
|
Yacht boat = boats.get((int) boatId);
|
||||||
|
boat.setVelocity(groundSpeed);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package seng302.client;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the client to store static variables, which other threads and classes
|
||||||
|
* observer so that they can update their status accordingly.
|
||||||
|
*/
|
||||||
|
public class ClientState {
|
||||||
|
|
||||||
|
private static String hostIp = "";
|
||||||
|
private static Boolean isHost = false;
|
||||||
|
private static Boolean raceStarted = false;
|
||||||
|
private static Boolean connectedToHost = false;
|
||||||
|
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
|
||||||
|
private static Boolean boatsUpdated = true;
|
||||||
|
private static String clientSourceId = "";
|
||||||
|
public static Stage primaryStage;
|
||||||
|
|
||||||
|
public static String getHostIp() {
|
||||||
|
return hostIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHostIp(String hostIp) {
|
||||||
|
ClientState.hostIp = hostIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isHost() {
|
||||||
|
return isHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHost(Boolean isHost) {
|
||||||
|
ClientState.isHost = isHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isRaceStarted() {
|
||||||
|
return raceStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRaceStarted(Boolean raceStarted) {
|
||||||
|
ClientState.raceStarted = raceStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isConnectedToHost() {
|
||||||
|
return connectedToHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setConnectedToHost(Boolean connectedToHost) {
|
||||||
|
ClientState.connectedToHost = connectedToHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<Integer, Yacht> getBoats() {
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isBoatsUpdated() {
|
||||||
|
return boatsUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBoatsUpdated(Boolean boatsUpdated) {
|
||||||
|
ClientState.boatsUpdated = boatsUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientSourceId() {
|
||||||
|
return clientSourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setClientSourceId(String clientSourceId) {
|
||||||
|
ClientState.clientSourceId = clientSourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBoats(Map<Integer, Yacht> boats) {
|
||||||
|
ClientState.boats = boats;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package seng302.client;
|
||||||
|
|
||||||
|
import java.util.Observable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by LobbyController to run a separate thread-loop
|
||||||
|
* updates the controller when change is detected.
|
||||||
|
*/
|
||||||
|
public class ClientStateQueryingRunnable extends Observable implements Runnable {
|
||||||
|
|
||||||
|
private Boolean terminate = false;
|
||||||
|
|
||||||
|
public ClientStateQueryingRunnable() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies observers(the lobby controller) that "game started" if ClientState
|
||||||
|
* raceStarted flag is true and terminates itself. Also, it notifies observers
|
||||||
|
* to add/remove players if ClientState boatsUpdated flag is true, then resets
|
||||||
|
* the flag to false;
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while(!terminate) {
|
||||||
|
// Sleeping the thread so it will respond to the if statement below
|
||||||
|
// if you know a better fix, pls tell me :) -ryan
|
||||||
|
try {
|
||||||
|
Thread.sleep(0);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientState.isRaceStarted() && ClientState.isConnectedToHost()) {
|
||||||
|
setChanged();
|
||||||
|
notifyObservers("game started");
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientState.isBoatsUpdated()) {
|
||||||
|
setChanged();
|
||||||
|
notifyObservers("update players");
|
||||||
|
ClientState.setBoatsUpdated(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to terminate the thread.
|
||||||
|
*
|
||||||
|
* Currently called by the main while loop when game started is detected.
|
||||||
|
*/
|
||||||
|
public void terminate() {
|
||||||
|
terminate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package seng302.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
import seng302.server.messages.BoatActionMessage;
|
||||||
|
import seng302.server.messages.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing a single connection to a Server for the purposes of sending and receiving on
|
||||||
|
* its own thread.
|
||||||
|
*/
|
||||||
|
public class ClientToServerThread implements Runnable {
|
||||||
|
|
||||||
|
private static final int LOG_LEVEL = 1;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private Integer ourID;
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private InputStream is;
|
||||||
|
private OutputStream os;
|
||||||
|
|
||||||
|
private Boolean updateClient = true;
|
||||||
|
private ByteArrayOutputStream crcBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ClientToServerThread which takes in ipAddress and portNumber and attempts to
|
||||||
|
* connect to the specified ipAddress and port.
|
||||||
|
*
|
||||||
|
* Upon successful socket connection, threeWayHandshake will be preformed and the instance will
|
||||||
|
* be put on a thread and run immediately.
|
||||||
|
*
|
||||||
|
* @param ipAddress a string of ip address to be connected to
|
||||||
|
* @param portNumber an integer port number
|
||||||
|
* @throws Exception SocketConnection if fail to connect to ip address and port number
|
||||||
|
* combination
|
||||||
|
*/
|
||||||
|
public ClientToServerThread(String ipAddress, Integer portNumber) throws Exception {
|
||||||
|
socket = new Socket(ipAddress, portNumber);
|
||||||
|
is = socket.getInputStream();
|
||||||
|
os = socket.getOutputStream();
|
||||||
|
|
||||||
|
Integer allocatedID = threeWayHandshake();
|
||||||
|
if (allocatedID != null) {
|
||||||
|
ourID = allocatedID;
|
||||||
|
clientLog("Successful handshake. Allocated ID: " + ourID, 0);
|
||||||
|
ClientState.setClientSourceId(String.valueOf(ourID));
|
||||||
|
} else {
|
||||||
|
clientLog("Unsuccessful handshake", 1);
|
||||||
|
closeSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out log messages and the time happened.
|
||||||
|
* Only perform task if log level is below LOG_LEVEL variable.
|
||||||
|
*
|
||||||
|
* @param message a string of message to be printed out
|
||||||
|
* @param logLevel an int for log level
|
||||||
|
*/
|
||||||
|
static void clientLog(String message, int logLevel) {
|
||||||
|
if (logLevel <= LOG_LEVEL) {
|
||||||
|
System.out.println(
|
||||||
|
"[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the thread loop. It exits the loop if ClientState connected to host
|
||||||
|
* variable is false.
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
int sync1;
|
||||||
|
int sync2;
|
||||||
|
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||||
|
while (ClientState.isConnectedToHost()) {
|
||||||
|
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) {
|
||||||
|
ClientPacketParser
|
||||||
|
.parsePacket(new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
} else {
|
||||||
|
clientLog("Packet has been dropped", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
closeSocket();
|
||||||
|
Platform.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Alert alert = new Alert(AlertType.ERROR);
|
||||||
|
alert.setHeaderText("Host has disconnected");
|
||||||
|
alert.setContentText("Cannot find Server");
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
clientLog("Disconnected from server", 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeSocket();
|
||||||
|
clientLog("Disconnected from server", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for an allocated sourceID and returns it to the server
|
||||||
|
*
|
||||||
|
* @return the sourceID allocated to us by the server
|
||||||
|
*/
|
||||||
|
private Integer threeWayHandshake() {
|
||||||
|
Integer ourSourceID = null;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
ourSourceID = is.read();
|
||||||
|
} catch (IOException e) {
|
||||||
|
clientLog("Three way handshake failed", 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (ourSourceID != null) {
|
||||||
|
try {
|
||||||
|
os.write(ourSourceID);
|
||||||
|
return ourSourceID;
|
||||||
|
} catch (IOException e) {
|
||||||
|
clientLog("Three way handshake failed", 1);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the post-start race course information
|
||||||
|
*/
|
||||||
|
public void sendBoatActionMessage(BoatActionMessage boatActionMessage) {
|
||||||
|
try {
|
||||||
|
os.write(boatActionMessage.getBuffer());
|
||||||
|
} catch (IOException e) {
|
||||||
|
clientLog("Could not write to server", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void closeSocket() {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
clientLog("Failed to close the socket", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int readByte() throws Exception {
|
||||||
|
int currentByte = -1;
|
||||||
|
try {
|
||||||
|
currentByte = is.read();
|
||||||
|
crcBuffer.write(currentByte);
|
||||||
|
} catch (IOException e) {
|
||||||
|
clientLog("Read byte 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 Thread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,597 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
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.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Polygon;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.client.ClientState;
|
||||||
|
import seng302.fxObjects.BoatGroup;
|
||||||
|
import seng302.models.Colors;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.mark.GateMark;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.fxObjects.MarkGroup;
|
||||||
|
import seng302.models.mark.MarkType;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
import seng302.models.map.Boundary;
|
||||||
|
import seng302.models.map.CanvasMap;
|
||||||
|
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.utilities.GeoPoint;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = 1100;
|
||||||
|
private final int CANVAS_HEIGHT = 920;
|
||||||
|
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<>();
|
||||||
|
private Text FPSDisplay = new Text();
|
||||||
|
private Polygon raceBorder = new Polygon();
|
||||||
|
|
||||||
|
//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();
|
||||||
|
FPSDisplay.setLayoutX(5);
|
||||||
|
FPSDisplay.setLayoutY(20);
|
||||||
|
FPSDisplay.setStrokeWidth(2);
|
||||||
|
group.getChildren().add(FPSDisplay);
|
||||||
|
group.getChildren().add(raceBorder);
|
||||||
|
initializeMarks();
|
||||||
|
initializeBoats();
|
||||||
|
|
||||||
|
timer = new AnimationTimer() {
|
||||||
|
private long lastTime = 0;
|
||||||
|
private int FPSCount = 30;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(long now) {
|
||||||
|
if (lastTime == 0) {
|
||||||
|
lastTime = now;
|
||||||
|
} else {
|
||||||
|
if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized
|
||||||
|
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 (FPSCount-- == 0) {
|
||||||
|
FPSCount = 30;
|
||||||
|
drawFps(frameRate.intValue());
|
||||||
|
}
|
||||||
|
raceViewController.updateSparkLine();
|
||||||
|
}
|
||||||
|
updateGroups();
|
||||||
|
if (ClientPacketParser.isRaceFinished()) {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
lastTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ClientPacketParser.isRaceFinished()) {
|
||||||
|
this.stop();
|
||||||
|
switchToFinishScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchToFinishScreen() {
|
||||||
|
try {
|
||||||
|
// canvas view -> anchor pane -> grid pane -> main view
|
||||||
|
GridPane gridPane = (GridPane) canvasPane.getParent().getParent();
|
||||||
|
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("/views/FinishScreenView.fxml")));
|
||||||
|
} catch (javafx.fxml.LoadException e) {
|
||||||
|
System.out.println("[Controller] FXML load exception");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[Controller] IO exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
||||||
|
GeoPoint 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));
|
||||||
|
GeoPoint 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 = ClientPacketParser.getXmlObject().getRaceXML();
|
||||||
|
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
|
||||||
|
raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
|
||||||
|
raceBorder.setStrokeWidth(3);
|
||||||
|
raceBorder.setFill(new Color(0,0,0,0));
|
||||||
|
List<Double> boundaryPoints = new ArrayList<>();
|
||||||
|
for (Limit limit : courseLimits) {
|
||||||
|
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
|
||||||
|
boundaryPoints.add(location.getX());
|
||||||
|
boundaryPoints.add(location.getY());
|
||||||
|
}
|
||||||
|
raceBorder.getPoints().setAll(boundaryPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (ClientPacketParser.boatLocations.containsKey(boatGroup.getRaceId())) {
|
||||||
|
updateBoatGroup(boatGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MarkGroup markGroup : markGroups) {
|
||||||
|
for (Long id : markGroup.getRaceIds()) {
|
||||||
|
if (ClientPacketParser.markLocations.containsKey(id)) {
|
||||||
|
updateMarkGroup(id, markGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkForCourseChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForCourseChanges() {
|
||||||
|
if (ClientPacketParser.isNewRaceXmlReceived()) {
|
||||||
|
addRaceBorder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBoatGroup(BoatGroup boatGroup) {
|
||||||
|
PriorityBlockingQueue<BoatPositionPacket> movementQueue = ClientPacketParser.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);
|
||||||
|
} catch (InterruptedException e){
|
||||||
|
System.out.println("[CanvasController] Interrupted Exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateMarkGroup (long raceId, MarkGroup markGroup) {
|
||||||
|
PriorityBlockingQueue<BoatPositionPacket> movementQueue = ClientPacketParser.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) {
|
||||||
|
System.out.println("[CanvasController] Interrupted exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws all the boats.
|
||||||
|
*/
|
||||||
|
private void initializeBoats() {
|
||||||
|
Map<Integer, Yacht> boats = ClientPacketParser.getBoats();
|
||||||
|
Group wakes = new Group();
|
||||||
|
Group trails = new Group();
|
||||||
|
Group annotations = new Group();
|
||||||
|
|
||||||
|
ArrayList<Participant> participants = ClientPacketParser.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());
|
||||||
|
if (boat.getSourceId().equals(Integer.parseInt(ClientState.getClientSourceId()))) {
|
||||||
|
boatGroup.setAsPlayer();
|
||||||
|
boatGroups.add(boatGroup);
|
||||||
|
annotations.getChildren().add(boatGroup.getAnnotations());
|
||||||
|
} else {
|
||||||
|
//Move annotations and boat to bottom of group keeping player ontop.
|
||||||
|
if (boatGroups.size() > 0) {
|
||||||
|
boatGroups.add(0, boatGroup);
|
||||||
|
} else {
|
||||||
|
boatGroups.add(boatGroup);
|
||||||
|
}
|
||||||
|
if (annotations.getChildren().size() > 0) {
|
||||||
|
annotations.getChildren().add(0, boatGroup.getAnnotations());
|
||||||
|
} else {
|
||||||
|
annotations.getChildren().add(boatGroup.getAnnotations());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trails.getChildren().add(boatGroup.getTrail());
|
||||||
|
wakes.getChildren().add(boatGroup.getWake());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.getChildren().addAll(trails);
|
||||||
|
group.getChildren().addAll(wakes);
|
||||||
|
group.getChildren().addAll(annotations);
|
||||||
|
group.getChildren().addAll(boatGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeMarks() {
|
||||||
|
List<Mark> allMarks = ClientPacketParser.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()){
|
||||||
|
FPSDisplay.setVisible(true);
|
||||||
|
FPSDisplay.setText(String.format("%d FPS", fps));
|
||||||
|
} else {
|
||||||
|
FPSDisplay.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
ClientPacketParser.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 : ClientPacketParser.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.client.ClientState;
|
||||||
|
import seng302.client.ClientToServerThread;
|
||||||
|
import seng302.server.messages.BoatActionMessage;
|
||||||
|
import seng302.server.messages.BoatActionType;
|
||||||
|
|
||||||
|
public class Controller implements Initializable {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private AnchorPane contentPane;
|
||||||
|
private ClientToServerThread clientToServerThread;
|
||||||
|
private long lastSendingTime;
|
||||||
|
private int KEY_STROKE_SENDING_FREQUENCY = 50;
|
||||||
|
|
||||||
|
public Object setContentPane(String jfxUrl) {
|
||||||
|
try {
|
||||||
|
contentPane.getChildren().removeAll();
|
||||||
|
contentPane.getChildren().clear();
|
||||||
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl)));
|
||||||
|
Parent view = fxmlLoader.load();
|
||||||
|
contentPane.getChildren().addAll(view);
|
||||||
|
return fxmlLoader.getController();
|
||||||
|
} catch (javafx.fxml.LoadException e) {
|
||||||
|
System.err.println(e.getCause());
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
setUpStartScreen();
|
||||||
|
lastSendingTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUpStartScreen() {
|
||||||
|
contentPane.getChildren().removeAll();
|
||||||
|
contentPane.getChildren().clear();
|
||||||
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml");
|
||||||
|
startScreenController.setController(this);
|
||||||
|
ClientPacketParser.boatLocations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Handle the key-pressed event from the text field. */
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
BoatActionMessage boatActionMessage;
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY && ClientState.isRaceStarted()) {
|
||||||
|
lastSendingTime = currentTime;
|
||||||
|
switch (e.getCode()) {
|
||||||
|
case SPACE: // align with vmg
|
||||||
|
boatActionMessage = new BoatActionMessage(BoatActionType.VMG);
|
||||||
|
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||||
|
break;
|
||||||
|
case PAGE_UP: // upwind
|
||||||
|
boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND);
|
||||||
|
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||||
|
break;
|
||||||
|
case PAGE_DOWN: // downwind
|
||||||
|
boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND);
|
||||||
|
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||||
|
break;
|
||||||
|
case ENTER: // tack/gybe
|
||||||
|
boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE);
|
||||||
|
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||||
|
break;
|
||||||
|
//TODO Allow a zoom in and zoom out methods
|
||||||
|
case Z: // zoom in
|
||||||
|
System.out.println("Zoom in");
|
||||||
|
break;
|
||||||
|
case X: // zoom out
|
||||||
|
System.out.println("Zoom out");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
switch (e.getCode()) {
|
||||||
|
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
||||||
|
case SHIFT: // sails in/sails out
|
||||||
|
BoatActionMessage boatActionMessage = new BoatActionMessage(BoatActionType.SAILS_IN);
|
||||||
|
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientToServerThread(ClientToServerThread ctt) {
|
||||||
|
clientToServerThread = ctt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
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 seng302.client.ClientPacketParser;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
|
|
||||||
|
public class FinishScreenViewController implements Initializable {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private GridPane finishScreenGridPane;
|
||||||
|
@FXML
|
||||||
|
private TableView<Yacht> finishOrderTable;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> posCol;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> boatNameCol;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> shortNameCol;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> countryCol;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
finishScreenGridPane.getStylesheets()
|
||||||
|
.add(getClass().getResource("/css/master.css").toString());
|
||||||
|
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
|
||||||
|
// set up data for table
|
||||||
|
ObservableList<Yacht> data = FXCollections.observableArrayList();
|
||||||
|
finishOrderTable.setItems(data);
|
||||||
|
|
||||||
|
// setting table col data
|
||||||
|
posCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("position")
|
||||||
|
);
|
||||||
|
boatNameCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("boatName")
|
||||||
|
);
|
||||||
|
shortNameCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("shortName")
|
||||||
|
);
|
||||||
|
countryCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("country")
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if the boat is racing
|
||||||
|
ArrayList<Participant> participants = ClientPacketParser.getXmlObject().getRaceXML()
|
||||||
|
.getParticipants();
|
||||||
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
|
for (Participant p : participants) {
|
||||||
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add data to table
|
||||||
|
for (Yacht boat : ClientPacketParser.getBoatsPos().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceId())) {
|
||||||
|
data.add(boat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finishOrderTable.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setContentPane(String jfxUrl) {
|
||||||
|
try {
|
||||||
|
// get the main controller anchor pane (FinishView -> MainView)
|
||||||
|
AnchorPane contentPane = (AnchorPane) finishScreenGridPane.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) {
|
||||||
|
System.out.println("[Controller] FXML load exception");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[Controller] IO exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchToStartScreenView() {
|
||||||
|
setContentPane("/views/StartScreenView.fxml");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
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.ListView;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import seng302.client.ClientState;
|
||||||
|
import seng302.client.ClientStateQueryingRunnable;
|
||||||
|
import seng302.gameServer.GameStages;
|
||||||
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.gameServer.MainServerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing the actions of the lobby screen
|
||||||
|
* Created by wmu16 on 10/07/17.
|
||||||
|
*/
|
||||||
|
public class LobbyController implements Initializable, Observer{
|
||||||
|
@FXML
|
||||||
|
private GridPane lobbyScreen;
|
||||||
|
@FXML
|
||||||
|
private Text lobbyIpText;
|
||||||
|
@FXML
|
||||||
|
private Button readyButton;
|
||||||
|
@FXML
|
||||||
|
private ListView firstListView;
|
||||||
|
@FXML
|
||||||
|
private ListView secondListView;
|
||||||
|
@FXML
|
||||||
|
private ListView thirdListView;
|
||||||
|
@FXML
|
||||||
|
private ListView fourthListView;
|
||||||
|
@FXML
|
||||||
|
private ListView fifthListView;
|
||||||
|
@FXML
|
||||||
|
private ListView sixthListView;
|
||||||
|
@FXML
|
||||||
|
private ListView seventhListView;
|
||||||
|
@FXML
|
||||||
|
private ListView eighthListView;
|
||||||
|
@FXML
|
||||||
|
private ImageView firstImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView secondImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView thirdImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView fourthImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView fifthImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView sixthImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView seventhImageView;
|
||||||
|
@FXML
|
||||||
|
private ImageView eighthImageView;
|
||||||
|
|
||||||
|
private static List<ObservableList<String>> competitors = new ArrayList<>();
|
||||||
|
private static ObservableList<String> firstCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> secondCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> thirdCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> fourthCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> fifthCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> sixthCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> seventhCompetitor = FXCollections.observableArrayList();
|
||||||
|
private static ObservableList<String> eighthCompetitor = FXCollections.observableArrayList();
|
||||||
|
private ClientStateQueryingRunnable clientStateQueryingRunnable;
|
||||||
|
private static List<ImageView> imageViews;
|
||||||
|
private static List<ListView> listViews;
|
||||||
|
|
||||||
|
private int MAX_NUM_PLAYERS = 8;
|
||||||
|
|
||||||
|
private Boolean switchedPane = false;
|
||||||
|
private MainServerThread mainServerThread;
|
||||||
|
private Controller controller;
|
||||||
|
|
||||||
|
private void setContentPane(String jfxUrl) {
|
||||||
|
try {
|
||||||
|
AnchorPane contentPane = (AnchorPane) lobbyScreen.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) {
|
||||||
|
System.out.println("[Controller] FXML load exception");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[Controller] IO exception");
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
// System.out.println("[Controller] Null Pointer Exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
if (ClientState.isHost()) {
|
||||||
|
lobbyIpText.setText("Lobby Host IP: " + ClientState.getHostIp());
|
||||||
|
readyButton.setDisable(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lobbyIpText.setText("Connected to IP: " + ClientState.getHostIp());
|
||||||
|
readyButton.setDisable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// put all javafx objects in lists, so we can iterate though conveniently
|
||||||
|
imageViews = new ArrayList<>();
|
||||||
|
Collections.addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView,
|
||||||
|
fifthImageView, sixthImageView, seventhImageView, eighthImageView);
|
||||||
|
listViews = new ArrayList<>();
|
||||||
|
Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView,
|
||||||
|
sixthListView, seventhListView, eighthListView);
|
||||||
|
competitors = new ArrayList<>();
|
||||||
|
Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor,
|
||||||
|
fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor);
|
||||||
|
|
||||||
|
initialiseListView();
|
||||||
|
initialiseImageView(); // parrot gif init
|
||||||
|
|
||||||
|
// set up client state query thread, so that when it receives the race-started packet
|
||||||
|
// it can switch to the race view
|
||||||
|
ClientStateQueryingRunnable clientStateQueryingRunnable = new ClientStateQueryingRunnable();
|
||||||
|
clientStateQueryingRunnable.addObserver(this);
|
||||||
|
Thread clientStateQueryingThread = new Thread(clientStateQueryingRunnable, "Client State querying thread");
|
||||||
|
clientStateQueryingThread.setDaemon(true);
|
||||||
|
clientStateQueryingThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observers "ClientStateQueryingRunnable".
|
||||||
|
* When the clients state has been marked to "race start", the querying thread
|
||||||
|
* will notify this lobby to change the view
|
||||||
|
* @param o
|
||||||
|
* @param arg
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
Platform.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (arg.equals("game started") && !switchedPane) {
|
||||||
|
switchToRaceView();
|
||||||
|
}
|
||||||
|
if (arg.equals(("update players"))) {
|
||||||
|
initialiseListView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all ListViews and ImageViews according to the current competitors
|
||||||
|
*/
|
||||||
|
private void initialiseListView() {
|
||||||
|
listViews.forEach(listView -> listView.getItems().clear());
|
||||||
|
imageViews.forEach(gif -> gif.setVisible(false));
|
||||||
|
competitors.forEach(ol -> ol.removeAll());
|
||||||
|
|
||||||
|
List<Integer> ids = new ArrayList<>(ClientState.getBoats().keySet());
|
||||||
|
for (int i = 0; i < ids.size(); i++) {
|
||||||
|
competitors.get(i).add(ClientState.getBoats().get(ids.get(i)).getBoatName());
|
||||||
|
listViews.get(i).setItems(competitors.get(i));
|
||||||
|
imageViews.get(i).setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads preset images into imageViews
|
||||||
|
*/
|
||||||
|
private void initialiseImageView() {
|
||||||
|
for (int i = 0; i < MAX_NUM_PLAYERS; i++) {
|
||||||
|
imageViews.get(i).setImage(new Image(getClass().getResourceAsStream("/pics/sail.png")));
|
||||||
|
}
|
||||||
|
// Image image1 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// firstImageView.setImage(image1);
|
||||||
|
// Image image2 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// secondImageView.setImage(image2);
|
||||||
|
// Image image3 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// thirdImageView.setImage(image3);
|
||||||
|
// Image image4 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// fourthImageView.setImage(image4);
|
||||||
|
// Image image5 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// fifthImageView.setImage(image5);
|
||||||
|
// Image image6 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// sixthImageView.setImage(image6);
|
||||||
|
// Image image7 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// seventhImageView.setImage(image7);
|
||||||
|
// Image image8 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||||
|
// eighthImageView.setImage(image8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void leaveLobbyButtonPressed() {
|
||||||
|
if (ClientState.isHost()) {
|
||||||
|
GameState.setCurrentStage(GameStages.CANCELLED);
|
||||||
|
mainServerThread.terminate();
|
||||||
|
}
|
||||||
|
ClientState.setConnectedToHost(false);
|
||||||
|
controller.setUpStartScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void readyButtonPressed() {
|
||||||
|
GameState.setCurrentStage(GameStages.RACING);
|
||||||
|
mainServerThread.startGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void switchToRaceView() {
|
||||||
|
if (!switchedPane) {
|
||||||
|
switchedPane = true;
|
||||||
|
setContentPane("/views/RaceView.fxml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainServerThread(MainServerThread mainServerThread) {
|
||||||
|
this.mainServerThread = mainServerThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setController(Controller controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,668 @@
|
|||||||
|
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.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.client.ClientPacketParser;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
import seng302.controllers.annotations.Annotation;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationController;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationDelegate;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationsState;
|
||||||
|
import seng302.fxObjects.BoatGroup;
|
||||||
|
import seng302.fxObjects.MarkGroup;
|
||||||
|
import seng302.models.*;
|
||||||
|
import seng302.models.mark.GateMark;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
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 Text windSpeedText;
|
||||||
|
@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<>(ClientPacketParser.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) {
|
||||||
|
System.out.println("[RaceViewController] IO exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
updateOrder();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 = ClientPacketParser.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 : ClientPacketParser.getXmlObject().getRaceXML()
|
||||||
|
.getAllCompoundMarks()) {
|
||||||
|
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the wind direction arrow and text as from info from the ClientPacketParser
|
||||||
|
*/
|
||||||
|
private void updateWindDirection() {
|
||||||
|
windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection()));
|
||||||
|
windArrowText.setRotate(ClientPacketParser.getWindDirection());
|
||||||
|
windSpeedText.setText(String.format("%.1f Knots", ClientPacketParser.getWindSpeed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the clock for the race
|
||||||
|
*/
|
||||||
|
private void updateRaceTime() {
|
||||||
|
if (ClientPacketParser.isRaceFinished()) {
|
||||||
|
timerLabel.setFill(Color.RED);
|
||||||
|
timerLabel.setText("Race Finished!");
|
||||||
|
} else {
|
||||||
|
timerLabel.setText(getTimeSinceStartOfRace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grabs the boats currently in the race as from the ClientPacketParser and sets them to be selectable
|
||||||
|
* in the boat selection combo box
|
||||||
|
*/
|
||||||
|
private void updateBoatSelectionComboBox() {
|
||||||
|
ObservableList<Yacht> observableBoats = FXCollections
|
||||||
|
.observableArrayList(ClientPacketParser.getBoatsPos().values());
|
||||||
|
boatSelectionComboBox.setItems(observableBoats);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the order of the boats as from the ClientPacketParser 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 = ClientPacketParser.getXmlObject().getRaceXML()
|
||||||
|
.getParticipants();
|
||||||
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
|
for (Participant p : participants) {
|
||||||
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientPacketParser.isRaceStarted()) {
|
||||||
|
/*
|
||||||
|
for (Yacht boat : ClientPacketParser.getBoatsPos().values()) {
|
||||||
|
System.out.println("Hi tjere" + boat.getBoatName());
|
||||||
|
if (participantIDs.contains(boat.getSourceId()) || true
|
||||||
|
) { // check if the boat is racing
|
||||||
|
if (boat.getBoatStatus() == 69) { // 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);
|
||||||
|
System.out.println("Adding " + textToAdd.getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
for (Yacht boat : ClientPacketParser.getBoats().values()){
|
||||||
|
Text textToAdd = new Text(boat.getSourceId() + ". " + boat.getShortName() + " ");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
textToAdd.setStyle("");
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Yacht boat : ClientPacketParser.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(ClientPacketParser.getWindSpeed());
|
||||||
|
} else {
|
||||||
|
angleAndSpeed = PolarTable
|
||||||
|
.getOptimalDownwindVMG(ClientPacketParser.getWindSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
||||||
|
|
||||||
|
|
||||||
|
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
||||||
|
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
||||||
|
Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
||||||
|
Line rightLayline = new Line();
|
||||||
|
Line leftLayline = new Line();
|
||||||
|
if (lineFuncResult == 1) {
|
||||||
|
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle,
|
||||||
|
ClientPacketParser
|
||||||
|
.getWindDirection());
|
||||||
|
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle,
|
||||||
|
ClientPacketParser
|
||||||
|
.getWindDirection());
|
||||||
|
} else if (lineFuncResult == -1) {
|
||||||
|
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle,
|
||||||
|
ClientPacketParser
|
||||||
|
.getWindDirection());
|
||||||
|
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle,
|
||||||
|
ClientPacketParser
|
||||||
|
.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 (ClientPacketParser.getTimeSinceStart() > 0) {
|
||||||
|
String timerMinute = Long.toString(ClientPacketParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long.toString(ClientPacketParser.getTimeSinceStart() % 60);
|
||||||
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
timerString = "-" + timerMinute + ":" + timerSecond;
|
||||||
|
} else {
|
||||||
|
String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60);
|
||||||
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
timerString = timerMinute + ":" + timerSecond;
|
||||||
|
}
|
||||||
|
return timerString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean isDisplayFps() {
|
||||||
|
return displayFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAnnotations(Integer annotationLevel) {
|
||||||
|
switch (annotationLevel) {
|
||||||
|
// No Annotations
|
||||||
|
case 0:
|
||||||
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
|
bg.setVisibility(false, false, false, false, false, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Important Annotations
|
||||||
|
case 1:
|
||||||
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
|
bg.setVisibility(
|
||||||
|
importantAnnotations.getAnnotationState(Annotation.NAME),
|
||||||
|
importantAnnotations.getAnnotationState(Annotation.SPEED),
|
||||||
|
importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
||||||
|
importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
||||||
|
importantAnnotations.getAnnotationState(Annotation.TRACK),
|
||||||
|
importantAnnotations.getAnnotationState(Annotation.WAKE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// All Annotations
|
||||||
|
case 2:
|
||||||
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
|
bg.setVisibility(true, true, true, true, true, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import seng302.client.ClientState;
|
||||||
|
import seng302.client.ClientToServerThread;
|
||||||
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.gameServer.MainServerThread;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class describing the actions of the start screen controller
|
||||||
|
* Created by wmu16 on 10/07/17.
|
||||||
|
*/
|
||||||
|
public class StartScreenController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField ipTextField;
|
||||||
|
@FXML
|
||||||
|
private TextField portTextField;
|
||||||
|
@FXML
|
||||||
|
private GridPane startScreen2;
|
||||||
|
|
||||||
|
private Controller controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the fxml content into the parent pane
|
||||||
|
* @param jfxUrl
|
||||||
|
* @return the controller of the fxml
|
||||||
|
*/
|
||||||
|
private Object setContentPane(String jfxUrl) {
|
||||||
|
try {
|
||||||
|
AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
|
||||||
|
contentPane.getChildren().removeAll();
|
||||||
|
contentPane.getChildren().clear();
|
||||||
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
|
||||||
|
contentPane.getChildren().addAll((Pane) fxmlLoader.load());
|
||||||
|
|
||||||
|
return fxmlLoader.getController();
|
||||||
|
} catch (javafx.fxml.LoadException e) {
|
||||||
|
System.out.println("[Controller] FXML load exception");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[Controller] IO exception");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ATTEMPTS TO:
|
||||||
|
* Sets up a new game state with your IP address as designated as the host.
|
||||||
|
* Starts a thread to listen for incoming connections.
|
||||||
|
* Starts a client to server thread and connects to own ip.
|
||||||
|
* Switches to the lobby screen
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
public void hostButtonPressed() {
|
||||||
|
try {
|
||||||
|
String ipAddress = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
// get the lobby controller so that we can pass the game server thread to it
|
||||||
|
new GameState(getLocalHostIp());
|
||||||
|
MainServerThread mainServerThread = new MainServerThread();
|
||||||
|
ClientState.setHost(true);
|
||||||
|
// host will connect and handshake to itself after setting up the server
|
||||||
|
// TODO: 24/07/17 wmu16 - Make port number some static global type constant?
|
||||||
|
ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942);
|
||||||
|
ClientState.setConnectedToHost(true);
|
||||||
|
controller.setClientToServerThread(clientToServerThread);
|
||||||
|
LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
|
||||||
|
lobbyController.setMainServerThread(mainServerThread);
|
||||||
|
lobbyController.setController(controller);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Alert alert = new Alert(AlertType.ERROR);
|
||||||
|
alert.setHeaderText("Cannot host");
|
||||||
|
alert.setContentText("Oops, failed to host, try to restart.");
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ATTEMPTS TO:
|
||||||
|
* Connect to an ip address and port using the ip and port specified on start screen.
|
||||||
|
* Starts a Client To Server Thread to maintain connection to host.
|
||||||
|
* Switch view to lobby view.
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
public void connectButtonPressed() {
|
||||||
|
// TODO: 10/07/17 wmu16 - Finish function
|
||||||
|
try {
|
||||||
|
String ipAddress = ipTextField.getText().trim().toLowerCase();
|
||||||
|
Integer port = Integer.valueOf(portTextField.getText().trim());
|
||||||
|
|
||||||
|
ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
|
||||||
|
ClientState.setHost(false);
|
||||||
|
ClientState.setConnectedToHost(true);
|
||||||
|
|
||||||
|
ClientState.setHostIp(ipAddress);
|
||||||
|
controller.setClientToServerThread(clientToServerThread);
|
||||||
|
LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
|
||||||
|
lobbyController.setController(controller);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Alert alert = new Alert(AlertType.ERROR);
|
||||||
|
alert.setHeaderText("Cannot reach the host");
|
||||||
|
alert.setContentText("Please check your host IP address.");
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setController(Controller controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the local host ip address and sets this ip to ClientState.
|
||||||
|
* Only runs by the host.
|
||||||
|
*
|
||||||
|
* @return the localhost ip address
|
||||||
|
*/
|
||||||
|
private String getLocalHostIp() {
|
||||||
|
String ipAddress = null;
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
NetworkInterface ni = e.nextElement();
|
||||||
|
if (ni.isLoopback())
|
||||||
|
continue;
|
||||||
|
if(ni.isPointToPoint())
|
||||||
|
continue;
|
||||||
|
if(ni.isVirtual())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||||
|
while(addresses.hasMoreElements()) {
|
||||||
|
InetAddress address = addresses.nextElement();
|
||||||
|
if(address instanceof Inet4Address) { // skip all ipv6
|
||||||
|
ipAddress = address.getHostAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("[StartScreenController] Exception");
|
||||||
|
}
|
||||||
|
if (ipAddress == null) {
|
||||||
|
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||||
|
}
|
||||||
|
ClientState.setHostIp(ipAddress);
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotations the user can select as important
|
||||||
|
*/
|
||||||
|
public enum Annotation {
|
||||||
|
SPEED,
|
||||||
|
WAKE,
|
||||||
|
TRACK,
|
||||||
|
NAME,
|
||||||
|
ESTTIMETONEXTMARK,
|
||||||
|
LEGTIME
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.net.URL;;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class ImportantAnnotationController implements Initializable {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JavaFX Outlets
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatWakeSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatSpeedSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatTrackSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatNameSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatEstTimeToNextMarkSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatElapsedTimeSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private AnchorPane annotationSelectWindow;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button closeButton;
|
||||||
|
|
||||||
|
private ImportantAnnotationDelegate delegate;
|
||||||
|
private ImportantAnnotationsState importantAnnotationsState;
|
||||||
|
private Stage stage;
|
||||||
|
|
||||||
|
public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
importantAnnotationsState = new ImportantAnnotationsState();
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not an annotation is considered important, then
|
||||||
|
* sends an update to the delegate
|
||||||
|
*
|
||||||
|
* @param annotation The annotation
|
||||||
|
* @param isSet True if annotation is important
|
||||||
|
*/
|
||||||
|
private void setAnnotation(Annotation annotation, Boolean isSet) {
|
||||||
|
importantAnnotationsState.setAnnotationState(annotation, isSet);
|
||||||
|
sendUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an update to the delegate when the important
|
||||||
|
* annotations have changed
|
||||||
|
*/
|
||||||
|
private void sendUpdate() {
|
||||||
|
this.delegate.importantAnnotationsChanged(importantAnnotationsState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the current state of the 'important annotations'
|
||||||
|
*
|
||||||
|
* @param currentState hashmap containing the states of each annotation
|
||||||
|
*/
|
||||||
|
public void loadState(ImportantAnnotationsState currentState) {
|
||||||
|
this.importantAnnotationsState = currentState;
|
||||||
|
|
||||||
|
// Initialise checkboxes
|
||||||
|
for (Annotation annotation : importantAnnotationsState.getAnnotations()) {
|
||||||
|
switch (annotation) {
|
||||||
|
case WAKE:
|
||||||
|
boatWakeSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPEED:
|
||||||
|
boatSpeedSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TRACK:
|
||||||
|
boatTrackSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAME:
|
||||||
|
boatNameSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESTTIMETONEXTMARK:
|
||||||
|
boatEstTimeToNextMarkSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LEGTIME:
|
||||||
|
boatElapsedTimeSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View did load
|
||||||
|
*
|
||||||
|
* @param location .
|
||||||
|
* @param resources .
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
boatWakeSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.WAKE, boatWakeSelect.isSelected()));
|
||||||
|
boatSpeedSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.SPEED, boatSpeedSelect.isSelected()));
|
||||||
|
boatTrackSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.TRACK, boatTrackSelect.isSelected()));
|
||||||
|
boatNameSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.NAME, boatNameSelect.isSelected()));
|
||||||
|
boatEstTimeToNextMarkSelect.setOnAction(event -> setAnnotation(Annotation.ESTTIMETONEXTMARK,
|
||||||
|
boatEstTimeToNextMarkSelect.isSelected()));
|
||||||
|
boatElapsedTimeSelect.setOnAction(
|
||||||
|
event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected()));
|
||||||
|
// TODO: 26/07/17 cir27 - Create a more robust fix for this when the annotation for the game are decided upon.
|
||||||
|
boatEstTimeToNextMarkSelect.setVisible(false);
|
||||||
|
boatEstTimeToNextMarkSelect.setDisable(true);
|
||||||
|
boatElapsedTimeSelect.setVisible(false);
|
||||||
|
boatElapsedTimeSelect.setDisable(true);
|
||||||
|
closeButton.setOnAction(event -> stage.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ImportantAnnotationDelegate handles updating the important annotations
|
||||||
|
* displayed to the user on behalf of the ImportantAnnotationController
|
||||||
|
*/
|
||||||
|
public interface ImportantAnnotationDelegate {
|
||||||
|
/**
|
||||||
|
* The important annotations have been changed, update the
|
||||||
|
* annotations displayed to the user
|
||||||
|
* @param importantAnnotationsState The current state of the selected annotations
|
||||||
|
*/
|
||||||
|
void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ImportantAnnotationsState {
|
||||||
|
public static final Boolean DEFAULT_ANNOTATION_STATE = true;
|
||||||
|
private Map<Annotation, Boolean> currentState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the users preference for the annotations
|
||||||
|
* they consider to be important
|
||||||
|
*/
|
||||||
|
public ImportantAnnotationsState(){
|
||||||
|
this.currentState = new HashMap<>();
|
||||||
|
initialiseState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set each annotation to the default annotation state
|
||||||
|
*/
|
||||||
|
private void initialiseState(){
|
||||||
|
for (Annotation annotation : getAnnotations()){
|
||||||
|
currentState.put(annotation, DEFAULT_ANNOTATION_STATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state (visibility) of an annotation
|
||||||
|
* @param annotation The annotation to set
|
||||||
|
* @param visible Whether or not the annotation should be visible
|
||||||
|
*/
|
||||||
|
public void setAnnotationState(Annotation annotation, Boolean visible){
|
||||||
|
this.currentState.put(annotation, visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state (visibility) of a specific annotation
|
||||||
|
* @param annotation The annotation to check
|
||||||
|
* @return True if visible, else false
|
||||||
|
*/
|
||||||
|
public Boolean getAnnotationState(Annotation annotation){
|
||||||
|
return this.currentState.containsKey(annotation) && this.currentState.get(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Return an array containing all defined annotations
|
||||||
|
*/
|
||||||
|
public Annotation[] getAnnotations(){
|
||||||
|
return Annotation.class.getEnumConstants();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package seng302.fxObjects;
|
||||||
|
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of annotations for boats.
|
||||||
|
*/
|
||||||
|
public class BoatAnnotations extends Group{
|
||||||
|
|
||||||
|
//Text offset constants
|
||||||
|
private static final double X_OFFSET_TEXT = 18d;
|
||||||
|
private static final double Y_OFFSET_TEXT_INIT = -29d;
|
||||||
|
private static final double Y_OFFSET_PER_TEXT = 12d;
|
||||||
|
//Background constants
|
||||||
|
private static final double TEXT_BUFFER = 3;
|
||||||
|
private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER;
|
||||||
|
private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER;
|
||||||
|
private static final double BACKGROUND_H_PER_TEXT = 9.5d;
|
||||||
|
private static final double BACKGROUND_W = 125d;
|
||||||
|
private static final double BACKGROUND_ARC_SIZE = 10;
|
||||||
|
|
||||||
|
private Rectangle background = new Rectangle();
|
||||||
|
private Text teamNameObject;
|
||||||
|
private Text velocityObject;
|
||||||
|
private Text estTimeToNextMarkObject;
|
||||||
|
private Text legTimeObject;
|
||||||
|
private boolean isPlayer = false;
|
||||||
|
private Yacht boat;
|
||||||
|
|
||||||
|
BoatAnnotations (Yacht boat, Color theme) {
|
||||||
|
super.setCache(true);
|
||||||
|
this.boat = boat;
|
||||||
|
background.setX(BACKGROUND_X);
|
||||||
|
background.setY(BACKGROUND_Y);
|
||||||
|
background.setWidth(BACKGROUND_W);
|
||||||
|
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4);
|
||||||
|
background.setArcHeight(BACKGROUND_ARC_SIZE);
|
||||||
|
background.setArcWidth(BACKGROUND_ARC_SIZE);
|
||||||
|
background.setFill(new Color(1, 1, 1, 0.75));
|
||||||
|
background.setStroke(theme);
|
||||||
|
background.setStrokeWidth(2);
|
||||||
|
background.setCache(true);
|
||||||
|
background.setCacheHint(CacheHint.SPEED);
|
||||||
|
|
||||||
|
teamNameObject = getTextObject(boat.getShortName(), theme);
|
||||||
|
teamNameObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT);
|
||||||
|
|
||||||
|
velocityObject = getTextObject("0 m/s", theme);
|
||||||
|
velocityObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 2);
|
||||||
|
velocityObject.setVisible(false);
|
||||||
|
|
||||||
|
estTimeToNextMarkObject = getTextObject("Next mark: ", theme);
|
||||||
|
estTimeToNextMarkObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 3);
|
||||||
|
estTimeToNextMarkObject.setVisible(false);
|
||||||
|
|
||||||
|
legTimeObject = getTextObject("Last mark: -", theme);
|
||||||
|
legTimeObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 4);
|
||||||
|
legTimeObject.setVisible(false);
|
||||||
|
|
||||||
|
this.setVisible(true, false, false, false);
|
||||||
|
|
||||||
|
super.getChildren().addAll(background, teamNameObject, velocityObject, estTimeToNextMarkObject, legTimeObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.setStrokeWidth(2);
|
||||||
|
text.setCacheHint(CacheHint.SPEED);
|
||||||
|
text.setCache(true);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update () {
|
||||||
|
teamNameObject.setText("Player: " + boat.getShortName());
|
||||||
|
|
||||||
|
velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocityMMS())));
|
||||||
|
|
||||||
|
if (boat.getTimeTillNext() != null) {
|
||||||
|
DateFormat format = new SimpleDateFormat("mm:ss");
|
||||||
|
String timeToNextMark = format
|
||||||
|
.format(boat.getTimeTillNext() - ClientPacketParser.getCurrentTimeLong());
|
||||||
|
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
|
||||||
|
} else {
|
||||||
|
estTimeToNextMarkObject.setText("Next mark: -");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boat.getMarkRoundTime() != null) {
|
||||||
|
DateFormat format = new SimpleDateFormat("mm:ss");
|
||||||
|
String elapsedTime = format
|
||||||
|
.format(ClientPacketParser.getCurrentTimeLong() - boat.getMarkRoundTime());
|
||||||
|
legTimeObject.setText("Last mark: " + elapsedTime);
|
||||||
|
}else {
|
||||||
|
legTimeObject.setText("Last mark: - ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVisible(boolean nameVisibility, boolean speedVisibility,
|
||||||
|
boolean estTimeVisibility, boolean lastMarkVisibility) {
|
||||||
|
int totalVisible = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a temporary fix until the new annotation group is added along with the visualiser
|
||||||
|
overhaul.
|
||||||
|
*/
|
||||||
|
totalVisible = updateVisibility(nameVisibility, teamNameObject, totalVisible);
|
||||||
|
if (isPlayer)
|
||||||
|
totalVisible = updateVisibility(speedVisibility, velocityObject, totalVisible);
|
||||||
|
// totalVisible = updateVisibility(estTimeVisibility, estTimeToNextMarkObject, totalVisible);
|
||||||
|
// totalVisible = updateVisibility(lastMarkVisibility, legTimeObject, totalVisible);
|
||||||
|
if (totalVisible != 0) {
|
||||||
|
background.setVisible(true);
|
||||||
|
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * totalVisible);
|
||||||
|
} else {
|
||||||
|
background.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int updateVisibility (boolean visibility, Text text, int totalVisible) {
|
||||||
|
if (visibility){
|
||||||
|
totalVisible ++;
|
||||||
|
text.setVisible(true);
|
||||||
|
text.setLayoutX(X_OFFSET_TEXT);
|
||||||
|
text.setLayoutY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * totalVisible);
|
||||||
|
} else {
|
||||||
|
text.setVisible(false);
|
||||||
|
}
|
||||||
|
return totalVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets these annotations to show more detailed info.
|
||||||
|
*/
|
||||||
|
public void setAsPlayer () {
|
||||||
|
isPlayer = true;
|
||||||
|
velocityObject.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
package seng302.fxObjects;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import javafx.scene.shape.Line;
|
||||||
|
import javafx.scene.shape.Polygon;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
import seng302.controllers.CanvasController;
|
||||||
|
import seng302.models.mark.GateMark;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 Wake wake;
|
||||||
|
private Line leftLayLine;
|
||||||
|
private Line rightLayline;
|
||||||
|
private Double distanceTravelled = 0.0;
|
||||||
|
private Point2D lastPoint;
|
||||||
|
private boolean destinationSet;
|
||||||
|
private BoatAnnotations boatAnnotations;
|
||||||
|
private Color color;
|
||||||
|
private Boolean isSelected = true; //All boats are initialised as selected\
|
||||||
|
private boolean isPlayer = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
destinationSet = false;
|
||||||
|
this.boat = boat;
|
||||||
|
initChildren(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) {
|
||||||
|
destinationSet = false;
|
||||||
|
this.boat = boat;
|
||||||
|
initChildren(color, points);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
this.color = color;
|
||||||
|
boatPoly = new Polygon(points);
|
||||||
|
boatPoly.setFill(this.color);
|
||||||
|
boatPoly.setOnMouseEntered(event -> {
|
||||||
|
boatPoly.setFill(Color.FLORALWHITE);
|
||||||
|
boatPoly.setStroke(Color.RED);
|
||||||
|
});
|
||||||
|
boatPoly.setOnMouseExited(event -> {
|
||||||
|
boatPoly.setFill(this.color);
|
||||||
|
boatPoly.setStroke(Color.BLACK);
|
||||||
|
});
|
||||||
|
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
||||||
|
boatPoly.setCache(true);
|
||||||
|
boatPoly.setCacheHint(CacheHint.SPEED);
|
||||||
|
boatAnnotations = new BoatAnnotations(boat, this.color);
|
||||||
|
|
||||||
|
leftLayLine = new Line();
|
||||||
|
rightLayline = new Line();
|
||||||
|
|
||||||
|
wake = new Wake(0, -BOAT_HEIGHT);
|
||||||
|
super.getChildren().addAll(boatPoly, boatAnnotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx);
|
||||||
|
boatAnnotations.setLayoutY(boatAnnotations.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);
|
||||||
|
boatAnnotations.setLayoutX(x);
|
||||||
|
boatAnnotations.setLayoutY(y);
|
||||||
|
wake.setLayoutX(x);
|
||||||
|
wake.setLayoutY(y);
|
||||||
|
wake.rotate(rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotateTo(double rotation) {
|
||||||
|
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
|
||||||
|
destinationSet = true;
|
||||||
|
Double dx = Math.abs(boatPoly.getLayoutX() - newXValue);
|
||||||
|
Double dy = Math.abs(boatPoly.getLayoutY() - newYValue);
|
||||||
|
moveTo(newXValue, newYValue, rotation);
|
||||||
|
|
||||||
|
|
||||||
|
rotateTo(rotation);
|
||||||
|
wake.setRotation(rotation, groundSpeed);
|
||||||
|
boat.setVelocity(groundSpeed);
|
||||||
|
isStopped = false;
|
||||||
|
lastRotation = rotation;
|
||||||
|
boatAnnotations.update();
|
||||||
|
|
||||||
|
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
|
||||||
|
|
||||||
|
if (distanceTravelled > 10 && isPlayer) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = ClientPacketParser.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 = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
||||||
|
|
||||||
|
|
||||||
|
Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
||||||
|
Integer windLineFuncResult = GeoUtility.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;
|
||||||
|
setLineGroupVisible(isSelected);
|
||||||
|
setWakeVisible(isSelected);
|
||||||
|
boatAnnotations.setVisible(isSelected);
|
||||||
|
setLayLinesVisible(isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) {
|
||||||
|
boatAnnotations.setVisible(teamName, velocity, estTime, legTime);
|
||||||
|
this.wake.setVisible(wake);
|
||||||
|
this.lineGroup.setVisible(trail);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Group getWake () {
|
||||||
|
return wake;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Group getTrail() {
|
||||||
|
return lineGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Group getAnnotations() {
|
||||||
|
return boatAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getBoatLayoutX() {
|
||||||
|
return boatPoly.getLayoutX();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Double getBoatLayoutY() {
|
||||||
|
return boatPoly.getLayoutY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStopped() {
|
||||||
|
return isStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boat.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this boat to appear highlighted
|
||||||
|
*/
|
||||||
|
public void setAsPlayer() {
|
||||||
|
boatPoly.getPoints().setAll(
|
||||||
|
-BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
|
||||||
|
0.0, -BOAT_HEIGHT / 1.75,
|
||||||
|
BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
|
||||||
|
);
|
||||||
|
boatPoly.setStroke(Color.BLACK);
|
||||||
|
boatPoly.setStrokeWidth(3);
|
||||||
|
boatAnnotations.setAsPlayer();
|
||||||
|
isPlayer = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package seng302.fxObjects;
|
||||||
|
|
||||||
|
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.models.mark.GateMark;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.mark.MarkType;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveMarkTo (double x, double y, long raceId)
|
||||||
|
{
|
||||||
|
if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||||
|
Circle markCircle = (Circle) super.getChildren().get(0);
|
||||||
|
//One of the test streams produced frequent, jittery movements. Added this as a fix.
|
||||||
|
if (Math.abs(markCircle.getCenterX() - x) > 5 || Math.abs(markCircle.getCenterY() - y) > 5) {
|
||||||
|
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) {
|
||||||
|
if (Math.abs(markCircle1.getCenterX() - x) > 5 || Math.abs(markCircle1.getCenterY() - y) > 5) {
|
||||||
|
markCircle1.setCenterX(x);
|
||||||
|
markCircle1.setCenterY(y);
|
||||||
|
connectingLine.setStartX(markCircle1.getCenterX());
|
||||||
|
connectingLine.setStartY(markCircle1.getCenterY());
|
||||||
|
}
|
||||||
|
} else if (marks.get(1).getId() == raceId) {
|
||||||
|
if (Math.abs(markCircle2.getCenterX() - x) > 5 || Math.abs(markCircle2.getCenterY() - y) > 5) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package seng302.fxObjects;
|
||||||
|
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Arc;
|
||||||
|
import javafx.scene.shape.ArcType;
|
||||||
|
import javafx.scene.shape.StrokeLineCap;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.scene.transform.Scale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group containing objects used to represent wakes onscreen. Contains functionality for their animation.
|
||||||
|
*/
|
||||||
|
public class Wake extends Group {
|
||||||
|
|
||||||
|
//The number of wakes
|
||||||
|
private int numWakes = 8;
|
||||||
|
//The total possible difference between the first wake and the last. Increasing/Decreasing this will make wakes fan out more/less.
|
||||||
|
private final double MAX_DIFF = 75;
|
||||||
|
//Increasing/decreasing this will alter the speed that wakes converge when the heading stop changing. Anything over about 1500 may cause oscillation.
|
||||||
|
private final int UNIFICATION_SPEED = 45;
|
||||||
|
|
||||||
|
|
||||||
|
private Arc[] arcs = new Arc[numWakes];
|
||||||
|
private double[] rotationalVelocities = new double[numWakes];
|
||||||
|
private double[] rotations = new double[numWakes];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wake at the given location.
|
||||||
|
*
|
||||||
|
* @param startingX x location where the tip of wake arcs will be.
|
||||||
|
* @param startingY y location where the tip of wake arcs will be.
|
||||||
|
*/
|
||||||
|
Wake(double startingX, double startingY) {
|
||||||
|
super.setLayoutX(startingX);
|
||||||
|
super.setLayoutY(startingY);
|
||||||
|
Arc arc;
|
||||||
|
for (int i = 0; i < numWakes; i++) {
|
||||||
|
//Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg.
|
||||||
|
arc = new Arc(0, 0, 0, 0, -110, 40);
|
||||||
|
arc.setCache(true);
|
||||||
|
arc.setCacheHint(CacheHint.ROTATE);
|
||||||
|
arc.setType(ArcType.OPEN);
|
||||||
|
arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i)));
|
||||||
|
arc.setStrokeWidth(3.0);
|
||||||
|
arc.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||||
|
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
|
||||||
|
arcs[i] = arc;
|
||||||
|
arc.getTransforms().setAll(
|
||||||
|
new Rotate(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
super.getChildren().addAll(arcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRotation (double rotation, double velocity) {
|
||||||
|
// if (Math.abs(rotations[0] - rotation) > 20) {
|
||||||
|
rotate(rotation);
|
||||||
|
// } else {
|
||||||
|
// rotations[0] = rotation;
|
||||||
|
// ((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation);
|
||||||
|
// for (int i = 1; i < numWakes; i++) {
|
||||||
|
// double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
|
||||||
|
// double shortestDistance = Math.atan2(
|
||||||
|
// Math.sin(wakeSeparationRad),
|
||||||
|
// Math.cos(wakeSeparationRad)
|
||||||
|
// );
|
||||||
|
// double distDeg = Math.toDegrees(shortestDistance);
|
||||||
|
// if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
|
||||||
|
// rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * 2 * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
|
||||||
|
//
|
||||||
|
// } else {
|
||||||
|
// if (distDeg < (MAX_DIFF / numWakes)) {
|
||||||
|
// rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
|
||||||
|
// } else
|
||||||
|
// rotationalVelocities[i] = rotationalVelocities[i - 1];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
double rad = (14 / numWakes) + velocity;
|
||||||
|
for (Arc arc : arcs) {
|
||||||
|
arc.setRadiusX(rad);
|
||||||
|
arc.setRadiusY(rad);
|
||||||
|
rad += (14 / numWakes) + (velocity / 2.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arcs rotate based on the distance they would have travelled over the supplied time interval.
|
||||||
|
*/
|
||||||
|
void updatePosition() {
|
||||||
|
for (int i = 0; i < numWakes; i++) {
|
||||||
|
rotations[i] = rotations[i] + rotationalVelocities[i];
|
||||||
|
((Rotate) arcs[i].getTransforms().get(0)).setAngle(rotations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate all wakes to the given rotation.
|
||||||
|
*
|
||||||
|
* @param rotation the from north angle in degrees to rotate to.
|
||||||
|
*/
|
||||||
|
void rotate(double rotation) {
|
||||||
|
for (int i = 0; i < arcs.length; i++) {
|
||||||
|
rotations[i] = rotation;
|
||||||
|
rotationalVelocities[i] = 0;
|
||||||
|
arcs[i].getTransforms().setAll(new Rotate(rotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.models.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,193 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.models.Player;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.server.messages.BoatActionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Static class to hold information about the current state of the game (model)
|
||||||
|
* Created by wmu16 on 10/07/17.
|
||||||
|
*/
|
||||||
|
public class GameState implements Runnable {
|
||||||
|
|
||||||
|
private static Integer STATE_UPDATES_PER_SECOND = 60;
|
||||||
|
|
||||||
|
private static Long previousUpdateTime;
|
||||||
|
public static Double windDirection;
|
||||||
|
private static Double windSpeed;
|
||||||
|
|
||||||
|
private static String hostIpAddress;
|
||||||
|
private static List<Player> players;
|
||||||
|
private static Map<Integer, Yacht> yachts;
|
||||||
|
private static Boolean isRaceStarted;
|
||||||
|
private static GameStages currentStage;
|
||||||
|
|
||||||
|
private static long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
||||||
|
public GameState(String hostIpAddress) {
|
||||||
|
windDirection = 180d;
|
||||||
|
windSpeed = 10000d;
|
||||||
|
this.hostIpAddress = hostIpAddress;
|
||||||
|
players = new ArrayList<>();
|
||||||
|
currentStage = GameStages.LOBBYING;
|
||||||
|
isRaceStarted = false;
|
||||||
|
yachts = new HashMap<>();
|
||||||
|
//set this when game stage changes to prerace
|
||||||
|
previousUpdateTime = System.currentTimeMillis();
|
||||||
|
yachts = new HashMap<>();
|
||||||
|
|
||||||
|
new Thread(this).start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHostIpAddress() {
|
||||||
|
return hostIpAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Player> getPlayers() {
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addPlayer(Player player) {
|
||||||
|
players.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removePlayer(Player player) {
|
||||||
|
players.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addYacht(Integer sourceId, Yacht 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) {
|
||||||
|
if (currentStage == GameStages.RACING){
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
GameState.currentStage = currentStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getStartTime(){
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double getWindSpeedMMS() {
|
||||||
|
return windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double getWindSpeedKnots() {
|
||||||
|
return windSpeed / 1000 * ClientPacketParser.MS_TO_KNOTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<Integer, Yacht> getYachts() {
|
||||||
|
return yachts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateBoat(Integer sourceId, BoatActionType actionType) {
|
||||||
|
Yacht playerYacht = yachts.get(sourceId);
|
||||||
|
// System.out.println("-----------------------");
|
||||||
|
switch (actionType) {
|
||||||
|
case VMG:
|
||||||
|
playerYacht.turnToVMG();
|
||||||
|
// System.out.println("Snapping to VMG");
|
||||||
|
break;
|
||||||
|
case SAILS_IN:
|
||||||
|
playerYacht.toggleSailIn();
|
||||||
|
// System.out.println("Toggling Sails");
|
||||||
|
break;
|
||||||
|
case SAILS_OUT:
|
||||||
|
playerYacht.toggleSailIn();
|
||||||
|
// System.out.println("Toggling Sails");
|
||||||
|
break;
|
||||||
|
case TACK_GYBE:
|
||||||
|
playerYacht.tackGybe(windDirection);
|
||||||
|
// System.out.println("Tack/Gybe");
|
||||||
|
break;
|
||||||
|
case UPWIND:
|
||||||
|
playerYacht.turnUpwind();
|
||||||
|
// System.out.println("Moving upwind");
|
||||||
|
break;
|
||||||
|
case DOWNWIND:
|
||||||
|
playerYacht.turnDownwind();
|
||||||
|
// System.out.println("Moving downwind");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBoatStatus(playerYacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update() {
|
||||||
|
Long timeInterval = System.currentTimeMillis() - previousUpdateTime;
|
||||||
|
previousUpdateTime = System.currentTimeMillis();
|
||||||
|
for (Yacht yacht : yachts.values()) {
|
||||||
|
yacht.update(timeInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(true) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("[GameState] interrupted exception");
|
||||||
|
}
|
||||||
|
if (currentStage == GameStages.PRE_RACE) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
//RACING
|
||||||
|
if (currentStage == GameStages.RACING) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printBoatStatus(Yacht playerYacht) {
|
||||||
|
System.out.println("-----------------------");
|
||||||
|
System.out.println("Sails are in: " + playerYacht.getSailIn());
|
||||||
|
System.out.println("Heading: " + playerYacht.getHeading());
|
||||||
|
System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000);
|
||||||
|
System.out.println("Lat: " + playerYacht.getLocation().getLat());
|
||||||
|
System.out.println("Lng: " + playerYacht.getLocation().getLng());
|
||||||
|
System.out.println("-----------------------\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.models.Player;
|
||||||
|
import seng302.server.messages.Heartbeat;
|
||||||
|
import seng302.server.messages.Message;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 extends Thread{
|
||||||
|
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<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,157 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Observable;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.models.Player;
|
||||||
|
import seng302.models.stream.PacketBufferDelegate;
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 extends Observable implements Runnable, ClientConnectionDelegate{
|
||||||
|
|
||||||
|
private static final int PORT = 4942;
|
||||||
|
private static final Integer CLIENT_UPDATES_PER_SECOND = 10;
|
||||||
|
private static final int LOG_LEVEL = 1;
|
||||||
|
private boolean terminated;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private ServerSocket serverSocket = null;
|
||||||
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||||
|
|
||||||
|
public MainServerThread() {
|
||||||
|
try {
|
||||||
|
serverSocket = new ServerSocket(PORT);
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
terminated = false;
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
ServerListenThread serverListenThread;
|
||||||
|
HeartbeatThread heartbeatThread;
|
||||||
|
|
||||||
|
serverListenThread = new ServerListenThread(serverSocket, this);
|
||||||
|
heartbeatThread = new HeartbeatThread(this);
|
||||||
|
|
||||||
|
heartbeatThread.start();
|
||||||
|
serverListenThread.start();
|
||||||
|
|
||||||
|
//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.PRE_RACE) {
|
||||||
|
updateClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
//RACING
|
||||||
|
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||||
|
updateClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
//FINISHED
|
||||||
|
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
|
||||||
|
try {
|
||||||
|
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.updateClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
this.addObserver(serverToClientThread);
|
||||||
|
setChanged();
|
||||||
|
notifyObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
if (serverToClientThread.getSocket() == player.getSocket()) {
|
||||||
|
this.deleteObserver(serverToClientThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setChanged();
|
||||||
|
notifyObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startGame() {
|
||||||
|
Timer t = new Timer();
|
||||||
|
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
serverToClientThread.sendRaceStatusMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
terminated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.models.Player;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.channels.ServerSocketChannel;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for a thread to listen to connections
|
||||||
|
* Created by wmu16 on 11/07/17.
|
||||||
|
*/
|
||||||
|
public class ServerListenThread extends Thread{
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private ClientConnectionDelegate delegate;
|
||||||
|
|
||||||
|
public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (true){
|
||||||
|
acceptConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
import seng302.server.messages.BoatActionType;
|
||||||
|
|
||||||
|
|
||||||
|
public class ServerPacketParser {
|
||||||
|
|
||||||
|
|
||||||
|
public static BoatActionType extractBoatAction(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||||
|
return BoatActionType.getType((int) actionTypeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
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.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.Observer;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
import seng302.models.Player;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.stream.packets.PacketType;
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
import seng302.models.xml.Race;
|
||||||
|
import seng302.models.xml.Regatta;
|
||||||
|
import seng302.models.xml.XMLGenerator;
|
||||||
|
import seng302.server.messages.BoatActionType;
|
||||||
|
import seng302.server.messages.BoatLocationMessage;
|
||||||
|
import seng302.server.messages.BoatStatus;
|
||||||
|
import seng302.server.messages.BoatSubMessage;
|
||||||
|
import seng302.server.messages.Message;
|
||||||
|
|
||||||
|
import seng302.server.messages.RaceStatus;
|
||||||
|
import seng302.server.messages.RaceStatusMessage;
|
||||||
|
import seng302.server.messages.RaceType;
|
||||||
|
import seng302.server.messages.XMLMessage;
|
||||||
|
import seng302.server.messages.XMLMessageSubType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
private static final Integer LOG_LEVEL = 1;
|
||||||
|
private static final Integer MAX_ID_ATTEMPTS = 10;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private InputStream is;
|
||||||
|
private OutputStream os;
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
private ByteArrayOutputStream crcBuffer;
|
||||||
|
|
||||||
|
private Boolean userIdentified = false;
|
||||||
|
private Boolean connected = true;
|
||||||
|
private Boolean updateClient = true;
|
||||||
|
// private Boolean initialisedRace = true;
|
||||||
|
|
||||||
|
private Integer seqNo;
|
||||||
|
private Integer sourceId;
|
||||||
|
|
||||||
|
private XMLGenerator xml;
|
||||||
|
|
||||||
|
public ServerToClientThread(Socket socket) {
|
||||||
|
this.socket = socket;
|
||||||
|
BufferedReader fn;
|
||||||
|
String fName = "";
|
||||||
|
BufferedReader ln;
|
||||||
|
String lName = "";
|
||||||
|
try {
|
||||||
|
is = socket.getInputStream();
|
||||||
|
os = socket.getOutputStream();
|
||||||
|
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()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("IO error in server thread upon grabbing streams", 1);
|
||||||
|
}
|
||||||
|
//Attempt threeway handshake with connection
|
||||||
|
sourceId = GameState.getUniquePlayerID();
|
||||||
|
if (threeWayHandshake(sourceId)) {
|
||||||
|
serverLog("Successful handshake. Client allocated id: " + sourceId, 0);
|
||||||
|
Yacht yacht = new Yacht(
|
||||||
|
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||||
|
);
|
||||||
|
// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
|
||||||
|
GameState.addYacht(sourceId, yacht);
|
||||||
|
GameState.addPlayer(new Player(socket, yacht));
|
||||||
|
} else {
|
||||||
|
serverLog("Unsuccessful handshake. Connection rejected", 1);
|
||||||
|
closeSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
seqNo = 0;
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void serverLog(String message, int logLevel) {
|
||||||
|
if (logLevel <= LOG_LEVEL) {
|
||||||
|
System.out.println(
|
||||||
|
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
sendSetupMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
int sync1;
|
||||||
|
int sync2;
|
||||||
|
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||||
|
|
||||||
|
while (socket.isConnected()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Perform a write if it is time to as delegated by the MainServerThread
|
||||||
|
if (updateClient) {
|
||||||
|
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
|
||||||
|
// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
|
||||||
|
// sendMessage(chatterMessage);
|
||||||
|
// try {
|
||||||
|
// GameState.outputState(os);
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// System.out.println("IO error in server thread upon writing to output stream");
|
||||||
|
// }
|
||||||
|
// sendBoatLocationPackets();
|
||||||
|
updateClient = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
//System.out.println("RECEIVED A PACKET");
|
||||||
|
switch (PacketType.assignPacketType(type)) {
|
||||||
|
case BOAT_ACTION:
|
||||||
|
BoatActionType actionType = ServerPacketParser
|
||||||
|
.extractBoatAction(
|
||||||
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
GameState.updateBoat(sourceId, actionType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverLog("Packet has been dropped", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected
|
||||||
|
// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
|
||||||
|
closeSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSetupMessages() {
|
||||||
|
xml = new XMLGenerator();
|
||||||
|
Race race = new Race();
|
||||||
|
|
||||||
|
for (Yacht yacht : GameState.getYachts().values()) {
|
||||||
|
race.addBoat(yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@TODO calculate lat/lng values
|
||||||
|
xml.setRegatta(new Regatta("RaceVision Test Game", 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateClient() {
|
||||||
|
sendBoatLocationPackets();
|
||||||
|
updateClient = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to confirm the connection just accepted.
|
||||||
|
* Sends ID, expects that ID echoed for confirmation,
|
||||||
|
* if so, sends a confirmation packet back to that connection
|
||||||
|
* Creates a player instance with that ID and this thread and adds it to the GameState
|
||||||
|
* If not, close the socket and end the threads execution
|
||||||
|
*
|
||||||
|
* @param id the id to try and assign to the connection
|
||||||
|
* @return A boolean indicating if it was a successful handshake
|
||||||
|
*/
|
||||||
|
private Boolean threeWayHandshake(Integer id) {
|
||||||
|
Integer confirmationID = null;
|
||||||
|
Integer identificationAttempt = 0;
|
||||||
|
while (!userIdentified) {
|
||||||
|
try {
|
||||||
|
os.write(id); //Send out new ID looking for echo
|
||||||
|
confirmationID = is.read();
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Three way handshake failed", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client
|
||||||
|
return true;
|
||||||
|
} else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
identificationAttempt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// @TODO @FIX ConnectionReset Exception when a client disconnects before it is garbage collected
|
||||||
|
currentByte = is.read();
|
||||||
|
crcBuffer.write(currentByte);
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("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) {
|
||||||
|
//serverLog("Player " + sourceId + " side socket disconnected", 1);
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Message send failed", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSeqNo() {
|
||||||
|
seqNo++;
|
||||||
|
return seqNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void sendBoatLocationPackets() {
|
||||||
|
ArrayList<Yacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
for (Yacht yacht : yachts) {
|
||||||
|
// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng());
|
||||||
|
BoatLocationMessage boatLocationMessage =
|
||||||
|
new BoatLocationMessage(
|
||||||
|
yacht.getSourceId(),
|
||||||
|
getSeqNo(),
|
||||||
|
yacht.getLocation().getLat(),
|
||||||
|
yacht.getLocation().getLng(),
|
||||||
|
yacht.getHeading(),
|
||||||
|
(long) yacht.getVelocityMMS());
|
||||||
|
|
||||||
|
sendMessage(boatLocationMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendRaceStatusMessage() {
|
||||||
|
// variables taken from GameServerThread
|
||||||
|
|
||||||
|
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||||
|
BoatStatus boatStatus;
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
|
||||||
|
for (Player player : GameState.getPlayers()) {
|
||||||
|
Yacht y = player.getYacht();
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
boatStatus = BoatStatus.PRESTART;
|
||||||
|
} else if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||||
|
boatStatus = BoatStatus.RACING;
|
||||||
|
} else {
|
||||||
|
boatStatus = BoatStatus.UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l,
|
||||||
|
1234l);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
} else {
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), GameState.getWindDirection(),
|
||||||
|
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||||
|
RaceType.MATCH_RACE, 1, boatSubMessages));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Yacht yacht;
|
||||||
|
private Integer lastMarkPassed;
|
||||||
|
|
||||||
|
|
||||||
|
public Player(Socket socket, Yacht 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 Yacht 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
|
||||||
|
* upwind and downwind in separate tables here as well
|
||||||
|
* Created by wmu16 on 22/05/17.
|
||||||
|
*/
|
||||||
|
public final class PolarTable {
|
||||||
|
|
||||||
|
//A Polar table will consist of a wind speed key to a hashmap value of pairs of wind angles and boat speeds
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> polarTable;
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
||||||
|
|
||||||
|
private static int upTwaIndex;
|
||||||
|
private static int dnTwaIndex;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* as a value
|
||||||
|
*/
|
||||||
|
public static void parsePolarFile(InputStream polarFile) {
|
||||||
|
polarTable = new HashMap<>();
|
||||||
|
upwindOptimal = new HashMap<>();
|
||||||
|
downwindOptimal = new HashMap<>();
|
||||||
|
|
||||||
|
String line;
|
||||||
|
Boolean isHeaderLine = true;
|
||||||
|
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
String[] thisLine = line.split(",");
|
||||||
|
|
||||||
|
//Initial line in file
|
||||||
|
if (isHeaderLine) {
|
||||||
|
deduceHeaders(thisLine);
|
||||||
|
isHeaderLine = false;
|
||||||
|
} else {
|
||||||
|
HashMap<Double, Double> thisPolar = new HashMap<>();
|
||||||
|
HashMap<Double, Double> thisUpWindPolar = new HashMap<>();
|
||||||
|
HashMap<Double, Double> thisDnWindPolar = new HashMap<>();
|
||||||
|
Double thisWindSpeed = Double.parseDouble(thisLine[0]);
|
||||||
|
|
||||||
|
// -3 <== -1 for length -1, and a further -2 as we iterate in pairs of 2 so finish before final 2
|
||||||
|
for (int i = 1; i < thisLine.length; i += 2) {
|
||||||
|
Double thisWindAngle = Double.parseDouble(thisLine[i]);
|
||||||
|
Double thisBoatSpeed = Double.parseDouble(thisLine[i + 1]);
|
||||||
|
thisPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
if (i == upTwaIndex) {
|
||||||
|
thisUpWindPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
} else if (i == dnTwaIndex) {
|
||||||
|
thisDnWindPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
polarTable.put(thisWindSpeed, thisPolar);
|
||||||
|
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
||||||
|
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[PolarTable] IO exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the header line of a polar file
|
||||||
|
* @param thisLine The line which is the header of a polar file
|
||||||
|
*/
|
||||||
|
private static void deduceHeaders(String[] thisLine) {
|
||||||
|
|
||||||
|
for (int i = 0; i < thisLine.length; i++) {
|
||||||
|
String thisItem = thisLine[i];
|
||||||
|
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
||||||
|
upTwaIndex = i;
|
||||||
|
}
|
||||||
|
else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||||
|
dnTwaIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The entire polar table
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getPolarTable() {
|
||||||
|
return polarTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The polar table just containing the optimal upwind values
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getUpwindOptimal() {
|
||||||
|
return upwindOptimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The polar table just containing the optimal downwind values
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getDownwindOptimal() {
|
||||||
|
return downwindOptimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will raise an exception if a polar table has just one row of data
|
||||||
|
* @param thisWindSpeed The current wind speed
|
||||||
|
* @return HashMap containing just the optimal upwind angle and resulting boat speed
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
return upwindOptimal.get(polarWindSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will raise an exception if a polar table has just one row of data
|
||||||
|
* @param thisWindSpeed The current wind speed
|
||||||
|
* @return HashMap containing just the optimal downwind angle and resulting boat speed
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
return downwindOptimal.get(polarWindSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
|
||||||
|
|
||||||
|
return polarTable.get(polarWindSpeed).get(polarAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,404 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import static seng302.utilities.GeoUtility.getGeoCoordinate;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import seng302.client.ClientPacketParser;
|
||||||
|
import seng302.controllers.RaceViewController;
|
||||||
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.utilities.GeoPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
private final Double TURN_STEP = 5.0;
|
||||||
|
|
||||||
|
private Double lastHeading;
|
||||||
|
private Boolean sailIn;
|
||||||
|
|
||||||
|
|
||||||
|
// Used in boat group
|
||||||
|
private Color colour;
|
||||||
|
|
||||||
|
private String boatType;
|
||||||
|
private Integer sourceId;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
private String boatName;
|
||||||
|
private String country;
|
||||||
|
|
||||||
|
// Situational data
|
||||||
|
|
||||||
|
|
||||||
|
// Boat status
|
||||||
|
private Integer boatStatus;
|
||||||
|
private Integer legNumber;
|
||||||
|
private Integer penaltiesAwarded;
|
||||||
|
private Integer penaltiesServed;
|
||||||
|
private Long estimateTimeAtFinish;
|
||||||
|
private String position;
|
||||||
|
private GeoPoint location;
|
||||||
|
private Double heading;
|
||||||
|
private Double velocity;
|
||||||
|
private Long timeTillNext;
|
||||||
|
private Long markRoundTime;
|
||||||
|
|
||||||
|
// Mark rounding
|
||||||
|
private Mark lastMarkRounded;
|
||||||
|
private Mark nextMark;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param location latlon location of the boat stored in a geopoint
|
||||||
|
* @param heading heading of the boat in degrees from 0 to 365 with 0 being north
|
||||||
|
*/
|
||||||
|
public Yacht(GeoPoint location, Double heading) {
|
||||||
|
this.location = location;
|
||||||
|
this.heading = heading;
|
||||||
|
this.velocity = 0.0;
|
||||||
|
this.sailIn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in EventTest and RaceTest.
|
||||||
|
*
|
||||||
|
* @param boatName Create a yacht object with name.
|
||||||
|
*/
|
||||||
|
public Yacht(String boatName, String shortName, GeoPoint location, Double heading) {
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.shortName = shortName;
|
||||||
|
this.location = location;
|
||||||
|
this.heading = heading;
|
||||||
|
this.velocity = 0.0;
|
||||||
|
this.sailIn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
this.sailIn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 = "-";
|
||||||
|
this.sailIn = false;
|
||||||
|
this.location = new GeoPoint(57.670341, 11.826856);
|
||||||
|
this.heading = 120.0; //In degrees
|
||||||
|
this.velocity = 0d; //in mms-1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timeInterval since last update in milliseconds
|
||||||
|
*/
|
||||||
|
public void update(Long timeInterval) {
|
||||||
|
|
||||||
|
Double secondsElapsed = timeInterval / 1000000.0;
|
||||||
|
Double windSpeedKnots = GameState.getWindSpeedKnots();
|
||||||
|
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
|
||||||
|
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
|
||||||
|
Double maxBoatSpeed = boatSpeedInKnots / ClientPacketParser.MS_TO_KNOTS * 1000;
|
||||||
|
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) {
|
||||||
|
|
||||||
|
if (velocity < maxBoatSpeed) {
|
||||||
|
velocity += maxBoatSpeed / 15; // Acceleration
|
||||||
|
}
|
||||||
|
if (velocity > maxBoatSpeed) {
|
||||||
|
velocity = maxBoatSpeed; // Prevent the boats from exceeding top speed
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Deceleration
|
||||||
|
|
||||||
|
if (velocity > 0d) {
|
||||||
|
if (maxBoatSpeed != 0d) {
|
||||||
|
velocity -= maxBoatSpeed / 600;
|
||||||
|
} else {
|
||||||
|
velocity -= velocity / 100;
|
||||||
|
}
|
||||||
|
if (velocity < 0) {
|
||||||
|
velocity = 0d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Double metersCovered = velocity * secondsElapsed;
|
||||||
|
location = getGeoCoordinate(location, heading, metersCovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adjustHeading(Double amount) {
|
||||||
|
Double newVal = heading + amount;
|
||||||
|
lastHeading = heading;
|
||||||
|
// TODO: 24/07/17 wmu16 - '%' in java does remainder, we need modulo. All this must be changed here, this is why we have neg values!
|
||||||
|
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tackGybe(Double windDirection) {
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
adjustHeading(-2 * normalizedHeading);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleSailIn() {
|
||||||
|
sailIn = !sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void turnUpwind() {
|
||||||
|
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() {
|
||||||
|
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 turnToVMG() {
|
||||||
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
Double optimalHeading;
|
||||||
|
HashMap<Double, Double> optimalPolarMap;
|
||||||
|
|
||||||
|
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
|
||||||
|
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
|
||||||
|
optimalHeading = optimalPolarMap.keySet().iterator().next();
|
||||||
|
} else {
|
||||||
|
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
|
||||||
|
optimalHeading = optimalPolarMap.keySet().iterator().next();
|
||||||
|
}
|
||||||
|
// Take optimal heading and turn into correct
|
||||||
|
optimalHeading =
|
||||||
|
optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L);
|
||||||
|
|
||||||
|
turnTowardsHeading(optimalHeading);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void turnTowardsHeading(Double newHeading) {
|
||||||
|
System.out.println(newHeading);
|
||||||
|
if (heading < 90 && newHeading > 270) {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
} else {
|
||||||
|
if (heading < newHeading) {
|
||||||
|
adjustHeading(TURN_STEP);
|
||||||
|
} else {
|
||||||
|
adjustHeading(-TURN_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double normalizeHeading() {
|
||||||
|
Double normalizedHeading = heading - GameState.windDirection;
|
||||||
|
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||||
|
return normalizedHeading;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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 void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) {
|
||||||
|
timeTillNext = 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 void setVelocity(double velocity) {
|
||||||
|
this.velocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setMarkRoundingTime(Long markRoundingTime) {
|
||||||
|
this.markRoundTime = markRoundingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getVelocityMMS() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getVelocityKnots() {
|
||||||
|
return velocity / 1000 * ClientPacketParser.MS_TO_KNOTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimeTillNext() {
|
||||||
|
return timeTillNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMarkRoundTime() {
|
||||||
|
return markRoundTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getLastMarkRounded() {
|
||||||
|
return lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMarkRounded(Mark lastMarkRounded) {
|
||||||
|
this.lastMarkRounded = lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextMark(Mark nextMark) {
|
||||||
|
this.nextMark = nextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getNextMark(){
|
||||||
|
return nextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSailIn() {
|
||||||
|
return sailIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeoPoint getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Boundary class represents a rectangle territorial boundary on a map. It
|
||||||
|
* contains four extremity double values(N, E, S, W). N and S are represented as
|
||||||
|
* latitudes in radians. E and W are represented as longitudes in radians.
|
||||||
|
*
|
||||||
|
* Created by Haoming on 10/5/17
|
||||||
|
*/
|
||||||
|
public class Boundary {
|
||||||
|
|
||||||
|
private double northLat, eastLng, southLat, westLng;
|
||||||
|
|
||||||
|
public Boundary(double northLat, double eastLng, double southLat, double westLng) {
|
||||||
|
this.northLat = northLat;
|
||||||
|
this.eastLng = eastLng;
|
||||||
|
this.southLat = southLat;
|
||||||
|
this.westLng = westLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getCentreLat() {
|
||||||
|
return (northLat + southLat) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getCentreLng() {
|
||||||
|
return (eastLng + westLng) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getNorthLat() {
|
||||||
|
return northLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getEastLng() {
|
||||||
|
return eastLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getSouthLat() {
|
||||||
|
return southLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getWestLng() {
|
||||||
|
return westLng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import seng302.utilities.GeoPoint;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import java.lang.Math;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CanvasMap retrieves a map image with given geo boundary from Google Map server.
|
||||||
|
* By passing a rectangle like geo boundary, it returns a map image with the
|
||||||
|
* highest resolution. However, due to free quote account usage limit, the maximum
|
||||||
|
* resolution is only 1280 * 1280.
|
||||||
|
*
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class CanvasMap {
|
||||||
|
|
||||||
|
private Boundary boundary;
|
||||||
|
private long width, height; // desired image size
|
||||||
|
private int zoom;
|
||||||
|
|
||||||
|
private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE";
|
||||||
|
|
||||||
|
public CanvasMap(Boundary boundary) {
|
||||||
|
this.boundary = boundary;
|
||||||
|
calculateOptimalMapSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image getMapImage() {
|
||||||
|
try {
|
||||||
|
URL url = new URL(getRequest());
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
return new Image(connection.getInputStream());
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("[CanvasMap] Exception");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequest() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("https://maps.googleapis.com/maps/api/staticmap?");
|
||||||
|
sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng()));
|
||||||
|
sb.append(String.format("&zoom=%d", zoom));
|
||||||
|
sb.append(String.format("&size=%dx%d&scale=2", width, height));
|
||||||
|
sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map
|
||||||
|
// sb.append(String.format("&markers=%f,%f", boundary.getSouthLat(), boundary.getWestLng()));
|
||||||
|
// sb.append(String.format("&key=%s", KEY));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateOptimalMapSize() {
|
||||||
|
for (int z = 20; z > 0; z--) {
|
||||||
|
MapSize mapSize = getMapSize(z, boundary);
|
||||||
|
zoom = z;
|
||||||
|
width = mapSize.width;
|
||||||
|
height = mapSize.height;
|
||||||
|
// if map size is valid, exit the loop as we have the highest resolution
|
||||||
|
if (mapSize.isValid()) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapSize getMapSize(int zoom, Boundary boundary) {
|
||||||
|
double scale = Math.pow(2, zoom);
|
||||||
|
GeoPoint geoSW = new GeoPoint(boundary.getSouthLat(), boundary.getWestLng());
|
||||||
|
GeoPoint geoNE = new GeoPoint(boundary.getNorthLat(), boundary.getEastLng());
|
||||||
|
Point2D pointSW = MercatorProjection.toMapPoint(geoSW);
|
||||||
|
Point2D pointNE = MercatorProjection.toMapPoint(geoNE);
|
||||||
|
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
|
||||||
|
Math.abs(pointNE.getY() - pointSW.getY()) * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapSize {
|
||||||
|
long width, height;
|
||||||
|
|
||||||
|
MapSize(double width, double height) {
|
||||||
|
this.width = Math.round(width);
|
||||||
|
this.height = Math.round(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map size is valid when width and height are both less than 640 pixels
|
||||||
|
* @return true if both dimensions are less than 640px
|
||||||
|
*/
|
||||||
|
boolean isValid() {
|
||||||
|
return Math.max(width, height) <= 640;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZoom() {
|
||||||
|
return zoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import seng302.utilities.GeoPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility class useful to convert between Geo locations and Mercator projection
|
||||||
|
* planar coordinates.
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class MercatorProjection {
|
||||||
|
|
||||||
|
private static final double MERCATOR_RANGE = 256;
|
||||||
|
private static final double pixelsPerLngDegree = MERCATOR_RANGE / 360.0;
|
||||||
|
private static final double pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A help function keeps the value in bound between -0.9999 and 0.9999.
|
||||||
|
* @param value in bound value
|
||||||
|
* @return the value in bound
|
||||||
|
*/
|
||||||
|
private static double bound(double value) {
|
||||||
|
return Math.min(Math.max(value, -0.9999), 0.9999);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projects a Geo Location (lat, lng) on a planar
|
||||||
|
* @param geo GeoPoint (lat, lng) location to be projected
|
||||||
|
* @return the projection Point2D (x, y) on planar
|
||||||
|
*/
|
||||||
|
public static Point2D toMapPoint(GeoPoint geo) {
|
||||||
|
double x, y;
|
||||||
|
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||||
|
x = (origin.getX() + geo.getLng() * pixelsPerLngDegree);
|
||||||
|
|
||||||
|
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
|
||||||
|
// 89.189. This is about a third of a tile past the edge of the world tile.
|
||||||
|
double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
|
||||||
|
y = origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian);
|
||||||
|
return new Point2D(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the planar projection (x, y) back to Geo Location (lat, lng)
|
||||||
|
* @param point Point2D (x, y) to be converted back
|
||||||
|
* @return the original Geo location converted from the given projection point
|
||||||
|
*/
|
||||||
|
public static GeoPoint toMapGeo(Point2D point) {
|
||||||
|
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||||
|
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
|
||||||
|
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
|
||||||
|
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
|
||||||
|
return new GeoPoint(lat, lng);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class TestMapController implements Initializable{
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Canvas mapCanvas;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
GraphicsContext gc = mapCanvas.getGraphicsContext2D();
|
||||||
|
Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966);
|
||||||
|
CanvasMap canvasMap = new CanvasMap(bound);
|
||||||
|
gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package seng302.models.stream;
|
||||||
|
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
|
||||||
|
public interface PacketBufferDelegate {
|
||||||
|
boolean addToBuffer(StreamPacket streamPacket);
|
||||||
|
}
|
||||||
@@ -0,0 +1,610 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package seng302.models.stream.packets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Kusal on 4/24/2017.
|
||||||
|
*/
|
||||||
|
public enum PacketType {
|
||||||
|
HEARTBEAT,
|
||||||
|
RACE_STATUS,
|
||||||
|
DISPLAY_TEXT_MESSAGE,
|
||||||
|
XML_MESSAGE,
|
||||||
|
RACE_START_STATUS,
|
||||||
|
YACHT_EVENT_CODE,
|
||||||
|
YACHT_ACTION_CODE,
|
||||||
|
CHATTER_TEXT,
|
||||||
|
BOAT_LOCATION,
|
||||||
|
MARK_ROUNDING,
|
||||||
|
COURSE_WIND,
|
||||||
|
AVG_WIND,
|
||||||
|
BOAT_ACTION,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
public static PacketType assignPacketType(int packetType){
|
||||||
|
switch(packetType){
|
||||||
|
case 1:
|
||||||
|
return HEARTBEAT;
|
||||||
|
case 12:
|
||||||
|
return RACE_STATUS;
|
||||||
|
case 20:
|
||||||
|
return DISPLAY_TEXT_MESSAGE;
|
||||||
|
case 26:
|
||||||
|
return XML_MESSAGE;
|
||||||
|
case 27:
|
||||||
|
return RACE_START_STATUS;
|
||||||
|
case 29:
|
||||||
|
return YACHT_EVENT_CODE;
|
||||||
|
case 31:
|
||||||
|
return YACHT_ACTION_CODE;
|
||||||
|
case 36:
|
||||||
|
return CHATTER_TEXT;
|
||||||
|
case 37:
|
||||||
|
return BOAT_LOCATION;
|
||||||
|
case 38:
|
||||||
|
return MARK_ROUNDING;
|
||||||
|
case 44:
|
||||||
|
return COURSE_WIND;
|
||||||
|
case 47:
|
||||||
|
return AVG_WIND;
|
||||||
|
case 100:
|
||||||
|
return BOAT_ACTION;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package seng302.models.stream.packets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 23/04/17.
|
||||||
|
*/
|
||||||
|
public class StreamPacket {
|
||||||
|
|
||||||
|
//Change int to an ENUM for the type
|
||||||
|
private PacketType type;
|
||||||
|
|
||||||
|
private long messageLength;
|
||||||
|
private long timeStamp;
|
||||||
|
private byte[] payload;
|
||||||
|
|
||||||
|
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
|
||||||
|
this.type = PacketType.assignPacketType(type);
|
||||||
|
this.messageLength = messageLength;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMessageLength() {
|
||||||
|
return messageLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.models.xml;
|
||||||
|
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Race object that can be parsed into XML
|
||||||
|
*/
|
||||||
|
public class Race {
|
||||||
|
private List<Yacht> 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(Yacht yacht){
|
||||||
|
yachts.add(yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of boats in the race
|
||||||
|
* @return A List of boats
|
||||||
|
*/
|
||||||
|
public List<Yacht> 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.models.xml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, Double latitude, Double longitude) {
|
||||||
|
this.name = name;
|
||||||
|
this.id = DEFAULT_REGATTA_ID;
|
||||||
|
this.courseName = name;
|
||||||
|
|
||||||
|
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,161 @@
|
|||||||
|
package seng302.models.xml;
|
||||||
|
|
||||||
|
import freemarker.template.Configuration;
|
||||||
|
import freemarker.template.Template;
|
||||||
|
import freemarker.template.TemplateException;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import seng302.server.messages.XMLMessageSubType;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An XML generator to generate the Race, Boat, and Regatta XML dynamically
|
||||||
|
*/
|
||||||
|
public class XMLGenerator {
|
||||||
|
private static final String XML_TEMPLATE_DIR = "/server_config/xml_templates";
|
||||||
|
private static final String REGATTA_TEMPLATE_NAME = "regatta.ftlh";
|
||||||
|
private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
|
||||||
|
private static final String RACE_TEMPLATE_NAME = "race.ftlh";
|
||||||
|
private Configuration configuration;
|
||||||
|
private Regatta regatta;
|
||||||
|
private Race race;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up a configuration instance for Apache Freemake
|
||||||
|
*/
|
||||||
|
private void setupConfiguration() {
|
||||||
|
configuration = new Configuration(Configuration.VERSION_2_3_26);
|
||||||
|
|
||||||
|
try {
|
||||||
|
configuration.setClassForTemplateLoading(getClass(), XML_TEMPLATE_DIR);
|
||||||
|
} catch (NullPointerException e){
|
||||||
|
System.out.println("[FATAL] Server could not load XML Template directory, ensure this directory isn't empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the XML Generator
|
||||||
|
*/
|
||||||
|
public XMLGenerator(){
|
||||||
|
setupConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the race regatta to send to players
|
||||||
|
* Note: This must be set before a regatta message can be generated
|
||||||
|
* @param regatta The race regatta
|
||||||
|
*/
|
||||||
|
public void setRegatta(Regatta regatta){
|
||||||
|
this.regatta = regatta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the race to send to players
|
||||||
|
* Note: This must be set before a boat or race message can be generated
|
||||||
|
* @param race The race
|
||||||
|
*/
|
||||||
|
public void setRace(Race race){
|
||||||
|
this.race = race;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an XML template and generate the output as a string
|
||||||
|
* @param templateName The templates file name
|
||||||
|
* @param type The XML message sub type
|
||||||
|
*/
|
||||||
|
private String parseToXmlString(String templateName, XMLMessageSubType type) throws IOException, TemplateException {
|
||||||
|
Template template;
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(os);
|
||||||
|
|
||||||
|
template = configuration.getTemplate(templateName);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case REGATTA:
|
||||||
|
template.process(regatta, writer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BOAT:
|
||||||
|
template.process(race, writer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RACE:
|
||||||
|
template.process(race, writer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return os.toString("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
System.out.println("[FATAL] UTF-8 Not supported");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the race regatta as a string
|
||||||
|
* Note: Regatta must be set before calling this
|
||||||
|
* @return String containing the regatta XML, null if there was an error
|
||||||
|
*/
|
||||||
|
public String getRegattaAsXml(){
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
if (regatta == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
|
||||||
|
} catch (TemplateException e) {
|
||||||
|
System.out.println("[FATAL] Error parsing regatta");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[FATAL] Error reading regatta");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the boats XML as a string
|
||||||
|
* Note: Race must be set before calling this
|
||||||
|
* @return String containing the boats XML, null if there was an error
|
||||||
|
*/
|
||||||
|
public String getBoatsAsXml() {
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
if (race == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = parseToXmlString(BOATS_TEMPLATE_NAME, XMLMessageSubType.BOAT);
|
||||||
|
} catch (TemplateException e) {
|
||||||
|
System.out.println("[FATAL] Error parsing boats");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[FATAL] Error reading boats");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the race XML as a string
|
||||||
|
* Note: Race must be set before calling this
|
||||||
|
* @return String containing the race XML, null if there was an error
|
||||||
|
*/
|
||||||
|
public String getRaceAsXml() {
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
if (race == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = parseToXmlString(RACE_TEMPLATE_NAME, XMLMessageSubType.RACE);
|
||||||
|
} catch (TemplateException e) {
|
||||||
|
System.out.println("[FATAL] Error parsing race");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[FATAL] Error reading race");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 BoatActionType actionType;
|
||||||
|
|
||||||
|
public BoatActionMessage(BoatActionType 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 12/07/17.
|
||||||
|
*/
|
||||||
|
public enum BoatActionType {
|
||||||
|
|
||||||
|
VMG(1),
|
||||||
|
SAILS_IN(2),
|
||||||
|
SAILS_OUT(3),
|
||||||
|
TACK_GYBE(4),
|
||||||
|
UPWIND(5),
|
||||||
|
DOWNWIND(6);
|
||||||
|
|
||||||
|
private final int type;
|
||||||
|
private static final Map<Integer, BoatActionType> intToTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (BoatActionType type : BoatActionType.values()) {
|
||||||
|
intToTypeMap.put(type.getValue(), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoatActionType(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BoatActionType getType(int value) {
|
||||||
|
return intToTypeMap.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class BoatLocationMessage extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 56;
|
||||||
|
|
||||||
|
private long messageVersionNumber;
|
||||||
|
private long time;
|
||||||
|
private long sourceId;
|
||||||
|
private long sequenceNum;
|
||||||
|
private DeviceType deviceType;
|
||||||
|
private double latitude;
|
||||||
|
private double longitude;
|
||||||
|
private long altitude;
|
||||||
|
private Double heading;
|
||||||
|
private long pitch;
|
||||||
|
private long roll;
|
||||||
|
private long boatSpeed;
|
||||||
|
private long COG;
|
||||||
|
private long SOG;
|
||||||
|
private long apparentWindSpeed;
|
||||||
|
private long apparentWindAngle;
|
||||||
|
private long trueWindSpeed;
|
||||||
|
private long trueWindDirection;
|
||||||
|
private long trueWindAngle;
|
||||||
|
private long currentDrift;
|
||||||
|
private long currentSet;
|
||||||
|
private long rudderAngle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the location, altitude and sensor data from the boat.
|
||||||
|
* @param sourceId ID of the boat
|
||||||
|
* @param sequenceNum Sequence number of the message
|
||||||
|
* @param latitude The boats latitude
|
||||||
|
* @param longitude The boats longitude
|
||||||
|
* @param heading The boats heading
|
||||||
|
* @param boatSpeed The boats speed
|
||||||
|
*/
|
||||||
|
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
||||||
|
messageVersionNumber = 1;
|
||||||
|
time = System.currentTimeMillis();
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.sequenceNum = sequenceNum;
|
||||||
|
this.deviceType = DeviceType.RACING_YACHT;
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
this.altitude = 0;
|
||||||
|
this.heading = heading;
|
||||||
|
this.pitch = 0;
|
||||||
|
this.roll = 0;
|
||||||
|
this.boatSpeed = boatSpeed;
|
||||||
|
this.COG = 2;
|
||||||
|
this.SOG = boatSpeed ;
|
||||||
|
this.apparentWindSpeed = 0;
|
||||||
|
this.apparentWindAngle = 0;
|
||||||
|
this.trueWindSpeed = 0;
|
||||||
|
this.trueWindDirection = 0;
|
||||||
|
this.trueWindAngle = 0;
|
||||||
|
this.currentDrift = 0;
|
||||||
|
this.currentSet = 0;
|
||||||
|
this.rudderAngle = 0;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
long headingToSend = (long)((heading/360.0) * 65535.0);
|
||||||
|
|
||||||
|
putByte((byte) messageVersionNumber);
|
||||||
|
putInt(time, 6);
|
||||||
|
putInt((int) sourceId, 4);
|
||||||
|
putUnsignedInt((int) sequenceNum, 4);
|
||||||
|
putByte((byte) deviceType.getCode());
|
||||||
|
putInt((int) latLonToBinaryPackedLong(latitude), 4);
|
||||||
|
putInt((int) latLonToBinaryPackedLong(longitude), 4);
|
||||||
|
putInt((int) altitude, 4);
|
||||||
|
putInt(headingToSend, 2);
|
||||||
|
putInt((int) pitch, 2);
|
||||||
|
putInt((int) roll, 2);
|
||||||
|
putInt((int) boatSpeed, 2);
|
||||||
|
putUnsignedInt((int) COG, 2);
|
||||||
|
putUnsignedInt((int) SOG, 2);
|
||||||
|
putUnsignedInt((int) apparentWindSpeed, 2);
|
||||||
|
putInt((int) apparentWindAngle, 2);
|
||||||
|
putUnsignedInt((int) trueWindSpeed, 2);
|
||||||
|
putUnsignedInt((int) trueWindDirection, 2);
|
||||||
|
putInt((int) trueWindAngle, 2);
|
||||||
|
putUnsignedInt((int) currentDrift, 2);
|
||||||
|
putUnsignedInt((int) currentSet, 2);
|
||||||
|
putInt((int) rudderAngle, 2);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of a boat
|
||||||
|
*/
|
||||||
|
public enum BoatStatus {
|
||||||
|
UNDEFINED(0),
|
||||||
|
PRESTART(1),
|
||||||
|
RACING(2),
|
||||||
|
FINISHED(3),
|
||||||
|
DNS(4),
|
||||||
|
DNF(5),
|
||||||
|
DSQ(6),
|
||||||
|
CS(7);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
BoatStatus(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of each boat, sent within a race status message
|
||||||
|
*/
|
||||||
|
public class BoatSubMessage{
|
||||||
|
private final int MESSAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private long sourceId;
|
||||||
|
private BoatStatus boatStatus;
|
||||||
|
private long legNumber;
|
||||||
|
private long numberPenaltiesAwarded;
|
||||||
|
private long numberPenaltiesServed;
|
||||||
|
private long estimatedTimeAtNextMark;
|
||||||
|
private long estimatedTimeAtFinish;
|
||||||
|
private ByteBuffer buff = ByteBuffer.allocate(getSize());
|
||||||
|
private int buffPos = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boat Sub message from section 4.2 of the AC35 streaming data interface spec
|
||||||
|
* @param sourceId The source ID of the boat
|
||||||
|
* @param boatStatus The boats status
|
||||||
|
* @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc)
|
||||||
|
* @param numberPenaltiesAwarded The number of penalties awarded to the boat
|
||||||
|
* @param numberPenaltiesServed The number of penalties served to the boat
|
||||||
|
* @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race
|
||||||
|
* @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark
|
||||||
|
*/
|
||||||
|
public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed,
|
||||||
|
long estimatedTimeAtFinish, long estimatedTimeAtNextMark){
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
this.legNumber = legNumber;
|
||||||
|
this.numberPenaltiesAwarded = numberPenaltiesAwarded;
|
||||||
|
this.numberPenaltiesServed = numberPenaltiesServed;
|
||||||
|
this.estimatedTimeAtFinish = estimatedTimeAtFinish;
|
||||||
|
this.estimatedTimeAtNextMark = estimatedTimeAtNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The size of this message in bytes
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putInBuffer(byte[] bytes, long val){
|
||||||
|
byte[] tmp = bytes.clone();
|
||||||
|
Message.reverse(tmp);
|
||||||
|
|
||||||
|
buff.put(tmp);
|
||||||
|
buffPos += tmp.length;
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ByteBuffer containing this boat status message
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
// Source ID, 4 bytes
|
||||||
|
putInBuffer(Message.intToByteArray(sourceId, 4), 4);
|
||||||
|
|
||||||
|
// Boat Status, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Leg number, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Number of penalties awarded, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Number of penalties served, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Estimated time at next mark, 6 bytes
|
||||||
|
putInBuffer(Message.intToByteArray((int) estimatedTimeAtNextMark, 6),6);
|
||||||
|
|
||||||
|
// Estimated time at finish, 6 bytes
|
||||||
|
putInBuffer(Message.intToByteArray((int) estimatedTimeAtFinish, 6), 6);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package seng302.server.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,16 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
public enum DeviceType {
|
||||||
|
UNKNOWN(0),
|
||||||
|
RACING_YACHT(1);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
DeviceType(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class Header {
|
||||||
|
// From API spec
|
||||||
|
private final int syncByte1 = 0x47;
|
||||||
|
private final int syncByte2 = 0x83;
|
||||||
|
|
||||||
|
private MessageType messageType;
|
||||||
|
private int timeStamp;
|
||||||
|
private int sourceId;
|
||||||
|
private short messageLength;
|
||||||
|
private static final int MESSAGE_LEN = 15;
|
||||||
|
private ByteBuffer buff;
|
||||||
|
private int buffPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message Header from section 3.2 of the AC35 Streaming
|
||||||
|
* Data spec
|
||||||
|
* @param messageType The type of the message following this header
|
||||||
|
* @param sourceId The message source (as defined in the spec)
|
||||||
|
* @param messageLength The length of the message following this header
|
||||||
|
*/
|
||||||
|
public Header(MessageType messageType, int sourceId, Short messageLength){
|
||||||
|
this.messageType = messageType;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.messageLength = messageLength;
|
||||||
|
timeStamp = (int) (System.currentTimeMillis() / 1000L);
|
||||||
|
buff = ByteBuffer.allocate(MESSAGE_LEN);
|
||||||
|
buffPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putInBuffer(byte[] bytes, long val){
|
||||||
|
byte[] tmp = bytes.clone();
|
||||||
|
Message.reverse(tmp);
|
||||||
|
|
||||||
|
buff.put(tmp);
|
||||||
|
buffPos += tmp.length;
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the buffer
|
||||||
|
*/
|
||||||
|
public void reset(){
|
||||||
|
buffPos = 0;
|
||||||
|
buff.clear();
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ByteBuffer containing the message header
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
reset();
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array(), messageType.getCode());
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(timeStamp, 6), timeStamp);
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(sourceId, 4), sourceId);
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(messageLength, 2), messageLength);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of this message
|
||||||
|
* @return the size of the message
|
||||||
|
*/
|
||||||
|
public static Integer getSize(){
|
||||||
|
return MESSAGE_LEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class Heartbeat extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat from the AC35 Streaming data spec
|
||||||
|
* @param seqNo Increment every time a message is sent
|
||||||
|
*/
|
||||||
|
public Heartbeat(int seqNo){
|
||||||
|
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
|
||||||
|
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putUnsignedInt(seqNo, 4);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class MarkRoundingMessage extends Message{
|
||||||
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
|
private final int MESSAGE_SIZE = 21;
|
||||||
|
|
||||||
|
private long time;
|
||||||
|
private long ackNumber;
|
||||||
|
private long raceId;
|
||||||
|
private long sourceId;
|
||||||
|
private RoundingBoatStatus boatStatus;
|
||||||
|
private RoundingSide roundingSide;
|
||||||
|
private long markId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message is sent when a boat passes a mark, start line, or finish line
|
||||||
|
* The purpose of this is to record the time when yachts cross marks
|
||||||
|
*/
|
||||||
|
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||||
|
RoundingSide roundingSide, int markId){
|
||||||
|
this.time = System.currentTimeMillis() / 1000L;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.boatStatus = roundingBoatStatus;
|
||||||
|
this.roundingSide = roundingSide;
|
||||||
|
this.markId = markId;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||||
|
putInt((int) time, 6);
|
||||||
|
putInt((int) ackNumber, 2);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putInt((int) sourceId, 4);
|
||||||
|
putByte((byte) boatStatus.getCode());
|
||||||
|
putByte((byte) roundingSide.getCode());
|
||||||
|
putByte((byte) markId);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of marks boats can round
|
||||||
|
*/
|
||||||
|
public enum MarkType {
|
||||||
|
UNKNOWN(0),
|
||||||
|
ROUNDING_MARK(1),
|
||||||
|
GATE(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
MarkType(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public abstract class Message {
|
||||||
|
private final int CRC_SIZE = 4;
|
||||||
|
private Header header;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
private int bufferPosition;
|
||||||
|
private CRC32 crc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param header Set the header for this message
|
||||||
|
*/
|
||||||
|
void setHeader(Header header){
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the header specified for this message
|
||||||
|
*/
|
||||||
|
Header getHeader(){
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of the message
|
||||||
|
*/
|
||||||
|
public abstract int getSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate byte buffer to correct size
|
||||||
|
*/
|
||||||
|
void allocateBuffer(){
|
||||||
|
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
bufferPosition = 0;
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the set header to the byte buffer
|
||||||
|
*/
|
||||||
|
void writeHeaderToBuffer(){
|
||||||
|
buffer.put(getHeader().getByteBuffer().array());
|
||||||
|
bufferPosition += Header.getSize();
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the buffer position by n bytes
|
||||||
|
* @param size Number of bytes to move the buffer by
|
||||||
|
*/
|
||||||
|
private void moveBufferPositionBy(int size){
|
||||||
|
bufferPosition += size;
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put an unsigned byte in the buffer
|
||||||
|
*/
|
||||||
|
void putUnsignedByte(byte b){
|
||||||
|
buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array());
|
||||||
|
moveBufferPositionBy(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put an signed byte in the buffer
|
||||||
|
*/
|
||||||
|
void putByte(byte b){
|
||||||
|
buffer.put(ByteBuffer.allocate(1).put(b).array());
|
||||||
|
moveBufferPositionBy(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Place an unsigned integer of the specified length in the buffer
|
||||||
|
* @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers)
|
||||||
|
* @param size The size of the int to be added to the buffer
|
||||||
|
*/
|
||||||
|
void putUnsignedInt(long val, int size){
|
||||||
|
if (size <= 1){
|
||||||
|
putUnsignedByte((byte) val);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (size < 4){
|
||||||
|
// Use short
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array();
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Use int
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a signed int of a specified length in the buffer
|
||||||
|
* @param val The integer value to add
|
||||||
|
* @param size The size of the integer to be added to the buffer
|
||||||
|
*/
|
||||||
|
void putInt(long val, int size){
|
||||||
|
if (size < 4){
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
}
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write an array of bytes to the buffer
|
||||||
|
* @param bytes to write
|
||||||
|
*/
|
||||||
|
void putBytes(byte[] bytes){
|
||||||
|
buffer.put(bytes);
|
||||||
|
moveBufferPositionBy(bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a ByteBuffer of bytes to the buffer
|
||||||
|
* @param bytes to write
|
||||||
|
* @param size number of bytes
|
||||||
|
*/
|
||||||
|
void putBytes(ByteBuffer bytes, int size){
|
||||||
|
buffer.put(bytes.array());
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the CRC of the buffer and append it to the end of the buffer
|
||||||
|
*/
|
||||||
|
void writeCRC(){
|
||||||
|
crc = new CRC32();
|
||||||
|
|
||||||
|
buffer.position(0);
|
||||||
|
|
||||||
|
byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE);
|
||||||
|
crc.update(data);
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
|
||||||
|
putInt((int) crc.getValue(), CRC_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current buffer as a byte array
|
||||||
|
*/
|
||||||
|
public byte[] getBuffer(){
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind the buffer to the beginning
|
||||||
|
*/
|
||||||
|
void rewind(){
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an integer to an array of bytes
|
||||||
|
* @param val The value to add
|
||||||
|
* @param len The width of the integer in the buffer
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] intToByteArray(long val, int len){
|
||||||
|
int index = 0;
|
||||||
|
byte[] data = new byte[len];
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++){
|
||||||
|
data[len - index - 1] = (byte) (val & 0xFF);
|
||||||
|
val >>>= 8;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* @param data The byte[] to reverse
|
||||||
|
*/
|
||||||
|
public static void reverse(byte[] data) {
|
||||||
|
for (int left = 0, right = data.length - 1; left < right; left++, right--) {
|
||||||
|
byte temp = (byte) (data[left] & 0xff);
|
||||||
|
data[left] = (byte) (data[right] & 0xff);
|
||||||
|
data[right] = (byte) (temp & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of messages
|
||||||
|
* sent by the server
|
||||||
|
*/
|
||||||
|
public enum MessageType {
|
||||||
|
HEARTBEAT(1),
|
||||||
|
RACE_STATUS(12),
|
||||||
|
DISPLAY_TEXT_MESSAGE(20),
|
||||||
|
XML_MESSAGE(26),
|
||||||
|
RACE_START_STATUS(27),
|
||||||
|
YACHT_EVENT_CODE(29),
|
||||||
|
YACHT_ACTION_CODE(31),
|
||||||
|
CHATTER_TEXT(36),
|
||||||
|
BOAT_LOCATION(37),
|
||||||
|
MARK_ROUNDING(38),
|
||||||
|
COURSE_WIND(44),
|
||||||
|
AVERAGE_WIND(47),
|
||||||
|
BOAT_ACTION(100);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
MessageType(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message code (From the API Spec)
|
||||||
|
* @return the message code
|
||||||
|
*/
|
||||||
|
int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The types of race start status messages
|
||||||
|
*/
|
||||||
|
public enum RaceStartNotificationType {
|
||||||
|
SET_RACE_START_TIME(1),
|
||||||
|
RACE_POSTPONED(2),
|
||||||
|
RACE_ABANDONED(3),
|
||||||
|
RACE_TERMINATED(4);
|
||||||
|
|
||||||
|
private final long type;
|
||||||
|
|
||||||
|
RaceStartNotificationType(long type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getType(){
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class RaceStartStatusMessage extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private long version;
|
||||||
|
private long timeStamp;
|
||||||
|
private long ackNumber;
|
||||||
|
private long raceStartTime;
|
||||||
|
private long raceId;
|
||||||
|
private RaceStartNotificationType notificationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent to clients with the expected start time of the race
|
||||||
|
* @param ackNumber Sequence number of message.
|
||||||
|
* @param raceStartTime Expected race start time
|
||||||
|
* @param raceId Race ID#
|
||||||
|
* @param notificationType Type of this notification
|
||||||
|
*/
|
||||||
|
public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){
|
||||||
|
this.version = 1;
|
||||||
|
this.timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.raceStartTime = raceStartTime;
|
||||||
|
this.notificationType = notificationType;
|
||||||
|
this.raceId = raceId;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putUnsignedByte((byte) version);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putInt((int) ackNumber, 2);
|
||||||
|
putInt((int) raceStartTime, 6);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putUnsignedByte((byte) notificationType.getType());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of the race
|
||||||
|
*/
|
||||||
|
public enum RaceStatus {
|
||||||
|
NOTACTIVE(0),
|
||||||
|
WARNING(1), // Between 3:00 and 1:00 before start
|
||||||
|
PREPARATORY(2), // Less than 1:00 before start
|
||||||
|
STARTED(3),
|
||||||
|
ABANDONED(6),
|
||||||
|
POSTPONED(7),
|
||||||
|
TERMINATED(8),
|
||||||
|
RACE_START_TIME_NOT_SET(9),
|
||||||
|
PRESTART(10); // More than 3:00 before start
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
RaceStatus(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class RaceStatusMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
||||||
|
private final int MESSAGE_VERSION = 2; //Always set to 1
|
||||||
|
private final int MESSAGE_BASE_SIZE = 24;
|
||||||
|
private final double windDirFactor = 0x4000 / 90;
|
||||||
|
|
||||||
|
|
||||||
|
private long currentTime;
|
||||||
|
private long raceId;
|
||||||
|
private RaceStatus raceStatus;
|
||||||
|
private long expectedStartTime;
|
||||||
|
private double raceWindDirection;
|
||||||
|
private long windSpeed;
|
||||||
|
private long numBoatsInRace;
|
||||||
|
private RaceType raceType;
|
||||||
|
private List<BoatSubMessage> boats;
|
||||||
|
private CRC32 crc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message containing the current status of the race
|
||||||
|
* @param raceId The ID of the current race
|
||||||
|
* @param raceStatus The status of the race
|
||||||
|
* @param expectedStartTime The expected start time
|
||||||
|
* @param raceWindDirection The wind direction (north, east, south)
|
||||||
|
* @param windSpeed The wind speed in mm/sec
|
||||||
|
* @param numBoatsInRace The number of boats in the race
|
||||||
|
* @param raceType The race type (Match/fleet)
|
||||||
|
* @param sourceId The source of this message
|
||||||
|
* @param boats A list of boat status sub messages
|
||||||
|
*/
|
||||||
|
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, double raceWindDirection,
|
||||||
|
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.raceStatus = raceStatus;
|
||||||
|
this.expectedStartTime = expectedStartTime;
|
||||||
|
this.raceWindDirection = raceWindDirection * windDirFactor;
|
||||||
|
this.windSpeed = windSpeed;
|
||||||
|
this.numBoatsInRace = numBoatsInRace;
|
||||||
|
this.raceType = raceType;
|
||||||
|
this.boats = boats;
|
||||||
|
crc = new CRC32();
|
||||||
|
|
||||||
|
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt(currentTime, 6);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putByte((byte) raceStatus.getCode());
|
||||||
|
putInt(expectedStartTime, 6);
|
||||||
|
putInt((int) this.raceWindDirection, 2);
|
||||||
|
putInt((int) windSpeed, 2);
|
||||||
|
putByte((byte) numBoatsInRace);
|
||||||
|
putByte((byte) raceType.getCode());
|
||||||
|
|
||||||
|
for (BoatSubMessage boatSubMessage : boats){
|
||||||
|
putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of this message in bytes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of races
|
||||||
|
* sent by the server
|
||||||
|
*/
|
||||||
|
public enum RaceType {
|
||||||
|
MATCH_RACE(1),
|
||||||
|
FLEET_RACE(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RaceType(long code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of a boat rounding a mark
|
||||||
|
*/
|
||||||
|
public enum RoundingBoatStatus {
|
||||||
|
UNKNOWN(0),
|
||||||
|
RACING(1),
|
||||||
|
DSQ(2),
|
||||||
|
WITHDRAWN(3);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RoundingBoatStatus(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The side the boat rounded the mark
|
||||||
|
*/
|
||||||
|
public enum RoundingSide {
|
||||||
|
UNKNOWN(0),
|
||||||
|
PORT(1),
|
||||||
|
STARBOARD(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RoundingSide(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class XMLMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
||||||
|
private final int MESSAGE_VERSION = 1; //Always set to 1
|
||||||
|
private final int MESSAGE_SIZE = 14;
|
||||||
|
|
||||||
|
// Message fields
|
||||||
|
private long timeStamp;
|
||||||
|
private long ack = 0x00; //Unused
|
||||||
|
private XMLMessageSubType xmlMessageSubType;
|
||||||
|
private long length;
|
||||||
|
private long sequence;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML Message from the AC35 Streaming data spec
|
||||||
|
* @param content The XML content
|
||||||
|
* @param type The XML Message Sub Type
|
||||||
|
*/
|
||||||
|
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
||||||
|
this.content = content;
|
||||||
|
this.xmlMessageSubType = type;
|
||||||
|
timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
ack = 0;
|
||||||
|
length = this.content.length();
|
||||||
|
sequence = sequenceNum;
|
||||||
|
|
||||||
|
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
// Write message fields
|
||||||
|
putUnsignedByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt((int) ack, 2);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putByte((byte)xmlMessageSubType.getType());
|
||||||
|
putInt((int) sequence, 2);
|
||||||
|
putInt((int) length, 2);
|
||||||
|
putBytes(content.getBytes());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The length of this message
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE + content.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of XML messages
|
||||||
|
*/
|
||||||
|
public enum XMLMessageSubType {
|
||||||
|
REGATTA(5),
|
||||||
|
RACE(6),
|
||||||
|
BOAT(7);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
XMLMessageSubType(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType(){
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.utilities.GeoPoint;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
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
|
||||||
|
GeoPoint originPos = new GeoPoint(lat, lng);
|
||||||
|
GeoPoint 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Mark;
|
||||||
|
import seng302.server.simulator.parsers.RaceParser;
|
||||||
|
import seng302.utilities.GeoPoint;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
System.out.println("[Simulator] interrupted exception ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng());
|
||||||
|
GeoPoint 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
|
||||||
|
GeoPoint pos = GeoUtility.getGeoCoordinate(
|
||||||
|
boat.getLastPassedCorner().getCompoundMark().getMark1(),
|
||||||
|
boat.getLastPassedCorner().getBearingToNextCorner(),
|
||||||
|
compensateDistance);
|
||||||
|
boat.setLat(pos.getLat());
|
||||||
|
boat.setLng(pos.getLng());
|
||||||
|
distanceFromLastMark = GeoUtility.getDistance(new GeoPoint(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
import seng302.utilities.GeoPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to represent general marks
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public class Mark extends GeoPoint {
|
||||||
|
|
||||||
|
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, getLat(), getLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class BoatsParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
|
||||||
|
public BoatsParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public abstract class FileParser {
|
||||||
|
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
|
public FileParser() {}
|
||||||
|
|
||||||
|
public FileParser(String path) {
|
||||||
|
this.filePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Document parseFile() {
|
||||||
|
try {
|
||||||
|
InputStream is = getClass().getResourceAsStream(this.filePath);
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(is);
|
||||||
|
// optional, in order to recover info from broken line.
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
return doc;
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("[FileParser] Exception");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Document parseFile(String xmlString) {
|
||||||
|
try {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
|
||||||
|
// optional, in order to recover info from broken line.
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
return doc;
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("[FileParser] Exception");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import seng302.server.simulator.Boat;
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class RaceParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
public RaceParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.path = path;
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses race.xml file and returns a list of corner which is the race course.
|
||||||
|
* @return a list of ordered corner to represent the course.
|
||||||
|
*/
|
||||||
|
public List<Corner> getCourse() {
|
||||||
|
CourseParser cp = new CourseParser(path);
|
||||||
|
return cp.getCourse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses race.xml file and return a list of boats which will compete in the
|
||||||
|
* race.
|
||||||
|
* @return a list of boats that are going to compete in the race.
|
||||||
|
*/
|
||||||
|
public List<Boat> getBoats() {
|
||||||
|
NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht");
|
||||||
|
List<Boat> boats = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < yachts.getLength(); i++) {
|
||||||
|
boats.add(getBoat(yachts.item(i)));
|
||||||
|
}
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a single boat from the given node
|
||||||
|
* @param node a node within a boat tag
|
||||||
|
* @return a boat instance parsed from the given node
|
||||||
|
*/
|
||||||
|
private Boat getBoat(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
|
||||||
|
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
|
||||||
|
return new Boat(sourceId, "Test Boat");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.utilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class represent Geo location (latitude, longitude).
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class GeoPoint {
|
||||||
|
|
||||||
|
double lat, lng;
|
||||||
|
|
||||||
|
public GeoPoint(double lat, double lng) {
|
||||||
|
this.lat = lat;
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package seng302.utilities;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
|
||||||
|
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(GeoPoint p1, GeoPoint 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(GeoPoint p1, GeoPoint 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 GeoPoint getGeoCoordinate(GeoPoint 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 GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7
|
||||||
|
4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4
|
||||||
|
8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10
|
||||||
|
12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14
|
||||||
|
16,0,0,30,12,42,19.2,60,25,75,27,90,31,115,32,145,30,153,28.8,175,20
|
||||||
|
20,0,0,30,13,41,24,60,29,75,37,90,39,115,40,145,38,153,36,175,24
|
||||||
|
25,0,0,30,15,40,30,60,38,75,44,90,49,115,50,145,49,151,47,175,30
|
||||||
|
30,0,0,30,15,42,30,60,37,75,42,90,48,115,49,145,48,150,46,175,32
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<configurations>
|
||||||
|
<race-name>AC35</race-name>
|
||||||
|
<race-size>6</race-size>
|
||||||
|
<time-scale>10.0</time-scale>
|
||||||
|
<wind-direction>135</wind-direction>
|
||||||
|
</configurations>
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<markers>
|
||||||
|
<marks>
|
||||||
|
<gate>
|
||||||
|
<name type="start-line">Start</name>
|
||||||
|
<mark>
|
||||||
|
<name>Start1</name>
|
||||||
|
<latitude>57.6703330</latitude>
|
||||||
|
<longitude>11.8278330</longitude>
|
||||||
|
<id>122</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Start2</name>
|
||||||
|
<latitude>57.6703330</latitude>
|
||||||
|
<longitude>11.8271333</longitude>
|
||||||
|
<id>123</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<mark>
|
||||||
|
<name>Mid Mark</name>
|
||||||
|
<latitude>57.6675700</latitude>
|
||||||
|
<longitude>11.8359880</longitude>
|
||||||
|
<id>131</id>
|
||||||
|
</mark>
|
||||||
|
<gate>
|
||||||
|
<name>Leeward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate1</name>
|
||||||
|
<latitude>57.6708220</latitude>
|
||||||
|
<longitude>11.8433900</longitude>
|
||||||
|
<id>124</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate2</name>
|
||||||
|
<latitude>57.6711220</latitude>
|
||||||
|
<longitude>11.8436900</longitude>
|
||||||
|
<id>125</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate>
|
||||||
|
<name>Windward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate1</name>
|
||||||
|
<latitude>57.6650170</latitude>
|
||||||
|
<longitude>11.8279170</longitude>
|
||||||
|
<id>126</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate2</name>
|
||||||
|
<latitude>57.6653170</latitude>
|
||||||
|
<longitude>11.8282170</longitude>
|
||||||
|
<id>127</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate type="finish-line">
|
||||||
|
<name>Finish</name>
|
||||||
|
<mark>
|
||||||
|
<name>Finish1</name>
|
||||||
|
<latitude>57.6715240</latitude>
|
||||||
|
<longitude>11.8444950</longitude>
|
||||||
|
<id>128</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Finish2</name>
|
||||||
|
<latitude>57.6718240</latitude>
|
||||||
|
<longitude>11.8447950</longitude>
|
||||||
|
<id>129</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
</marks>
|
||||||
|
<order>
|
||||||
|
<one>Start</one>
|
||||||
|
<two>Mid Mark</two>
|
||||||
|
<three>Leeward Gate</three>
|
||||||
|
<four>Windward Gate</four>
|
||||||
|
<five>Leeward Gate</five>
|
||||||
|
<six>Finish</six>
|
||||||
|
</order>
|
||||||
|
</markers>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<course>
|
||||||
|
<marks>
|
||||||
|
<gate>
|
||||||
|
<name type="start-line">Start</name>
|
||||||
|
<mark>
|
||||||
|
<name>Start1</name>
|
||||||
|
<latitude>32.296577</latitude>
|
||||||
|
<longitude>-64.854304</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Start2</name>
|
||||||
|
<latitude>32.293771</latitude>
|
||||||
|
<longitude>-64.855242</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<mark>
|
||||||
|
<name>Mid Mark</name>
|
||||||
|
<latitude>32.293039</latitude>
|
||||||
|
<longitude>-64.843983</longitude>
|
||||||
|
</mark>
|
||||||
|
<gate>
|
||||||
|
<name>Leeward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate1</name>
|
||||||
|
<latitude>32.284680</latitude>
|
||||||
|
<longitude>-64.850045</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate2</name>
|
||||||
|
<latitude>32.280164</latitude>
|
||||||
|
<longitude>-64.847591</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate>
|
||||||
|
<name>Windward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate1</name>
|
||||||
|
<latitude>32.309693</latitude>
|
||||||
|
<longitude>-64.835249</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate2</name>
|
||||||
|
<latitude>32.308046</latitude>
|
||||||
|
<longitude>-64.831785</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate type="finish-line">
|
||||||
|
<name>Finish</name>
|
||||||
|
<mark>
|
||||||
|
<name>Finish1</name>
|
||||||
|
<latitude>32.317379</latitude>
|
||||||
|
<longitude>-64.839291</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Finish2</name>
|
||||||
|
<latitude>32.317257</latitude>
|
||||||
|
<longitude>-64.836260</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
</marks>
|
||||||
|
<order>
|
||||||
|
<one>Start</one>
|
||||||
|
<two>Mid Mark</two>
|
||||||
|
<three>Leeward Gate</three>
|
||||||
|
<four>Windward Gate</four>
|
||||||
|
<five>Leeward Gate</five>
|
||||||
|
<six>Finish</six>
|
||||||
|
</order>
|
||||||
|
</course>
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<teams>
|
||||||
|
<team>
|
||||||
|
<name>Oracle Team USA</name>
|
||||||
|
<alias>USA</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>102</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Artemis Racing</name>
|
||||||
|
<alias>ART</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>101</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Emirates Team New Zealand</name>
|
||||||
|
<alias>NZL</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>103</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Land Rover BAR</name>
|
||||||
|
<alias>BAR</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>104</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>SoftBank Team Japan</name>
|
||||||
|
<alias>JAP</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>105</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Groupama Team France</name>
|
||||||
|
<alias>FRC</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>106</id>
|
||||||
|
</team>
|
||||||
|
</teams>
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
Background colours
|
||||||
|
*/
|
||||||
|
.background-blue{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-dark{
|
||||||
|
-fx-background-color: #2C2c36;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Exit button with no background
|
||||||
|
*/
|
||||||
|
.clear-exit-btn{
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Buttons
|
||||||
|
*/
|
||||||
|
.blue-ui-btn{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-text-fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-white {
|
||||||
|
-fx-text-fill: white !important;
|
||||||
|
-fx-fill:white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sliders
|
||||||
|
*/
|
||||||
|
.ui-slider .thumb {
|
||||||
|
-fx-background-color: rgb(60, 60, 60);
|
||||||
|
-fx-border-radius: 10;
|
||||||
|
-fx-border-color: darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider .track{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider .axis{
|
||||||
|
-fx-tick-label-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider .axis .axis-label{
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checkbox
|
||||||
|
*/
|
||||||
|
.ui-checkbox .box{
|
||||||
|
-fx-background-color: white;
|
||||||
|
-fx-graphic:none;
|
||||||
|
-fx-shape: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-checkbox .box .mark{
|
||||||
|
-fx-background-image: none;
|
||||||
|
-fx-image: none;
|
||||||
|
-fx-graphic: none;
|
||||||
|
-fx-shape: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-checkbox:selected .box{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-shape: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-checkbox:selected .box .mark{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-shape: none;
|
||||||
|
-fx-graphic: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Table
|
||||||
|
*/
|
||||||
|
.ui-table{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table:focused{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .column-header-background{
|
||||||
|
-fx-background-color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .column-header-background .label{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .column-header {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .table-cell{
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell:odd{
|
||||||
|
-fx-background-color: #0e6d6c;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell:selected {
|
||||||
|
-fx-background-color: #005797;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Combo Box
|
||||||
|
*/
|
||||||
|
|
||||||
|
.combo-box-base {
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combo-box-popup .list-view .list-cell:hover {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combo-box .cell:selected {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Remove scroll bars
|
||||||
|
*/
|
||||||
|
.ui-table *.scroll-bar:horizontal *.increment-button,
|
||||||
|
.ui-table *.scroll-bar:horizontal *.decrement-button {
|
||||||
|
-fx-background-color: null;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table *.scroll-bar:horizontal *.increment-arrow,
|
||||||
|
.ui-table *.scroll-bar:horizontal *.decrement-arrow {
|
||||||
|
-fx-background-color: null;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
-fx-shape: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table *.scroll-bar:vertical *.increment-arrow,
|
||||||
|
.ui-table *.scroll-bar:vertical *.decrement-arrow {
|
||||||
|
-fx-background-color: #0e6d6c;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
-fx-shape: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table *.scroll-bar:vertical *.increment-button,
|
||||||
|
.ui-table *.scroll-bar:vertical *.decrement-button {
|
||||||
|
-fx-background-color: #0e6d6c;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart{
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
-fx-text-fill: #ffffff;
|
||||||
|
-fx-font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis-label {
|
||||||
|
-fx-text-fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis {
|
||||||
|
-fx-tick-label-fill: #ffffff;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user