Compare commits

...

860 Commits

Author SHA1 Message Date
William Muir 2fcff65dd6 Fixed null pointer when cant find a server
tags: #fix
2017-08-17 15:44:05 +12:00
William Muir 4c730ea890 Fixed null pointer when cant find a server
tags: #fix
2017-08-17 15:40:55 +12:00
Alistair McIntyre a501b21d66 Merge branch 'develop' into 'master'
Sprint 6.0 Merge

Merge for sprint 6.0 tag to master.

See merge request !67
2017-08-17 14:59:01 +12:00
Alistair McIntyre 5fb8a0c2c1 Merge branch 'issue47_disconnect_crash_rebranch' into 'develop'
Issue47 disconnect crash rebranch

Fix for all disconnection issues. Fix is not robust. Need consistent interface between disconnection of ServerToClientThreads and MainServerThread.

See merge request !66
2017-08-17 14:55:45 +12:00
Alistair McIntyre d867a4b7a2 Fixed bug, should fix disconnection issues.
tags : #issue[47]
2017-08-17 14:53:54 +12:00
Michael Rausch 3c91db59f3 Changed wind change variation 2017-08-17 14:49:39 +12:00
Alistair McIntyre 72a390b484 Fixed build bug.
tags : #issue[47]
2017-08-17 14:47:20 +12:00
Alistair McIntyre 978def4cf7 Merge remote-tracking branch 'origin/develop' into issue47_disconnect_crash_rebranch
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-08-17 14:36:16 +12:00
Alistair McIntyre 2331942bf9 Merge branch 'Story1124_Sparkline' into 'develop'
Fixing Ordering

# Changes 
* Fixed ordering which was broken from a previous commit 
* Removed Sparkline as we could not get it working in time 

# Testing 
* Manual testlog only 

See merge request !65
2017-08-17 14:25:32 +12:00
Calum d41bdfebbc Merged Boat customization onto develop.
#bug #merge #refactor
2017-08-17 14:21:06 +12:00
Calum d0565503e8 Merge branch 'develop' into issue47_disconnect_crash_rebranch
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-17 14:15:27 +12:00
Haoming Yin f0c48e76e1 Merge branch 'Story80_BoatCustomization' into 'develop'
Story 80: Boat Customization

# Implementation
- Added Messages for Boat Customization Requests and Responses.
- Added functionality to handle Name and Boat Color Customization. (Shapes would be a next sprint thing)

# Testing
- Manually tested personally, will test correctly when boat shapes have also been implemented.


See merge request !64
2017-08-17 14:11:04 +12:00
Calum 3639e0d3ce Fixed disconnection issues. Fix is only temporary until a consistent interface for disconnections exists.
#bug #implement
2017-08-17 14:10:38 +12:00
Calum 0276911b88 Fixed disconnection issues. Fix is only temporary until a consistent interface for disconnections exists.
#bug #implement
2017-08-17 14:02:18 +12:00
Alistair McIntyre af3a400841 Fixed build bug.
tags : #story[1142]
2017-08-17 13:44:34 +12:00
Zhi You Tan 5a2a78a7d5 Bug fixes for final commit 2017-08-17 13:43:31 +12:00
Alistair McIntyre 5fc7442359 Merged Develop into branch.
tags : #story[1142]
2017-08-17 13:40:00 +12:00
Alistair McIntyre 39cfaf6780 Merge remote-tracking branch 'origin/develop' into Story80_BoatCustomization
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerPacketParser.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/gameServer/messages/ChatterMessage.java
#	src/main/java/seng302/gameServer/messages/MarkRoundingMessage.java
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
2017-08-17 13:31:27 +12:00
Zhi You Tan 7a11b5eb77 Merge branch 'develop' into Story1124_Sparkline 2017-08-17 13:14:08 +12:00
Alistair McIntyre faf4600f51 Fixed some weird bug with the messages package, trying to add some small QoL changes with potential interaction bugs with the customization/lobby menu.
tags : #story[1142]
2017-08-17 13:13:07 +12:00
Zhi You Tan 3e383465a9 Attempted to fix sparklines, temporarily disabled them for end of sprint. 2017-08-17 13:12:09 +12:00
Alistair McIntyre ac279583df Small documentation changes.
tags: #story[1142]
2017-08-17 12:31:15 +12:00
Calum ef2659a7b9 Merge branch 'develop' into issue47_disconnect_crash_rebranch
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
#	src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java
2017-08-17 12:16:14 +12:00
Calum 769d1956b3 Moved client side disconnection handling to this branch and reimplemented it with develop functionality.
#refactor #issue[47]
2017-08-17 12:11:36 +12:00
Haoming Yin 8ca455ed24 Fixed:
-  Fixed regularPacketsTest unit test, which checks how regularly packets are sent from C2S thread.
- Removed misused GameClient in main server thread.

tags: #story[1124]
2017-08-17 12:03:43 +12:00
Haoming Yin 7bda2bc580 Fixed:
-  Fixed regularPacketsTest unit test, which checks how regularly packets are sent from C2S thread.
- Removed misused GameClient in main server thread.

tags: #story[1124]
2017-08-17 11:46:14 +12:00
Haoming Yin d7a290478d Merge remote-tracking branch 'origin/develop' into develop 2017-08-17 11:02:54 +12:00
alistairjmcintyre d73e4f8ec5 Fixed a build error.
tags: #story[1142]
2017-08-17 01:23:34 +12:00
alistairjmcintyre 6e02d3e533 Added some general UI improvements such as autofilling the color field of the form, and looked at how best to deal with response packets, as it should be part of the spec.
tags: #story[1142]
2017-08-17 01:20:16 +12:00
Calum 7b4a70817b Merged onto develop.
#mergre #refactor
2017-08-17 01:03:14 +12:00
Calum 87acce71ea Merge branch 'develop' into story1118_map_arrows
# Conflicts:
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/RaceState.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-08-17 00:52:41 +12:00
Calum 95b1c8a01f Arrows now work as intended. Case added for interior angles.
#implement #story[1118]
2017-08-17 00:43:07 +12:00
Zhi You Tan dfd421c3b7 Merge branch '1124b_Fixing_Update_Order' into 'develop'
Updating Yacht Positions / Finish Screen

# Change Log 
* Yachts now update their position on the side bar as they pass marks 
* Finish screen is now displayed correctly at end of race, populated by races 

# Testing 
* Manual test log performed


See merge request !63
2017-08-17 00:11:13 +12:00
William Muir 4596f1e0f8 Finish screen all done, updates during race
tags: #story[1124] #pair[wmu16, cir27]
2017-08-16 23:42:07 +12:00
William Muir a59d06fbbf Finish screen 2017-08-16 23:36:45 +12:00
William Muir 65286f273b Working ordering for in game AND race finish screen.
List in RaceState holds a sorted list of yachts.
A Listener is added to this list, listening for a permutation change, on perm change, sorts the list by comparitor of leg number of each yacht (stable sort)
2017-08-16 23:22:58 +12:00
William Muir 9727e86249 Merge remote-tracking branch 'origin/story1118_map_arrows' into 1124_Fixing_Order_And_Finish_Screen
# Conflicts:
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/resources/server_config/xml_templates/race.ftlh
2017-08-16 22:40:32 +12:00
William Muir db614fe845 Initial commit for Fixing order 2017-08-16 21:16:27 +12:00
Calum 85899e3fbe Merged with develop functionality. 2017-08-16 21:11:34 +12:00
Michael Rausch 87d6799c10 Re-added wind speed & direction variations
- Wind speed  & direction are randomly updated
- Removed two duplicate threads ;)

Tags: #story[1124]
2017-08-16 20:48:37 +12:00
Alistair McIntyre 67f0c213c2 Everything works. Needs a bit of polish, and possibly look at boat shapes next.
tags: #story[1142]
2017-08-16 20:38:11 +12:00
Calum c103595bba Merge branch 'develop' into story1118_map_arrows
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/RaceState.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-16 20:34:27 +12:00
Calum 86a7c2565c Arrows now rendered in correct orientation but rounding arc sometimes inverted.
#implement #story[1118]
2017-08-16 20:29:56 +12:00
Calum 21e6819f16 Arrows now rendered in correct orientation but rounding arc sometimes inverted.
#implement #story[1118]
2017-08-16 20:17:25 +12:00
William Muir 28569506f0 Merge remote-tracking branch 'origin/develop' into develop 2017-08-16 20:09:20 +12:00
William Muir dad2ebf693 Fixed double thread issue! :D 2017-08-16 20:09:05 +12:00
Alistair McIntyre 02a7b804c1 Boat Names Change. Colors Change too. Updates clients during lobby when a change is made.
tags: #story[1142]
2017-08-16 19:31:27 +12:00
Peter Galloway 9dbb31dcef Merge branch 'Story40_Zooming' into 'develop'
Story40 zooming

# Zooming and Tracking
## Changes
* Boats can now be selected by clicking and selecting on the drop down menu
* Selected boats can now be tracked when the zoom level is increased
* Zoom in with the 'z' key and zoom out with the 'x' key

## Testing
* Manual testing logged in doc
* No cucumber tests made for this story

## Bug Fixes
* Fixed the sails animation bug

See merge request !62
2017-08-16 18:25:58 +12:00
Kusal Ekanayake a932f41cc2 Removed unused boolean in boat objects 2017-08-16 18:19:55 +12:00
Kusal Ekanayake 3570a3d2eb Removed unneeded imports 2017-08-16 18:16:55 +12:00
Kusal Ekanayake 85852df176 Modified limits to zooming
#story[1121]
2017-08-16 18:06:39 +12:00
Alistair McIntyre 6d045e9976 Merge branch 'develop' into Story80_BoatCustomization
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
2017-08-16 17:59:11 +12:00
Alistair McIntyre 4fc00f8a3c Boat names now change, just the issue of the lobby not updating unless another person joins/leaves to try and fix.
tags: #story[1142]
2017-08-16 17:57:22 +12:00
Kusal Ekanayake 9c2bac36b6 Merge branch 'develop' into Story40_Zooming 2017-08-16 17:27:09 +12:00
Kusal Ekanayake 8501fc0b6d Added limits to zooming
#story[1121]
2017-08-16 17:25:51 +12:00
Michael Rausch e9e7f306cd Merge branch 'Story1117_Course_Boundary_Collision' into 'develop'
Checked if a boat has crossed the boundary/course limit, if so, bounce the boat back.

# Boundary crossing detection
* if a boat has crossed the boundary, it will be bounced back to where it has came from

# Testing
* Manual testing has been done.
* Boats have bounced back when collides with other boats, mark or boundary. 

#story[1117] #pair[hyi25, zyt10]

See merge request !60
2017-08-16 17:25:14 +12:00
Michael Rausch fa68a5fdff Fixed commented out test
#story[1117]
2017-08-16 17:23:52 +12:00
William Muir 07a39722fb Minor bug fix to velocity calculation 2017-08-16 17:23:14 +12:00
William Muir 1d4ab0e1fa Merge remote-tracking branch 'origin/develop' into develop 2017-08-16 17:22:14 +12:00
William Muir 653651f97f Minor bug fix to velocity calculation 2017-08-16 17:22:00 +12:00
Haoming Yin b1ba6e729a Merge branch 'StartScreen' into 'develop'
Start screen

## Changes
* Race timer now works
* Lobby switches to race view at T-5 seconds
* Updated lobby title to show race name

## Testing
* Manual testing completed
* No unit or cucumber testing as this is mostly UI code

See merge request !61
2017-08-16 17:12:17 +12:00
Haoming Yin 99685d76a9 Merge remote-tracking branch 'origin/develop' into develop 2017-08-16 16:55:53 +12:00
Michael Rausch 711c94001b Fixed game crash, and improved timer
- Fixed the null pointer exception that happened on slower computers
- Made the timer start counting down when the host clicks ready

Tags: #story[1109]
2017-08-16 16:34:31 +12:00
Zhi You Tan 7f3d66d01d Checked if a boat has crossed the boundary/course limit, if so, bounce the boat back.
#story[1117] #pair[hyi25, zyt10]
2017-08-16 16:33:14 +12:00
Alistair McIntyre 1db75a8ae4 Request Messages now sending to server, need to update game and send response packet.
tags: #story[1142]
2017-08-16 14:54:50 +12:00
Kusal Ekanayake 7b9d28ade9 Progress made on the improper tracking of boats (boats aren't being correctly centered). 2017-08-16 14:51:52 +12:00
Michael Rausch 5843fc9212 The merge went well!!
Tags: #story[1109]
2017-08-16 14:51:44 +12:00
Calum 3542c646f8 Marker class can now store and show multiple arrows sequentially
#implement #story[1118]
2017-08-16 14:51:05 +12:00
Michael Rausch 8fb5ea2223 Merge branch 'develop' into StartScreen
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/utilities/StreamParser.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
2017-08-16 14:31:14 +12:00
Kusal Ekanayake 958c1e216f Fixed sail toggling issues and test. 2017-08-16 14:30:37 +12:00
Kusal Ekanayake 100689a20b Merged develop onto zooming for easier merge later on.
#story[1121]
2017-08-16 13:15:15 +12:00
Kusal Ekanayake 2da887e677 Merge branch 'develop' into Story40_Zooming
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/test/java/steps/ToggleSailSteps.java
2017-08-16 13:13:39 +12:00
Alistair McIntyre bd213bcd9e Merge branch 'develop' into Story80_BoatCustomization 2017-08-16 13:06:31 +12:00
William Muir bf016356a6 Finish screen now displays correctly on finishing.
GameState update now checks for finishing the race
Can now go back to main menu from finish screen
Labeled all threads for better debugging
Still need to implement race list ordering and finish screen ordering
2017-08-16 13:04:34 +12:00
William Muir 76a750a764 Moved sendRaceStatus Message out of S2C Thread into MS Thread (minor refactor) 2017-08-16 11:53:29 +12:00
William Muir 000d562ffe SailIn / out animation on client is now correct again 2017-08-16 11:27:46 +12:00
Haoming Yin d02f2385dd Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/model/ClientYacht.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java
#	src/test/java/steps/ToggleSailSteps.java
2017-08-16 10:11:58 +12:00
Haoming Yin a5261494cd Merge remote-tracking branch 'origin/1124_broadcast_mark_rounding_message' into 1124_broadcast_mark_rounding_message 2017-08-16 10:06:25 +12:00
Calum ac47e9d88a Game state now updates based on boat position. Arrows drawn as boat travels course. Currently do not point in correct direction, also the sparkline does not work.
#bug #refactor #implement #story[1118]
2017-08-16 03:51:48 +12:00
Calum 7329f7dc65 Merge branch 'develop' into story1118_map_arrows 2017-08-16 01:35:15 +12:00
Calum 7c5f146b11 Merge to integrate develop with separated Yacht classes. Functionality from this branch is not yet completed.
#refactor
2017-08-16 01:23:38 +12:00
Calum e3fbbd4590 Fixed sails in and out test.
#bug #test
2017-08-16 01:19:34 +12:00
Calum dc8baa09a3 Re-implemented collision tests.
#test #bug
2017-08-16 01:17:40 +12:00
Calum 720ce0ae5b Merged with develop. Moved all collision logic into game state.
#refactor
2017-08-16 01:04:16 +12:00
Calum a7a667b4bc Merge branch 'develop' into 1124_switching_to_finish_screen
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java
2017-08-15 23:45:50 +12:00
Calum 4e68cf31cf Fixed finish screen switch
tags: #fix
2017-08-15 23:30:27 +12:00
Zhi You Tan d03460d69e Implemented boat tracking when boat is selected by either clicking or selecting from sidebar combo box. Clicking tracked boat will cancel tracking, but using combobox will always result in one boat being selected.
#story[1121] #pair[wmu16, zyt10]
2017-08-15 23:16:25 +12:00
Peter Galloway 50baf6f85b implemented race finish functionality, finish screen not loading properly yet #story[1124] 2017-08-15 20:48:51 +12:00
William Muir 23a04facbe ServerYachts now send now have their own boat status.
This allows clients to know when this boat has finished

tags: #story[1124]
2017-08-15 20:24:51 +12:00
Kusal Ekanayake 7fbecc8b3d Fixed juttery movements when zooming and sometimes tracking.
Need to look into getting the boat propery fixed in the center.

#story[1121]
2017-08-15 19:14:16 +12:00
Alistair McIntyre 9cb5956f3c Updated lobby controller to pass the player ID through, and the lobby view to have a customize button.
tags: #story[1142]
2017-08-15 16:48:23 +12:00
Alistair McIntyre 366ebb3adb Merge branch 'Story66_Collision' into 'develop'
Collision branch merging to develop

# Change log
* Added yacht-mark collision and yacht will be pushed back upon collision.
* Added yacht-yacht collision and both yachts will be pushed back upon collision.
* Updated yacht map position initialisation. Yachts will all be spawned behind start line with a distance apart from each other.
* Added a collision visual alert when collision packet is received.
* Added two extra colours to support for 8 boats.

# Testing
* Junit tests for yacht-yacht collision.
* Manual testing for yacht map position initialisation
    * Yacht spawned apart from each other.
    * Yacht will spawn behind start line even if start line is different direction.

See merge request !59
2017-08-15 15:53:36 +12:00
Haoming Yin 458bd5408f Merge remote-tracking branch 'origin/1124_broadcast_mark_rounding_message' into 1124_broadcast_mark_rounding_message 2017-08-15 15:21:10 +12:00
Calum c00d165f47 Merge remote-tracking branch 'origin/1124_broadcast_mark_rounding_message' into 1124_broadcast_mark_rounding_message
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-08-15 15:13:40 +12:00
Calum d2bb15471a Changed the velocity equation for boats to handle outlying scenarios such as 0 velocity.
#bug
2017-08-15 15:12:39 +12:00
Zhi You Tan c0cd260610 Fixed junit fail 2017-08-15 15:06:11 +12:00
Haoming Yin 6a9357f598 Merged Develop onto refactored yacht branch
tags: #story[1124] #pair[hyi25, wmu16]
2017-08-15 15:04:35 +12:00
Haoming Yin 6909f99773 Merge remote-tracking branch 'origin/develop' into 1124_broadcast_mark_rounding_message
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameView.java
2017-08-15 14:58:13 +12:00
Zhi You Tan ce5424cc79 Fixed develop merge and now collision works 2017-08-15 14:53:15 +12:00
Haoming Yin c125708a4a Final commit for yacht refactor
tags: #story[1124] #pair[hyi25, wmu16]
2017-08-15 14:49:16 +12:00
Haoming Yin 2dc0ba07d9 WIP: Second commit for seperating server and client yacht classes
tags: #story[1124]
2017-08-15 14:30:39 +12:00
Zhi You Tan 2a3231d334 Merge remote-tracking branch 'origin/develop' into Story66_Collision
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/model/mark/MarkOrder.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-08-15 14:30:01 +12:00
Haoming Yin d6a436d2eb WIP: Initial commit for seperating server and client yacht classes
tags: #story[1124]
2017-08-15 14:18:48 +12:00
William Muir baacd8a9c0 Refactor. Taken Rounding logic out of yacht and into game state.
tags: #story[1124]
2017-08-14 23:52:06 +12:00
William Muir e1dddd317d WIP: Minor fixes 2017-08-14 17:57:41 +12:00
Peter Galloway b9ae9c4730 made server send appropriate racestatus flags in the race status packet #pair[mra106, ptg19] #story[1109] 2017-08-14 17:55:53 +12:00
Kusal Ekanayake fab5f9229f Got the boat's to track and center correctly.
As well as allowing selecting different boats to track them, doesn't work as intuitively as I would like but will need to sort out a boat selection feature first made specifically for the selection of only one boat.

 #story[1121]
2017-08-14 17:27:14 +12:00
Peter Galloway ff92262a78 fixed race clock countdown/count up #story[1109] 2017-08-14 16:32:50 +12:00
William Muir a4547e12cb Merged develop into this branch and resolved conflicts
tags: #story[1124] #pair[wmu16, hyi25]
2017-08-14 16:21:18 +12:00
William Muir 1a39b6e4a3 Merge branch 'develop' into 1124_broadcast_mark_rounding_message
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-08-14 16:16:04 +12:00
William Muir 58446ffaed MarkRounding Message now sent out correctly.
Added submark seqID attribute to each mark of a compound mark from parsing xml
Added Rounding side attribute to each individual mark as read from the xml
RoundingSide enum now has a method to get Enum from String literal
Now store the closest mark to each yacht in each update for purpose of knowing which mark they round
Minor code tidying, Added logger to serverToClientThread, removed 'serverLog' method
removed obsolete code
2017-08-14 16:11:32 +12:00
Alistair McIntyre 1f09005147 Message types for Request and Response created, and Messages for them also created.
tags: #story[1142]
2017-08-14 15:30:42 +12:00
Alistair McIntyre 3754b71f4d Merged updated develop into branch.
tags: #story[1142]
2017-08-14 14:31:02 +12:00
Alistair McIntyre 1d2222b599 Merge branch 'develop' into Story80_BoatCustomization 2017-08-14 14:23:05 +12:00
Alistair McIntyre 89ceab0c4b Merge branch 'Story64_SailsAnimations' into 'develop'
Story64 sails animations

The sail animations for the boats. 

The sail should follow the boat when the boats heading nears downwind and upwind. The shift key should make the sail have a squiggly animation and flow into the wind. A cucumber test has also been created and could be looked into for more stories.

Unintentionally merged in parts of the zooming story. (Basic zooming in and out)

See merge request !56
2017-08-14 14:19:00 +12:00
Alistair McIntyre 76dabb8138 Merge remote-tracking branch 'origin/Story64_SailsAnimations' into Story64_SailsAnimations
# Conflicts:
#	src/test/java/steps/ToggleSailSteps.java
2017-08-14 14:17:10 +12:00
Alistair McIntyre 8b543488e3 Fixed faulty develop merge and completed manual testing before merging back into develop.
tags: #story[1111]
2017-08-14 14:15:34 +12:00
Alistair McIntyre c52c345e53 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-14 14:04:53 +12:00
Alistair McIntyre 9420717b44 Added Messages to MessageType, continued working on request.
tags: #story[1142]
2017-08-14 13:38:41 +12:00
Kusal Ekanayake 9c1fe72f6b Merge and tests fix 2017-08-14 13:35:18 +12:00
Kusal Ekanayake 2e892f35ea Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-14 13:32:57 +12:00
Alistair McIntyre bedd742c93 Started on Request/Response packets for the Boat customization to begin the boat customization process.
tags: #story[1142]
2017-08-14 13:26:51 +12:00
Kusal Ekanayake add6856727 Merge remote-tracking branch 'origin/Story64_SailsAnimations' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-14 13:22:56 +12:00
Kusal Ekanayake 4dc6b2af2b Merge fix 2017-08-14 13:20:15 +12:00
Kusal Ekanayake e65558b8ac Merge branch 'develop' into Story40_Zooming
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-14 13:17:39 +12:00
William Muir e4cc5a0950 Fixing develop (sort of) 2017-08-14 12:52:58 +12:00
William Muir 4b4f8a25a3 Merge branch 'issue38_irregular_key_event_packets' into 'develop'
Issue38 irregular key event packets

A timed loop is now created on button press for sending out packets when a button is held down.
Removed the lists in LobbyController and replaced them with text objects to avoid the nested lists of single element lists.
Unit tests are in tests.java.seng302.visualiser.ClientToServerTets.RegularPacketsTest

**EDIT -** Tests fail due to some kind of error with reading from sockets in VM environment. Unable to fix at present.

See merge request !57
2017-08-14 12:49:52 +12:00
Zhi You Tan c77adf385e Terminating client thread before server thread to prevent alert box popping up 2017-08-14 11:21:33 +12:00
Zhi You Tan 9b7f4a93d8 Merge remote-tracking branch 'origin/issue38_irregular_key_event_packets' into issue38_irregular_key_event_packets 2017-08-14 11:02:19 +12:00
Zhi You Tan e780b47160 Fix javafx error on ci 2017-08-14 11:02:07 +12:00
Haoming Yin 028fc44dce Yacht now extends observable and can notify server to client thread to broadcast message.
#story[1124]
2017-08-14 10:48:11 +12:00
cir27 a185c9dc96 Implemented a factory class that creates mark arrows as needed. Needs to integrated with the real world data and tested to work. Works with all test data so far.
#implement #story[1118]
2017-08-14 03:44:46 +12:00
Calum 52e10997f1 Build issues caused by vm unable to open socket connections. Leaving as is for now.
#bug
2017-08-13 21:07:21 +12:00
Calum f2f750298c Added print statements for determining where javaFX is accessed from in the test. 2017-08-13 21:05:28 +12:00
Calum 1755b00079 Unncommented test to see if it breaks the build at remote repo. 2017-08-13 20:59:03 +12:00
Calum a844585faf Empty tests to check where bug is occuring 2017-08-13 20:54:13 +12:00
Calum b7fe79a5cc Attempted fix for build issues due to javaFX toolkit not being initialized. 2017-08-13 20:44:23 +12:00
Calum 2d5492601f Fixed build issues caused by changes to a yacht step turn constant.
#bug
2017-08-13 20:40:18 +12:00
Calum 0ee12021e2 Fixed bug that stopped clients from getting updated xml data.
#issue[46] #bug
2017-08-13 20:29:10 +12:00
Calum 6218d5506b Fixed bug that stopped clients from getting updated xml data.
#issue[48] #bug
2017-08-13 20:24:03 +12:00
Calum d79b0421c2 Fixed merged conflicts with merge onto develop.
#bug
2017-08-13 19:00:06 +12:00
Zhi You Tan 0b978593d4 Solved the fps counter zoom together with canvas problem. Workaround used was changing fps counter on game view to race view.
#story[1117] #pair[ptg19, zyt10]
2017-08-13 18:52:42 +12:00
Calum 8cb5b8caec Merge branch 'develop' into issue38_irregular_key_event_packets
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerPacketParser.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-13 18:44:34 +12:00
Calum 2093e79b6a Rather than a high frequency loop sending all packets a low frequency loop is made then destroyed on button press for sending turn packets. This means a fast response time on button press but fewer packets sent.
#implement #issue[38]
2017-08-13 18:38:24 +12:00
Kusal Ekanayake 8ec6490627 Started trying to get the zoomed in gave view to follow the boat.
Can easily make the boat stay put at the origin. can make it perfectly center, also weary of the weird jittery effect that is present when tracking. Tracking is active when the zoom scale is greater than 1.

#story[1121]
2017-08-13 17:02:35 +12:00
Kusal Ekanayake 0a7a9018f3 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-13 15:27:25 +12:00
Zhi You Tan 93cb0ca600 Updated yacht-yacht collision to push back both yacht.
Tweaked acceleration to increase time to top speed.

#story[1117] #pair[ptg19, zyt10]
2017-08-13 15:14:14 +12:00
Kusal Ekanayake 25c2de63a2 Working cucumber tests for sails toggle.
When sails in will send a sails out message when sails out will send a sails in message and then checks for the new status of the sail

#story[1111]
2017-08-13 14:55:33 +12:00
Kusal Ekanayake 2411a3cc2c Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-13 14:34:42 +12:00
Zhi You Tan fda233f5ad Merge branch 'Story71_TackAndGybeSmoothly' into 'develop'
Story 71 Tack/Gybe, Story 65 VMG Autopilot

# Implementation
- Added autopilot functionality to move boat towards a given heading with a single function call.
- Added functionality to move boat from one side of the wind to the other (tack/gybe) with a single button press.
- Added functionality to move boat to correct VMG from polars with single button press.
- Pressing a button a second time will disable the autopilot, as will pressing the Upwind/Downwind keys.

# Testing
- Fairly simple JUnit testing for a set of beginning headings and expected ends for the autopilot
- Unsure how cucumber testing would actually be better than simple junit testing in this case.

See merge request !55
2017-08-13 14:22:28 +12:00
Zhi You Tan c58cb1a476 WIP: Researched and implemented a simple right of way calculation (might be wrong).
#story[1117]
2017-08-11 23:57:27 +12:00
Michael Rausch d2fd9ebaea Started work on start screen
- Server now sends out race start status messages during lobby & pre-start stages
- Added timer in lobby

Tags: #story[1109]
2017-08-11 19:03:57 +12:00
Zhi You Tan fda6625256 Merge remote-tracking branch 'origin/develop' into Story66_Collision
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/model/mark/MarkOrder.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-10 20:23:56 +12:00
alistairjmcintyre 6ddaaa0dfa Removed extra print statement from test. 2017-08-10 20:18:36 +12:00
William Muir 55c22fa7e2 Merge branch 'MessageExtensions' into 'develop'
Message extensions

## Changes
* Implemented new Client<->Server handshake protocol

## Testing
* Manually tested as tests cannot be simulated in code. The following cases were tested
    * Client connects normally with empty lobby
    * Client connects normally with one other player in lobby
    * Client responds to lobby full error
    * Client responds to general error 

See merge request !54
2017-08-10 20:17:50 +12:00
Michael Rausch a7b8b0dbc3 Removed redundant input & output streams 2017-08-10 20:15:08 +12:00
Zhi You Tan 7e1686a980 Packets are sent out when collision happens and receiver is able to interpret and display as visual alert. Updated visual alert to looks better.
- Created yacht event code message to be sent out as packet.
- Added observers to main server thread on yacht so when collision detected, main server thread will send out yacht event message to all server to client threads.
- Updated collision visual alert using circle and animation timer.

#story[1117]
2017-08-10 19:50:30 +12:00
Alistair McIntyre 32b231e78a Added testing for the vmg autopilot
tags: #story[1107]
2017-08-10 19:19:41 +12:00
Michael Rausch 3ad37faedc Merge branch 'develop' into MessageExtensions 2017-08-10 19:01:46 +12:00
Michael Rausch 09c4f98056 Implemented new client - server handshake protocol
- Implemented new packet types
- Changed server & client logic to use new protocol

Tags: #story[1124] (Issue 39)
2017-08-10 19:01:30 +12:00
Alistair McIntyre 1c2870649a Added testing for the tack/gybe settings.
tags: #story[1105]
2017-08-10 18:58:38 +12:00
Alistair McIntyre a2ee4411be Merge branch 'develop' into Story71_TackAndGybeSmoothly 2017-08-10 17:59:19 +12:00
Alistair McIntyre a23352ef85 Merge branch 'develop' into Story71_TackAndGybeSmoothly 2017-08-10 17:59:10 +12:00
Alistair McIntyre f9d5bd10b1 Merge branch 'develop' into Story71_TackAndGybeSmoothly 2017-08-10 17:58:19 +12:00
William Muir 0b8ad137b3 Minor fixes for merge
#story[1124] #pair[wmu16, hyi25]
2017-08-10 17:47:24 +12:00
Alistair McIntyre a746191dba VMG works correctly, auto pilot is interupted by a repeated keypress or an upwind/downwind press.
tags: #story[1105]
2017-08-10 17:46:28 +12:00
Michael Rausch 1772d1c05e Merge branch '1124_Mark_Rounding_Implementation' into 'develop'
1124 mark rounding implementation

# ChangeLog 

* Implemented Mark Rounding Algorithm so the server acknowledges when boats pass around and through marks / gates 
* Compound Mark class changed so that it has one constructor which takes a list of Mark objects
* GeoUtility has had many methods added to it for purposes of deducing mark rounding

# Testing 

* JUnit tests done for GeoUtility Class
* JUnit tests done for CompoundMark Class
* Test class removed for Yacht Class as no longer testable
* Manual test-log entry made for mark rounding in game

See merge request !53
2017-08-10 17:20:33 +12:00
William Muir 1d7b527130 Tidied code. Added tests
#story[1124] #pair[wmu16, hyi25]
2017-08-10 16:45:30 +12:00
Alistair McIntyre 430779c943 Boat auto pilots correctly for tacking/gybing. Needs proper testing.
#story[1105]
2017-08-10 14:53:24 +12:00
William Muir 9c79897e01 Tidied code, added MidPoint to CompoundMark class
Compound Mark class is now constructed with a list of marks.
A mid point is created on its construction for use in Geo Calculations

#story[1124] #pair[wmu16, hyi25]
2017-08-10 13:58:32 +12:00
Michael Rausch 07386ed2db Improved boat bounce-back calculation
- Changed boat bounce back send the boat n meters in the opposite direction.
- Improved test to use the minimum of yacht and mark collision distances

Tags: #story[1117]
2017-08-10 13:01:31 +12:00
William Muir abb15f6edf Fixed Mark rounding Algorithm
Algorithm now knows when a player has to round a gate or just pass right through

#story[1124] #pair[wmu16, hyi25]
2017-08-10 12:08:03 +12:00
William Muir 87f2f1fe63 Fixed Mark rounding Algorithm
Algorithm now knows when a player has to round a gate or just pass right through

#story[1124] #pair[wmu16, hyi25]
2017-08-10 12:07:47 +12:00
William Muir 249ad9e5c0 Fixed Mark rounding Algorithm
Algorithm now knows when a player has to round a gate or just pass right through

#story[1124] #pair[wmu16, hyi25]
2017-08-10 12:02:19 +12:00
alistairjmcintyre 9d02d2fbea Implemented a fairly simple auto pilot setting for the yacht, on update if the boat is set to autopilot it will adjust the heading towards the desired heading. Needs some refinement.
#story[1105]
2017-08-10 02:04:51 +12:00
Michael Rausch b1598ccb0f Changed testUpdateYachtWithCollision to use MARK_COLLISION_DISTANCE
Changed testUpdateYachtWithCollision to use MARK_COLLISION_DISTANCE constant.

#story[1117]
2017-08-09 22:36:34 +12:00
Michael Rausch 08304f9c3e Fixed failing test 2017-08-09 22:05:49 +12:00
Michael Rausch 4cc48a355e Added mark collisions
- Boats now collide with marks
- Added method to MarkOrder to get all marks
- Reduced the frequency at which collisions are detected. This fixed some performance issues
- Added method to bounce the boat off a mark

Tags: #story[1117]
2017-08-09 21:57:50 +12:00
Calum 2c5fddb695 Implemented zooming and clipping of race border. Implementation is still hackish.
#pair[ptg19, cir27] #story[1121]
2017-08-09 16:56:46 +12:00
Calum 126d8ea870 Added factory class for producing mark arrows.
#story[1118]
2017-08-09 15:24:32 +12:00
Calum 30a6cb98ec Action packets now sent at regular 20ms intervals
#issue[38] #implement
2017-08-09 14:03:21 +12:00
Zhi You Tan 8813d06010 Created a simple red blink on a top of a yacht given source id.
Created and updated methods reading yacht event packet to translate to collision alert on visualiser.
WIP: sending yacht event packet to inform collision

#story[1117]
2017-08-09 01:26:59 +12:00
Kusal Ekanayake 0fbca89030 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-08 19:45:08 +12:00
Kusal Ekanayake 421ef3c65a Started progress on zooming, can now use z and x keys to zoom into map.
Nothing scales correctly asides from the map itself (the boats stay in the same position).

#story[1121]
2017-08-08 19:41:51 +12:00
Zhi You Tan 5937f8b640 Merge remote-tracking branch 'origin/develop' into Story66_Collision
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/model/Yacht.java
2017-08-08 17:04:34 +12:00
Zhi You Tan 5ec67d0b80 Added junit for update yacht (after collision implementation). Removed another version of initialise boat positions.
#story[1117]
2017-08-08 16:51:29 +12:00
Kusal Ekanayake 941febaf62 Tried to fix cucumber tests, getting closer.
#story[1111]
2017-08-08 16:31:27 +12:00
William Muir a545e9dbc3 Removing '>' characters from docstrings to fix build
Note: This is probably a XML style guid thing and is stupid. Can probably fix this
2017-08-08 16:14:13 +12:00
William Muir b0e7dddaf3 Fixed gate passing algorithm
boats now must pass through the correct way. This works for start in-race and finish gates
Refactored yacht algorithm code for better readability
Logging function added or seeing mark roundings occur

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-08 15:58:13 +12:00
Haoming Yin ed0a783374 Fixed the bug that boats could round over a gate but still "across" it. Added unit test to ensure the algorithm works.
tags: #story[1124]
2017-08-08 10:42:36 +12:00
Kusal Ekanayake 97696cc95b Started work on cucumber tests. Trying to sort through threads, getting inconsistent results.
#story[1111] #pair[kre39,ptg19]
2017-08-07 17:54:34 +12:00
William Muir 4375b73257 Implemented algorithm for checking if boat passes through a mark.
Mark rounding works for whole course (WITH BUGS)
Still some gate logic to work out.
Moved gate function to GeoUtil class

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-07 17:28:12 +12:00
Kusal Ekanayake f97b18d594 Sailsin graphics moving and working as expected.
#story[1111] #pair[kre39,ptg19]
2017-08-07 16:48:30 +12:00
Kusal Ekanayake c7857872ce Luffing sails animation working correctly, working on sailsin animation now.
#story[1111] #pair[kre39,ptg19]
2017-08-07 15:24:01 +12:00
Zhi You Tan 79105a1bdc Created a simple collision detection by iterating each boats per update. Working but sequential checks can be costly.
#story[1117]
2017-08-07 12:01:26 +12:00
Zhi You Tan a4b22190c0 Changed the spawn point to behind start line and calculated quadrant to make sure yachts spawn behind start line in different map scenario.
#story[1117]
2017-08-07 11:50:07 +12:00
Calum a3ce5998ff Action packets now sent at regular 20ms intervals
#issue[38] #implement
2017-08-07 10:58:07 +12:00
William Muir 7f0329dda6 WIP: Implemented basic mark rounding algorithm.
Removed RacePosition class. Instead marks are just grabbed from the mark order class when necessary.
No marks are stored as an attribute in the yacht class but the 'currentMarkSeqID' which is used to get current, and surrounding marks.
Works for all marks in between but not including starting and finishing gate as no angle can be made with them. Still to work out how to implement this

 #story[1124]
2017-08-07 00:23:54 +12:00
Calum 8a40119a98 Action packets now sent at regular 20ms intervals
#issue[38] #implement
2017-08-06 22:17:08 +12:00
Zhi You Tan a470cb66a2 Updated initialise boat function so it can now initialise boats with distance apart in meters.
Created a second prototype function which is more testable compared to the initial design. New function takes in parameters (starting marks, yacht starting position, yacht) and initialise yacht correctly with position.

#story[1117]
2017-08-06 21:16:14 +12:00
Kusal Ekanayake ecf2c52cfa Added tests, and sails to all clients.
#story[1111]
2017-08-06 15:43:22 +12:00
Haoming Yin 43788bd153 Added a method to test if a geo point is located in a triangle which is formed by other three geo points.
The method helps to check the mark rounding.

Also unit tests have been done for this method.

tags: #story[1124]
2017-08-06 12:36:57 +12:00
Zhi You Tan 81c2a8e0fd WIP: Added test initialise boat position test. Corrected ColorsTest after addition of two new colours. 2017-08-05 23:59:58 +12:00
William Muir e90a0ce435 Merge branch '1124_Mark_Sequence_From_RaceXML' into 'develop'
Loading mark sequence from RaceXML

# Loading mark sequence from RaceXML

## Change Log
1. Added MarkOrder class
* Mark order is read from the generated RaceXML and stored
* Added .getNextMark() to get the next mark in the race
* Added .equals() and .hashCode() for Marks
* NEW: Added RacePosition class to hold players position in the race
* NEW: Fixed issue where the duplicates weren't stored in the mark order

## Testing
* Unit tests in models/MarkOrderTest.java

## Acceptance Criteria
* Use the mark sequence in the raceXML
    * Met by change log item (1)

* Store relevant mark details with each participating boat (Last mark, next mark)
    * Method in MarkOrder to get next mark, however the last mark and next mark will need to be stored by whoever implements the second task.

See merge request !52
2017-08-05 15:18:26 +12:00
Zhi You Tan a727014fcb Implemented boats spawning in parallel at the start line with spacing.
Added two more colours to support up to eight boats.

#story[1117]
2017-08-05 00:31:36 +12:00
Kusal Ekanayake ae28ccf228 Made sails luff in the right direction.
#story[1111]
2017-08-04 16:24:34 +12:00
Michael Rausch 281ce2d842 Loading course mark order from RaceXML
- Re-engineered code to work with the new marks
- Fixed bug where race order wasn't correct (added RacePosition class to fix)
- Rewrote tests to work with new RacePosition class

Tags: #story[1124] (Task 1)
2017-08-04 13:20:50 +12:00
Kusal Ekanayake f8af9cc259 Works for clients and server.
Due to the information being sent and received, it only currently works on client side boats.

#story[1111]
2017-08-03 19:07:30 +12:00
Zhi You Tan 8af80e6c3a WIP: Connected game client to main server thread to pass compound mark variable.
Boats are initialised in main server thread behind start line before game starts.

#story[1117]
2017-08-03 18:39:15 +12:00
William Muir 874cdec654 Added booleans: has entered rounding zone, has crossed first line, has crossed second line
All for purposes of checking mark rounding.

Currently not yet finished

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-03 17:39:07 +12:00
Kusal Ekanayake 99d5545ed3 Made the sails work properly by toggling.
Need to remove the unneeded code I added.

#story[1111]
2017-08-03 16:33:51 +12:00
William Muir 423f1acdb6 Merge remote-tracking branch 'origin/develop' into develop 2017-08-03 16:29:22 +12:00
William Muir 454e9ac9f1 Added an attribute to each yacht: 'DistanceToNextMark'
This attribute is calculated at each update of the boat as prompted by the game state regularly
Removed the lat and lng attribute from the Yacht class and replaced its usage with the GeoPoint object instead

Removed redundant test files and merged GeoUtility and testGeoUtil test classes into one

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-03 16:29:12 +12:00
Kusal Ekanayake a8e70b3631 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
#	src/main/java/seng302/model/Yacht.java
2017-08-03 15:19:13 +12:00
Kusal Ekanayake eb83e9dcc5 Working idle sail animation.
Need to work on getting sails to toggle properly. Have the 2 animations almost working but unable to switch between the two.

#story[1111]
2017-08-03 14:50:56 +12:00
Zhi You Tan 1ab849fd0d Added cucumber dependency. 2017-08-03 14:02:52 +12:00
William Muir db078538ff Minor cleaning in yacht class
tags: #story[1124]
2017-08-03 13:53:30 +12:00
Calum 1c0d869894 Corrected a value in the race.xml 2017-08-03 13:49:53 +12:00
Calum f9e6df46c1 Fixed issues caused by merge.
#bug
2017-08-03 13:23:43 +12:00
Calum 5228c078bc Merge branch 'develop' into 1124_Mark_Sequence_From_RaceXML
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/server/simulator/Simulator.java
#	src/main/java/seng302/models/mark/Mark.java
#	src/main/java/seng302/visualiser/map/CanvasMap.java
2017-08-03 12:27:11 +12:00
William Muir f5f73ede6b Merge branch 'story61_player_perspective' into 'develop'
Big boy merge request. Issue 34. Issue 35.

# This merge request is to fix **Issue 34** and **Issue 35**
*This is will require some further work but is being added to develop as is*

# Changelog

## Mark classes from server now used in client also.
The mark setup client side was poor because all marks were of type AbstractMark but they all had different methods so you had to identify what kind of mark they were anyway. The server side implementation
used the same calls for all Mark types and had the bonus of a GeoPoint class with utilities for distance and calculations.

## Yacht Class now contains observable values
The yacht attributes used by the model are now observable.

## StreamParser now static utils class.
Data no longer needs to be stored in the staticly available thread. Can now be processed in a more direct fashion by classes calling the parsing utils when needed.

## XMLParser now static utils class.
XMLParser now converts XML formatted doc objects and returns the containing data, this removes the need for the class to store multiples kinds of XML data and can instead just return it for use elsewhere.

## Data storage classes added to java/seng302/model/stream/parser
Rather than the StreamParser updating the Model directly it just processes the data into basic data types and shoves it all into objects for processing elsewhere. This avoids the static StreamParser class needing to know about Model objects.

## All static ultilities moved to their own package to make them easier to locate. Any child classes moved to their own files and added to packages in the model package.

## All visualiser code moved to the a new visualiser package

## Grouped all high level logic for the app and data management.
Previously all the high level logic (e.g. when to start a race, what fxml to show, how to process data from the stream) was in a number of classes. Mainly StreamParser, Controller, StartScreenController, RaceViewController, BoatGroup and LobbyController. All of this has been grouped into GameClient.
* This reduces the need to share data between these classes. Previously all the controllers needed references to one another to work so they were essentially the same class anyways.
* This makes it easier to find specific logic.
* Everything is now accessabile from GameClient, or from something GameClient knows about, making it easier to pass information if needed.

## Logic removed from LobbyController. Now only displays information

## Annotation box now a generic case for a rectange containg any number or type of annotation.
This means that if any new annotation needs to be added you don't have to hard code a specific case for it. Just initialize the annotation in the box at anytime.

## GameClient now observes the state of Yacht and Marker classes for position changes and updates them accordingly.
This avoids having to pass data to the GameView and having to poll for changes.

## CanvasController renamed GameView, now only concerned with displaying the race.
Moved all unnecessary logic to GameClient. Also fixed some issues where it polled RaceViewController for data instead of being updated by RaceViewController.

## MarkerObjects now a simple circle bound to the location of a given Mark. To display a gate lines are bound between the circles bound to Marks if a CompoundMark has more than one Mark.
Made all the logic here much more straight forward.

## Replaced most public static data classes with listeners.
Removed a bunch of polling. Made sharing data simplier and more readable.

# Known Issues & Limitaitons

* Pretty much everything that wasn't working at the end of sprint 5.
* Because listeners are triggering stuff on the JavaFX thread you have to use `Platform.runLater(() -> {//Do stuff});` whenever you want to access FX stuff from other threads. You can't just be lazy and put everything in it because then the JavaFX thread goes really slow. This is actually good practice anyways since you want to process data off of them FX thread (so not anything that's not triggered by a controller or an event) to keep JavaFX responsive.
* There is a 100% chance stuff has broken with this refactor that I'm not aware of. Some of it I am aware of.

# Testing

Good joke. I did manual testing though.

# Acceptance criteria

It works more or less.

See merge request !50
2017-08-03 12:11:20 +12:00
William Muir 1160f274ee Reformatted code in appropriate style
Build fails server side only due to some dependency issue. Internet problem server side?? (M I C H A E L?)

tags: #issue[34]
2017-08-03 12:06:07 +12:00
William Muir 53f5d63f15 Fixed build. (Actually this time) ((NOW WERE READY TO MERGE)))
All doc string annotations were required to be fixed for all methods

tags: #issue[34]
2017-08-03 11:50:07 +12:00
William Muir 1150ec3e43 Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/App.java
2017-08-03 11:48:54 +12:00
William Muir c655cb3fab Fixed build. (Actually this time)
All doc string annotations were required to be fixed for all methods

tags: #issue[34]
2017-08-03 11:45:14 +12:00
William Muir 8e24c204fd Fixed build.
Google map does not work

tags: #issue[34]
2017-08-03 11:26:44 +12:00
Michael Rausch 7885b3fae2 Loading course mark order from RaceXML
- Mark order is read from the generated RaceXML and stored
- Added .getNextMark() to get the next mark in the race
- Added .equals() and .hashCode() for Marks

Tags: #story[1124] (Task 1)
2017-08-02 22:03:10 +12:00
Michael Rausch b01d39f19f Merge remote-tracking branch 'origin/develop' into develop 2017-08-02 20:42:22 +12:00
Michael Rausch bbf494e9a1 Merge branch 'master' into 'develop'
Merge Master into develop



See merge request !51
2017-08-02 20:42:07 +12:00
Michael Rausch eae201cb4b Merge remote-tracking branch 'origin/develop' into develop 2017-08-02 20:32:43 +12:00
Calum 87ef37a689 Fixed connecting to hosts. Fixed issue34 and 35 to the point where they can be developed off of.
#refactor #bug #issue[34, 35]
2017-08-02 00:26:57 +12:00
Alistair McIntyre b2c7b65191 Merge branch 'Logging' into 'master'
Added dependencies for slf4j



See merge request !49
2017-08-01 15:20:41 +12:00
Calum 908c0749cf Boats now move on screen as intended.
TODO - Make the client connect to the server.
2017-08-01 02:37:55 +12:00
Calum 47c5e6f155 Reverting some new graphics classes back to how they were on master. 2017-07-31 23:35:28 +12:00
Michael Rausch 9deba732b0 Added dependencies for slf4j 2017-07-31 18:34:55 +12:00
Calum b82d0d0137 Boats and map are now updated using the observer pattern.
#implement
2017-07-31 05:23:41 +12:00
Calum f1ad03e913 Refactored the setup for MarkObjects (now renamed Markers) and made the CompoundMark + Mark + GeoPoint classes the standard across all classes instead of GateMark + SingleMark + Mark.
#refactor
2017-07-31 02:19:19 +12:00
Calum 6cae338c1e Began fixing bugs with caused by asynchronous listener calls.
#bug
2017-07-30 20:12:19 +12:00
Calum 7894e31926 Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/client/ClientState.java
#	src/main/java/seng302/client/ClientStateQueryingRunnable.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/model/stream/StreamReceiver.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/fxObjects/BoatObject.java
#	src/test/java/seng302/model/stream/StreamReceiverTest.java
2017-07-30 18:07:28 +12:00
Michael Rausch 25d8c8f9c4 Minor bug fixes.
Server will now only save incoming connections if in lobbying mode as it should
Commented out boat status printing

tags: #story[1047]
2017-07-27 14:15:55 +12:00
Zhi You Tan 1d9dd76356 For client's lobby view, it now can shows the connected host IP
#story[1055]
2017-07-27 13:12:53 +12:00
Zhi You Tan c2c34705d5 removed an unnecessary print statement and caught an exception 2017-07-27 13:01:04 +12:00
Zhi You Tan 96ed5e445e Replaced print stack trace with print statements 2017-07-27 12:47:18 +12:00
Kusal Ekanayake 870dc07fd2 Slight improvements to hosting.
Allow a host/client to disconnect and reconnect/make lobby, leave lobby and play the game.

#pair[kre39,hyi25] #story[1047]
2017-07-27 12:45:22 +12:00
Michael Rausch ecbb3f6658 Merge remote-tracking branch 'origin/develop' into develop 2017-07-27 11:06:00 +12:00
Haoming Yin 34704bd93d Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 23:32:32 +12:00
Haoming Yin 201c88a253 Fixed a bug that program crashes when it receive regatta xml during the race, as it doesn't know how to handle the xml.
#story[984]
2017-07-26 23:32:16 +12:00
Peter Galloway 6c4da58d9d Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 22:42:48 +12:00
Peter Galloway b56fa5cba3 refactored to make boat trails work again #fix #refactor 2017-07-26 22:42:37 +12:00
Peter Galloway 9cedbeb6f6 Fixed latency issues caused by clientside movement packets queueing up but currently the trails aren't working anymore #fix #refactor 2017-07-26 22:02:46 +12:00
Michael Rausch 4c6d107102 Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 20:28:21 +12:00
Michael Rausch 7917a2584b Fixed alignment for wind direction 2017-07-26 20:28:13 +12:00
Alistair McIntyre f99f8a0d7c Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 20:13:09 +12:00
Alistair McIntyre 7db716f51c Boat should move towards optimal angle upwind and downwind when pressing spacebar (VMG)
#story[988]
2017-07-26 20:12:57 +12:00
Haoming Yin 592e5a088f Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 19:58:45 +12:00
Haoming Yin 2d6850950c Merge branch 'develop' into Documenting_client_and_server
# Conflicts:
#	src/main/java/seng302/client/ClientToServerThread.java
2017-07-26 19:57:43 +12:00
Haoming Yin ef6821a0cd Updated and added more documentations
#story[1047]
2017-07-26 19:55:35 +12:00
Michael Rausch 6e9535d78f Fixed race timer & Added boats to team position list
- Race status messages are sent at regular intervals instead of once at race start
- Boat positions are initialised on the Team Position list
- Timer counts up from when host clicks ready

Tags: #story[377]
2017-07-26 19:35:59 +12:00
Alistair McIntyre 72a45f5984 Fixed a bug where if boatMaxSpeed was 0 and sails were up, boat would not decelerate.
#story[986]
2017-07-26 17:11:34 +12:00
Alistair McIntyre de600fa062 Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 17:10:29 +12:00
Haoming Yin 87e3b4f246 Merge branch 'story61_player_highlighting' into 'develop'
Story61 player highlighting

Added some highlighting for the players boat and a random name generator to help distinguish clients. The highlighting implementation is pretty hackish since they will be replaced by the visualiser overhaul anyways. To avoid refactoring a lot of code I just made some unused javaFX elements invisible rather than removing them, I can do it properly if requested.

See merge request !48
2017-07-26 16:31:53 +12:00
Alistair McIntyre 7b47d72ef0 Merge branch 'develop' of /home/cosc/student/ajm412/Documents/SENG302/team-13 with conflicts. 2017-07-26 16:22:01 +12:00
Calum 7392bdb80d Added documentation
#document
2017-07-26 16:03:11 +12:00
Zhi You Tan af9f1417f1 Documented client packet parser, client state, client state querying runnable, client to server thread and replaced e.printStackTrace() with client log messages. 2017-07-26 16:01:41 +12:00
Calum 8fc00fd750 Enabled boat path for only the player.
#story[987]
2017-07-26 16:01:01 +12:00
Calum 4e5b67abfa Merge branch 'develop' into story61_player_highlighting 2017-07-26 15:52:29 +12:00
Calum 84e8ac89fc Added random name generator until players can chose how to identify themselves. 2017-07-26 15:52:02 +12:00
Calum 12d081a1af Added highlighting for the player and moved all their assets to the foreground
#story[987]
2017-07-26 15:49:00 +12:00
William Muir 5e6b402bf5 Minor structural changes. GameState now has a thread which updates itself so its update
rate can be independent of sending packets to the client in the MainServerThread

#story[986] #pair[wmu16, ptg19]
2017-07-26 14:48:32 +12:00
Alistair McIntyre 2bfa6cb038 Adjusted the velocity calculations to allow for Acceleration/Deceleration of the boats, rather than just coming to a complete halt when the sails are pulled in.
#story[986]
2017-07-26 14:01:51 +12:00
Calum 8ac44d13df Began fixing conflicts with LobbyController
#bug
2017-07-26 12:05:03 +12:00
William Muir d99055901f Removed Music 2017-07-26 11:38:29 +12:00
Calum 9c9f6e4e80 Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
2017-07-26 02:56:46 +12:00
Calum 08e369f1ae Merged with develop. Fixed many bugs in Visualiser.
#bugs
2017-07-26 02:49:31 +12:00
Michael Rausch b0e99ab444 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/server/ServerThread.java
2017-07-25 23:10:41 +12:00
Haoming Yin c77a48f589 Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 23:06:06 +12:00
Haoming Yin 0135426dfe Changed lobby player profile to a lower risk pictures
#story[1055]
2017-07-25 23:05:52 +12:00
William Muir 37c745c139 Polar velocities should now work as intended.
Snapping to VMG still needs to be implemented.
Still an issue of not being able to pass the total upwind or downwind point

tags: #story[986]
2017-07-25 22:27:26 +12:00
William Muir 7880039801 Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 22:19:19 +12:00
William Muir a56dac1e87 Polar velocities should now work as intended.
Snapping to VMG still needs to be implemented.
Still an issue of not being able to pass the total upwind or downwind point

tags: #story[986]
2017-07-25 22:19:03 +12:00
Haoming Yin 4ae422b47b When a player connects to the server, lobby will now show their user id and profile instead of showing all the profile gif at the beginning,
#story[1055] #pair[hyi25, zyt10]
2017-07-25 21:50:23 +12:00
Calum acd54dec7a Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/Boat.java
#	src/main/java/seng302/models/stream/XMLParser.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
#	src/main/java/seng302/visualiser/controllers/StartScreenController.java
#	src/main/java/seng302/visualiser/fxObjects/BoatObject.java
#	src/main/resources/views/LobbyView.fxml
#	src/main/resources/views/MainView.fxml
2017-07-25 21:05:15 +12:00
Calum 6242ab0b2e Implemented observer and strategy pattern in BoatAnnotations, now renamed AnnotationsBox.
Also implemented various other small fixes and further refactored code.

#refactor
2017-07-25 20:45:27 +12:00
Michael Rausch c8a96dcce9 Fixed XML Loading error, used VMG to calculate boat velocity
- XML read from stream instead of file
- Started implementing VMG to calculate boat velocity dynamically

Tags: #pair[wmu16, mra106] #story[986]
2017-07-25 20:14:50 +12:00
Kusal Ekanayake 4f2dca7ecf Added proper the starting locations for marks and boats. 2017-07-25 18:48:33 +12:00
Zhi You Tan e569574c01 Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 17:17:45 +12:00
Zhi You Tan f544734b4d Fixed sending wrong race xml when a player disconnected because xml is getting data from gamestate yacht but the yachts are not updated is player disconnect.
Heartbeat packet was sent out at wrong rate which cause the player disconnect detection to be slow. Heartbeat packet is send out every 200ms now.

#story[1055] #pair[hyi25, zyt10]
2017-07-25 17:17:36 +12:00
Kusal Ekanayake 80b439470b Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 15:22:56 +12:00
Kusal Ekanayake 539197cef5 Added a visual indicator of wind speed during race (text box)
#pair[kre39,mra106] #story[1040]
2017-07-25 15:22:46 +12:00
Zhi You Tan 1a867be387 Added keystroke frequency limit
Updated client and server log format

#story[988] #pair[hyi25, zyt10]
2017-07-25 15:13:48 +12:00
Kusal Ekanayake 3785cd705f Fixed wind speed and direction being sent correctly.
#pair[kre39,mra106] #story[1036]
2017-07-25 15:08:10 +12:00
Kusal Ekanayake 5d7a438080 Changed default ip back to non local host. 2017-07-25 14:28:21 +12:00
Kusal Ekanayake 2f12f3e34f Fixed bug to let multiple people play at the same time. 2017-07-25 14:27:52 +12:00
Zhi You Tan 52bfa3ad34 Merge remote-tracking branch 'origin/Story1055_Send_Race_Status_When_Host_Ready' into develop
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
2017-07-25 00:03:58 +12:00
Zhi You Tan d1d659b698 Lobby view will switch to race view when received race status packet with race start type.
Ready button can only be pressed by host. Once pressed, it will send out race status packet with race start to all clients.

#story[1055]
2017-07-25 00:01:59 +12:00
William Muir cdb9337aed Deleted GameServerThread after being re merged in
#chore
2017-07-24 23:20:47 +12:00
Zhi You Tan 8b0af5bb62 Updated observer so it sends out updated boats.xml when client disconnects
#story[1047] #pair[wmu16, zyt10]
2017-07-24 21:35:31 +12:00
Zhi You Tan 83232a935e Merge remote-tracking branch 'origin/Story984_Send_XML_After_Clients_Connect' into develop
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-24 21:30:12 +12:00
William Muir a30a1aa7c7 Tweaking to server loop making it send packets at 5Hz
Commented out some smoothing code in BoatGroup that was dependend on FPS screwing with movement
2017-07-24 21:14:17 +12:00
William Muir 07cebb6c5b Updated velocity in yacht constructor so the boat can be seen properly working for test purposes 2017-07-24 16:47:17 +12:00
Kusal Ekanayake 45bf65a3d3 Made clients receive new xml after new clients connect.
#pair[kre39,zyt10] #story[984]
2017-07-24 16:15:19 +12:00
Kusal Ekanayake 60f5a99b0c Merge branch 'Merging_GameLoop_with_Broadcast' into develop
# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
2017-07-24 15:32:52 +12:00
Kusal Ekanayake 526c12127f Merging game lobby with game state broadcast. Merge conflicts resolved.
Port numbers updated to 4942.
2017-07-24 15:26:51 +12:00
Kusal Ekanayake 1daac842f2 Merging game lobby with game state broadcast 2017-07-24 15:01:07 +12:00
Kusal Ekanayake 3e4a6f0f2e Merge remote-tracking branch 'origin/Story1055_Lobby_View_Update' into Merging_GameState_With_Lobby
# Conflicts:
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/client/ClientToServerThread.java
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-24 14:58:14 +12:00
Kusal Ekanayake c1e937049e Merge remote-tracking branch 'origin/Story62_Swtich_To_RaceView_When_Race_Start' into Merging_GameLoop_with_Broadcast
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-24 14:37:56 +12:00
Zhi You Tan 8f8d5c7384 Removed a test sending xml
#story[1055]
2017-07-24 14:24:54 +12:00
Calum aad93d8913 Parsing classes now static utilities. Data now moved to model via controller class. Race logic shifted out of grpahics classes. Several improvements to code readability.
#story[986] #refactor
2017-07-24 12:14:08 +12:00
Zhi You Tan 027c7a1480 Updated start screen controller to connect to itself after setting up server.
#story[1047]
2017-07-24 12:09:23 +12:00
Zhi You Tan df2efa3329 Lobby controller list view is able to react to changes in client state boats and update the list view appropriately.
#story[1055]
2017-07-24 11:15:10 +12:00
Zhi You Tan 9d754c8819 Implemented list views initialisation which will set the first pane to be your source id (after three way handshake) and the remaining pane to be the source id of other players based on boats.xml received.
Updated client parser and client state to save a list of player's boat

WIP: refresh list view to show the latest update in players

#story[1055]
2017-07-23 20:42:21 +12:00
Zhi You Tan e11ceed28c Merge remote-tracking branch 'origin/Story62_Creating_Game_Loop' into Story1055_Lobby_View_Update
# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-23 19:59:30 +12:00
Michael Rausch 8b8b6e4afa Fixed map glitch when race starts, and race XML bug
- Race XML wasn't being sent to clients, this was causing a null ptr exception
- Boat location was being set to an invalid lat/lng

Tags: #story[1047]
2017-07-23 18:20:13 +12:00
Kusal Ekanayake ed2a22b573 Tried to merge game loop with the broadcast. Minor error in canvas. need to fix.
#story[1047]
2017-07-23 17:37:45 +12:00
Kusal Ekanayake 41851ee925 Merge branch 'Story62_Creating_Game_Loop' into Merging_GameLoop_with_Broadcast
# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerListenThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/models/Yacht.java
2017-07-23 17:34:24 +12:00
Kusal Ekanayake ffc61942a9 Fixed broken pipe error.
Was caused by boat ids being over 1000 and when turned into xml having a comma placed between the hundreds and the thousands? So to fix, I just mad the max id number 999 so there should not be any issues regarding that if another broken pipe error occurs. Also switched to use the correct yacht constructor.

#story[1047]
2017-07-23 17:10:18 +12:00
Kusal Ekanayake 2e4382bff6 Trying to fix the boat information that is being sent over.
Current issue is that a broken pipe error keeps occurring potentially due to how the messages are being sent so quickly that the previous message may have not finished sending. Also only caused the xm packets to be sent on trigger when a new client connects.

#story[1047]
2017-07-23 16:22:59 +12:00
Zhi You Tan f542dbb61e Changed the competitors list view to eight individual list view.
Added eight individual image view to support future player icon implementation.

#story[1055]
2017-07-23 03:00:29 +12:00
William Muir 2869d139a3 Three way handshake implemented client and server side and functioning
Server generates a new Id for connections (Size of connections + 1)
Client now stores an id allocated to it by the server
The id is currently being saved in the clientToServer thread client side

tags: #story[987] #implement
2017-07-22 17:44:37 +12:00
William Muir 3ec930491f Minor refactor, threads now start themselves
tags: #story[989] #refactor
2017-07-22 16:45:24 +12:00
William Muir a0005064ac Fixed the Yacht clas so it now works.
Lists and Maps are instantiated as they should be in GameState which were creating NullPointers
Introduced ServerLog to server to client threads for bug reporting
Introduced some print statements to test the game state updating upon receiving key presses

tags: #story[989] #refactor #fix
2017-07-22 16:32:05 +12:00
Michael Rausch 33fae9d69a Added race boats to XML Generator
Tags: #story[1047]
2017-07-21 16:56:46 +12:00
Peter Galloway 913e5fee7b Hooked up key press actions to the GameState, applying the relevant maths to update headings etc.
tags: #story[989]  #pair[ptg19, wmu16]
2017-07-21 16:50:09 +12:00
Zhi You Tan 3992073303 Set race started state in client state when packet is passed in client packet parser.
#story[1055]
2017-07-21 16:36:56 +12:00
Zhi You Tan 797a99f632 Merge remote-tracking branch 'origin/develop' into Story62_Swtich_To_RaceView_When_Race_Start
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-07-21 16:35:04 +12:00
Zhi You Tan e891ed8a64 LobbyView now can change to RaceView upon race start packet received.
Added port number text field in start screen controller.
Created a client state.

#story[1055] #pair[hyi25, zyt10]
2017-07-21 16:14:45 +12:00
Michael Rausch e8c2cf809b Fixed Null Ptr Exception in GameState 2017-07-21 15:42:20 +12:00
Michael Rausch ec761893c7 Merge branch 'Story62_Reading_Keystrokes' into 'develop'
Story62 reading keystrokes

Big merge

See merge request !44
2017-07-21 15:39:39 +12:00
Kusal Ekanayake 5df7efda03 Started implementing the gameState broadcasts.
Initial xml files are almost broad casted, just need to create them (or import the for the regatta). Started the setup for sending boat location packets, should work once we get at least the boat xml working when being sent.

#story[1047]
2017-07-21 13:16:43 +12:00
Kusal Ekanayake 2fff73c075 Merge remote-tracking branch 'origin/984_Xml_Generation' into Broadcasting_GameState
# Conflicts:
#	src/main/java/seng302/controllers/FinishScreenViewController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/gameServer/GameServerThread.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/models/Yacht.java
2017-07-21 12:31:35 +12:00
Zhi You Tan d37cbd263e Merge remote-tracking branch 'origin/Story62_Creating_Game_Loop' into Story62_Reading_Keystrokes
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-21 11:13:40 +12:00
Calum 3ec1242a9a Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-07-21 11:13:04 +12:00
Calum 881f7f8e30 Changed package heirachy. Merged Controller and StartScreenController.
#refactor
2017-07-21 09:22:55 +12:00
Peter Galloway 12c2f31af9 Merge remote-tracking branch 'origin/Story62_Creating_Game_Loop' into Story62_Creating_Game_Loop
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-07-20 19:38:07 +12:00
Peter Galloway 49c0c029c3 adjusted the way the server is receiving key presses to enable them be passed through to the game state #pair[ptg19, wmu16] #story[989] 2017-07-20 19:35:59 +12:00
Alistair McIntyre da7a34fc55 Started adding functionality to calculate yacht velocity from the wind speed and direction using polar tables. Also began writing tests to cover this functionality, as it can't currently be tested within the game itself.
#story[986]
2017-07-20 14:30:13 +12:00
Michael Rausch 322ff740e2 Added race start time to race XML
- Added race start time to race XML
- Added documentation
- Removed test code in MainServerThread

Tags: #story[984]
2017-07-20 14:28:59 +12:00
Kusal Ekanayake b1575e57df Host also can be it's own client.
The host can connect to itself to become a client, packets are also sending from the host to client, update method should be ready to fully implemented. Added chatter packets to packet types to be used mostly for testing but can be further implemented for proper use in the future.

#story[1055]
2017-07-20 13:53:53 +12:00
Michael Rausch 82b219cdba Boat and race XML now generated dynamically
- Removed course from XML Generator as it was not needed
- Boat and race XML added
- Method names in Yacht class updated to follow JavaBean standard so they can be read by the template engine

Tags: #story[984]
2017-07-20 13:30:55 +12:00
Peter Galloway e317de7562 Added mock yachts to the game state for each client #story[1047] 2017-07-20 13:11:37 +12:00
Calum 9ecaa7c3b3 Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective 2017-07-20 13:05:26 +12:00
Calum 037b0db01b Refactoring client for more atomic classes, will mimic the socket, game state, logic thread layout used by the server.
#refactor
2017-07-20 13:04:29 +12:00
Zhi You Tan 1e80d76acd Retrieve local host ip address and show it on lobby view.
#story[1055] #pair[hyi25, zyt10]
2017-07-20 12:52:53 +12:00
Calum 360c55fdb9 Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/controllers/GameViewController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
2017-07-20 12:20:12 +12:00
Peter Galloway 176d65e0b2 Merge branch 'Story62_Reading_Keystrokes' into Story62_Creating_Game_Loop 2017-07-20 12:01:53 +12:00
William Muir 0c08f5a03c Refactoring to remove all superflous classes related to the server
GameServerThread --> MainServerThread
All server classes consolidated into the gameServer package and all others removed

tags: #story[1055] #refactor #fix
2017-07-20 11:46:06 +12:00
William Muir e257602b78 Merge remote-tracking branch 'origin/Story62_Reading_Keystrokes' into Story62_Reading_Keystrokes
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/gameServer/MainServerThread.java
2017-07-20 11:35:27 +12:00
William Muir 8f00f3a80c Refactoring for server package, Changed GameServerThread to MainServerThread.
All Server classes now in single gameServer package

tags: #story[1055]
2017-07-20 11:22:30 +12:00
Peter Galloway 63d24c001f Added position update in yacht #story[1047] 2017-07-19 19:41:07 +12:00
Peter 67668fe1fc Merge branch 'Story62_Reading_Keystrokes' into Story62_Creating_Game_Loop 2017-07-19 18:13:58 +12:00
Kusal Ekanayake dbbb41e12f Multiple clients are now shown in lobby of host.
When multiple clients connect to the host, the lobby table now fills up with the names of the threads (will need to be changed later). Re-enabled multiple people connecting and the lobby table. Formatting changes in the lobby screen also made.

#story[1047]
2017-07-19 16:05:21 +12:00
Michael Rausch 45053ba507 Added XML Generation
- Implemented a wrapper for Apache Freemake
- Implemented the regatta XML generator

Tags: #story[984]
2017-07-19 14:47:16 +12:00
Calum d9f5f7a137 Refactoring view for game development
#story[987]
2017-07-18 14:00:24 +12:00
William Muir b301ce5d27 fixed build (i think?)
tags: #story[1055] pair[wmu16, zyt10]
2017-07-18 12:26:47 +12:00
William Muir f02bd3b3f8 Merge remote-tracking branch 'origin/Story62_Reading_Keystrokes' into Story62_Reading_Keystrokes 2017-07-18 12:23:09 +12:00
William Muir e83eaa38e1 Upon hosting, and then creating a new instance and connecting to that IP, button transmissions work and print out on server!! :D
Took the send method out of the Message class as it didnt make sense to have it there. This meant taking it out of all subclasses too

tags: #story[1055] pair[wmu16, zyt10]
2017-07-18 12:22:58 +12:00
Kusal Ekanayake 102b5f3ca1 Added a program icon that I snipped from the logo using photoshop. 2017-07-18 10:46:41 +12:00
Haoming Yin 63958a6717 WIP: Implemented a temporary workaround to send an instance test to client server upon connection.
Still needs reengineering to change socket channels for sending to ouput stream in the message class.
Only client to server "working".

#story[1047] #pair[hyi25, wmu16] #pair[cir27, zyt10]
2017-07-17 17:00:04 +12:00
Haoming Yin 4b8ac32ca9 Merge branch '1047_Hosting_Game' into Story62_Reading_Keystrokes 2017-07-17 10:55:17 +12:00
Haoming Yin 00b29a1890 Merge remote-tracking branch 'origin/develop' into Story62_Reading_Keystrokes 2017-07-17 10:55:08 +12:00
Haoming Yin c7e5f93bc4 Merged GeoUtility and GeometryUtils classes
#story[1047]
2017-07-16 21:58:40 +12:00
Haoming Yin e4d87c91a2 Merge branch 'develop' into 1047_Hosting_Game 2017-07-16 21:54:08 +12:00
Zhi You Tan f84091e54e Removed (fxml) table view and its table column from lobby controller because the table view is removed from LobbyView.fxml
#story[988]
2017-07-14 18:27:54 +12:00
Zhi You Tan e03e8825b2 Merge remote-tracking branch 'origin/1047_Hosting_Game' into Story62_Reading_Keystrokes 2017-07-14 18:23:28 +12:00
Zhi You Tan 355f8543f5 Implemented a more reliable way for keystroke input and added boat action packet type so stream parser is able to read and decode the message appropriately.
#story[988] #pair[hyi25, zyt10]
2017-07-14 18:23:07 +12:00
William Muir 77e7db79cc WIP: Worked on new server thread class that would create and store multiple THREADS of connections. Researched Authorative server structures.
Fixed the current structure of the server to work with the old StreamReciever style and hook up to the Stream Parser

tags: #story[1047] pair[wmu16, mra106]
2017-07-14 17:09:33 +12:00
Zhi You Tan 2809d0d832 Merge branch '1047_Hosting_Game' into Story62_Reading_Keystrokes
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-07-14 16:28:42 +12:00
William Muir 5b908ec355 Merge remote-tracking branch 'origin/1047_Hosting_Game' into 1047_Hosting_Game
# Conflicts:
#	src/main/java/seng302/gameServer/GameServerThread.java
2017-07-13 22:55:49 +12:00
William Muir c480fca72a WIP: Worked on new server thread class that would create and store multiple THREADS of connections. Researched Authorative server structures
tags: #story[1047]
2017-07-13 22:55:03 +12:00
Michael Rausch c19f66a6a4 Added garbage collection for disconnected players
- Heartbeat messages are sent out from their own thread to each player
- If a heartbeat message can't be sent to a player, they are removed from the list of players
- Added equals method for players
Tags: #story[1047]
2017-07-13 22:07:03 +12:00
Michael Rausch 1e6fd1af09 Fixed bug where players were being added to the GameState twice
#story[1047]
2017-07-13 19:27:47 +12:00
Michael Rausch 55db2c9961 Fixed buffer overflow in message header
- Fixed buffer overflow by adding a reset method that  clears the buffer and sets the position to zero before re-writing the header
Tags: #story[1047]
2017-07-13 19:15:45 +12:00
Haoming Yin 6ec8b0c3c5 Terminated the game server socket when click exit lobby button
- the whole server thread should be terminated instead. To be fixed in the future.

#story[1047] #issue[28]
2017-07-13 17:51:25 +12:00
Kusal Ekanayake 78557a4536 Key presses are transmitted to a host (but there is no host currently connected)
#pair[kre39,zyt10] #story[988]
2017-07-13 15:39:48 +12:00
William Muir 8090cd7985 WIP: Adapted the old server thread class to the GameServerThread class to allow multiple clients to connect
tags: #story[1047]  #pair[wmu16]
2017-07-13 14:40:11 +12:00
Kusal Ekanayake 5ce34bed92 Key presses now assigned to enum and empty packet class is constructed.
#pair[kre39,zyt10] #story[988]
2017-07-13 14:35:41 +12:00
William Muir 035841f221 WIP: Adapted the old server thread class to the GameServerThread class to allow multiple clients to connect
tags: #story[1047]  #pair[wmu16, mra106]
2017-07-11 17:03:32 +12:00
Kusal Ekanayake ef61a687d6 Researched and implemented a way for the game to listen for key presses. When one of the valid key controls are pressed, feedback is given in the console. Has yet to be connected to a method which will create and send a message to the server.
#pair[kre39,zyt10] #story[988]
2017-07-11 16:32:15 +12:00
Haoming Yin fcb1e5e593 Removed unnecessary Position and GeoPoint classes to clear the code base.
- put utility classes in a package

#story[1047]
2017-07-10 23:51:01 +12:00
William Muir 752863a0d3 WIP: Created some basic controllers for the UI and started implementing backend for server stuff
Created GameState Class. Static, contains all info about current state of game: list of players and their respective details (coords etc) also Host info

Clients will observe this GameState class

Upon pressing host, new GameState is created and new GameConnectionListener created which listens for connections

Player class created which will be used to take each connection and store it as a player with other info about them regarding the game in the GameState class

tags: #story[1047]  #pair[wmu16, zyt10]
2017-07-10 16:03:13 +12:00
William Muir 1a3e330eb4 Created some basic UI for new start screen (host and connect) and a lobby
tags: #story[1047]  #pair[wmu16, zyt10]
2017-07-10 13:43:25 +12:00
William Muir 5f9da6b40a Fixed the bug where the polar file could not be read after being packaged
tags: #story[955]  #pair[wmu16, zyt10]
2017-07-10 12:36:32 +12:00
Michael Rausch aee62c29fe Merge remote-tracking branch 'origin/develop' into develop 2017-05-26 09:07:22 +12:00
Michael Rausch fe76ef9cdc Merge remote-tracking branch 'origin/develop' 2017-05-25 16:58:01 +12:00
Michael Rausch acbaa838ec Change default port to 4940
#story[956] #pair[wmu16, mra106]
2017-05-25 16:56:41 +12:00
Haoming Yin f4134d83b5 Merge branch 'issue#8_create_finish_screen' into 'develop'
Issue#8 create finish screen

Created a finish screen which user can see position of the final result and able to go back to start screen to rewatch new race

See merge request !41
2017-05-25 16:52:59 +12:00
Zhi You Tan 24cc10e1cd Merge remote-tracking branch 'origin/develop' into issue#8_create_finish_screen
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
2017-05-25 16:51:32 +12:00
Zhi You Tan 20b79b40f2 Fix the button position #story[923] 2017-05-25 16:45:35 +12:00
Zhi You Tan 72e2776b7e Fix the finish screen size #story[923] 2017-05-25 16:41:59 +12:00
Kusal Ekanayake 49e4c92da6 Merge branch 'develop' 2017-05-25 16:40:24 +12:00
Kusal Ekanayake ba761e4951 Merge branch 'remove_observers' into 'develop'
Remove observers

Refactor for annotations and a few improvements here and there. Fix for several tracked issues and numerous other bug fixes.

See merge request !40
2017-05-25 16:36:43 +12:00
Calum c1aa1d8eae merged with dev 2017-05-25 16:32:25 +12:00
Calum 4231c3ccd8 Merge remote-tracking branch 'origin/develop' into remove_observers 2017-05-25 16:30:15 +12:00
Calum 65223ceaaf shifted annotation layers. Merged with dev. 2017-05-25 16:29:27 +12:00
Kusal Ekanayake ca22615c08 Add scroll bar to the side panel to cater for smaller screen. 2017-05-25 16:18:47 +12:00
Zhi You Tan 559a9f38c0 Merge remote-tracking branch 'origin/develop' into issue#8_create_finish_screen
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-25 16:12:44 +12:00
Kusal Ekanayake 762829e5ff Merge branch 'develop' 2017-05-25 15:56:10 +12:00
Kusal Ekanayake 835f79b113 Swapped to the local stream as default 2017-05-25 15:56:02 +12:00
Michael Rausch 3bd8added4 Merge remote-tracking branch 'origin/develop' into develop 2017-05-25 14:56:11 +12:00
Kusal Ekanayake e0854bc68c Merge remote-tracking branch 'origin/master' 2017-05-25 14:55:35 +12:00
Kusal Ekanayake cec7014856 Set axis ons parkline style 2017-05-25 14:54:34 +12:00
Michael Rausch ba527a1979 Merge remote-tracking branch 'origin/develop' into develop 2017-05-25 14:54:28 +12:00
Calum b73e4c89db Merge remote-tracking branch 'origin/develop' into remove_observers
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
#	src/main/java/seng302/fxObjects/MarkGroup.java
#	src/main/java/seng302/models/Yacht.java
2017-05-25 14:52:54 +12:00
Kusal Ekanayake f1a9da83fc Quick fixes before merge 2017-05-25 14:22:01 +12:00
Kusal Ekanayake 6e903bfbed Merge branch 'develop' into 38b_LayLines
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/models/Yacht.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-25 14:16:55 +12:00
Michael Rausch 23d62f552e Merged develop onto this branch
Tidied code, removed print statements

#story[956] #pair[wmu16, mra106]
2017-05-25 14:06:33 +12:00
Kusal Ekanayake 53f6a6b8c5 Implimented a new way to determine positions
#story[952]
2017-05-25 14:06:33 +12:00
Michael Rausch 945acb6071 Merge remote-tracking branch 'origin/develop' into develop 2017-05-25 13:57:07 +12:00
Michael Rausch af81bf5891 Merge remote-tracking branch 'origin/develop' into 38b_LayLines
# Conflicts:
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Event.java
#	src/main/java/seng302/models/mark/Mark.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-25 13:56:08 +12:00
Michael Rausch e72ac1def8 Laylines are now togglable. Still not completely fool proof
Laylines appear only for the selected boat. The dissapear if the boat is not seleccted. Laylines are the colour of the boat

Laylines do not yet automatically update for a selected boat when it passes a mark

There is still a polling interval of a second in which if you select a boat after it has just passed a mark it still thinks the next mark is that mark and laylines are displayed incorrectly

#story[956] #pair[wmu16, mra106]
2017-05-25 13:48:48 +12:00
Zhi You Tan f163dfdd11 Removed a print statement 2017-05-25 13:14:36 +12:00
Zhi You Tan a1eda8d91d Fix boatPos in StreamParser.java being empty and affecting all the assessors 2017-05-25 12:58:11 +12:00
Zhi You Tan 5ed02a1fe1 Merge remote-tracking branch 'origin/develop' into issue#8_create_finish_screen
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
2017-05-25 12:32:10 +12:00
Zhi You Tan 66d4a4b958 Fix finish screen showing wrong ("-" as) position 2017-05-25 12:28:13 +12:00
Calum adbb9ffe3b Removed completed TODO request. 2017-05-25 10:31:37 +12:00
Calum 0cd2867ac0 Made annotations more readable when overlapping. 2017-05-25 10:30:01 +12:00
Calum 4d29354797 Merge branch 'develop' into remove_observers
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-25 10:25:54 +12:00
Calum a6d9c66fc9 Removed unused import statements and class variables. Removed non error print statements. 2017-05-25 10:20:39 +12:00
William Muir 99588c7ff8 Laylines now working upon selecting a boat. Still need to make their visibility togglable
Currently just paints upon clicking

tags: #story[956]
2017-05-25 09:54:00 +12:00
William Muir 68c3e3e999 Merged develop onto this branchk
tags: #story[956]
2017-05-25 09:21:47 +12:00
William Muir 67b5650288 Merge branch 'develop' into 38b_LayLines
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Yacht.java
2017-05-25 09:19:42 +12:00
Calum 3085125f3e Merged with develop 2017-05-25 01:21:40 +12:00
Calum 5e26ad7c36 Merge remote-tracking branch 'origin/develop' into remove_observers
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Yacht.java
2017-05-25 01:12:10 +12:00
Calum 765ea06c3b Several tweaks and improvements to a annotations and other visual aspects of them program. Fixed bug causing minimization crashes.
#implement #refactor #issue[23]
2017-05-25 01:02:33 +12:00
William Muir ffe70a8313 Trying to get laylines to display on proper respective sides of the gate
Having a great time here, Not quite working. There was an issue where the laylines are crossed and I think its tied to the fact that sometimes the two seperate points of the gate mark appear to have the same x y coordinate so the line function doesnt work? idk

tags: #story[956] #pair[wmu16]
2017-05-25 00:22:15 +12:00
Haoming Yin 331e0fc6ab Merge branch 'Story34_Sparklines' into 'develop'
Story34 sparklines



See merge request !38
2017-05-24 22:24:50 +12:00
Kusal Ekanayake b3fd735f5c Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
2017-05-24 21:50:15 +12:00
William Muir 3cbbdb070f Basic implementation for mapping windspeed to draw a polar on a gate complete
Created functionality to grab the closest windspeed value to map to VMG values based off the current wind speed in the Polar Table

Created new RaceXML mark object which contains ALL marks for purposes of sequencing

Displaying correct (?) polars for one point only on a gate

Created functionality to receive leg data for each boat and then map that to the next gate. This may only work for the current race due to a slight fudge factor

Created functionality to receive wind speed

tags: #story[956] #pair[wmu16, mra106]
2017-05-24 20:31:07 +12:00
Haoming Yin d2e55bf964 Removed the FPS background, and lowered the update frequency so it doesn't flip too fast.
#story[923]
2017-05-24 19:33:24 +12:00
Kusal Ekanayake 05cdadac79 Merge remote-tracking branch 'origin/Story34_Sparklines' into Story34_Sparklines 2017-05-24 18:57:09 +12:00
Kusal Ekanayake e6aed88188 Merge with develop.
#story[952]
2017-05-24 18:56:56 +12:00
Kusal Ekanayake d032314ddb Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Yacht.java
2017-05-24 18:56:04 +12:00
Peter Galloway 6301fd2fb7 Merge branch 'issue#5_fix_pre-race_boats_on_leaderboard' into 'develop'
Issue#5 fix pre race boats on leaderboard

Updated leaderboard so it only shows competing boats and pre-race no longer shows only one boat

See merge request !36
2017-05-24 18:52:37 +12:00
Kusal Ekanayake 34c3899ec4 Removed unused css for charts 2017-05-24 18:51:52 +12:00
Kusal Ekanayake 46f5fc5172 Merge with develop.
#story[952]
2017-05-24 18:47:27 +12:00
Kusal Ekanayake 81c021b59a Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/resources/views/RaceView.fxml
2017-05-24 18:45:01 +12:00
Kusal Ekanayake 8ab57e4e61 Cleaned up code for review.
#story[952]
2017-05-24 18:43:50 +12:00
Calum 14a7305a2d Made FPS marker not draw on canvas.
Have to revert the use of the observer pattern since there is no time to change how and when data is passed.
2017-05-24 17:17:06 +12:00
Zhi You Tan 13ff179840 Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard 2017-05-24 16:18:33 +12:00
Zhi You Tan e7060d4b6f Finish screen will show when race finishes, added functionality to return to start screen when in finish screen, updated finishScreenView.fxml to have controller and also four corner anchors to fit to parent 2017-05-24 16:17:56 +12:00
Zhi You Tan 641039720e Merge remote-tracking branch 'origin/develop' into issue#8_create_finish_screen 2017-05-24 16:12:37 +12:00
Michael Rausch 6f132f1e38 Merge remote-tracking branch 'origin/develop' into develop 2017-05-24 15:22:30 +12:00
Michael Rausch 76c0d34760 Merge branch '37-display_map_on_canvas' into 'develop'
37 display map on canvas

Merge request for story #928

See merge request !37
2017-05-24 15:05:39 +12:00
Michael Rausch 71637d7286 Removed dead code from canvas controller and canvas map
Tags: #story[928]
2017-05-24 15:04:12 +12:00
William Muir 1cac7cc189 Calculation of upwind downwind leg given a boat, wind and next mark now works. created GeometryUtils class
Can accurately calculate if a boat is going upwind or downward using a line function for the wind vector from the gate and the boat position from the gate.

Requires knowledge of the next mark which requires the boat to have passed a mark. This could be fixed by extracting the leg number from the race status packet and mapping these to gates in an initalisation step

tags: #story[956]
2017-05-24 14:57:22 +12:00
Haoming Yin c42942430f Fixed bug that grid panel pushes annotation panel up out of window.
#story[928]
2017-05-24 14:57:06 +12:00
Kusal Ekanayake 7abb36c362 The axis is labeled correctly and is scaled accordingly.
The sparkline should work from the start of a race now also. We now have the boat in first place listed at the top along with the axis labeling and marking matching the position (eg, 1st at top. 6th at bottom). Boats picked up on the visualiser park way through the race are able to added and drawn onto the sparkline.

#story[952]
2017-05-24 14:42:41 +12:00
Michael Rausch a1e8d29b9c Merge remote-tracking branch 'origin/develop' into develop 2017-05-24 14:36:04 +12:00
Zhi You Tan c449da2916 [WIP] created finishScreenView.fxml, finishScreenViewController.java 2017-05-24 14:24:14 +12:00
Haoming Yin 3fd0c0a2dd Merge remote-tracking branch 'origin/37-display_map_on_canvas' into 37-display_map_on_canvas
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-24 13:55:42 +12:00
Haoming Yin 397f7d003a Fixed the size of race canvas and race view so that canvas won't be stretched
- canvas view is set to 1280 * 960

#story[928]
2017-05-24 13:54:05 +12:00
Haoming Yin f85d3bf5fe Plugged Canvas map to canvas view controller to display map
- rebase on the latest develop status
- optimised the scaling factor the map to fit the canvas view
- a new image containing map image is displayed under race canvas

#story[928]
2017-05-24 13:52:49 +12:00
Calum d22d758757 Fix issues caused by not updating the time since last mark value frequently. BoatAnnotations now has an update() function that must be called somewhat regularly. 2017-05-24 03:23:02 +12:00
Calum acbde5aad8 Moved boat annotations into their own class. Implemented observer pattern.
Observer pattern appears to have caused issues with updating Text objects.
Made annotations look nicer. Kinda.

#refactor
2017-05-24 03:09:11 +12:00
Haoming Yin 8f93956ff1 Added methods to calculate optimal map size given a geo boundary.
- From zoom level 20 to 1, once find a size that contains the whole boundary, then the size will be used to retrieve map image from google

#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 4fe4ac1079 Added get map size (width and height) method in canvasMap with given boundary
#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 8a2f0a9f45 Added unit tests for Mercator projection class.
- changed its methods to static
- add some documentation for its methods

#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 5cc865f0af Created Mercator projection to convert between Geo location and planar projection point.
- MapGeo and MapPoint encapsulate geo location and planar projection point into classes.

#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 189ba93e64 Created a canvas map class to fetch map image from google
- also added Bound class to encapsulate map boundary.
- created TestMapView and its controller just for testing.

#story[928]
2017-05-24 00:49:03 +12:00
William Muir 89464e033e Initial work on calculating when a gate is upwind or downwind
Need to know if a gate is upwind or downwind to use the appropriate polar table

Currently calculate the angle between the next mark and the vector of the current mark to the wind, if this angle is less than 90 degrees than the next mark should be down wind

Pretty poor implementation currently, just prototype

Doesn't appear to be working as intended currently. Just a prototype for how we could implement further

tags: #story[956]
2017-05-24 00:08:55 +12:00
Zhi You Tan 6d7c36e31f Updated code to check if boat is in the race before displaying in the leaderboard
#story[923]
2017-05-23 20:00:23 +12:00
Michael Rausch ca8ea03870 Added method in mark group to draw lay lines, also added next mark to Yacht class.
#story[956]
2017-05-23 18:54:42 +12:00
Kusal Ekanayake 3f57adb9cf Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/resources/views/RaceView.fxml
2017-05-23 14:06:24 +12:00
Zhi You Tan 2686dac62e Fixed Yacht position initialise. TO-DO: check if the boat is participating before showing up on leaderboard. 2017-05-23 11:53:23 +12:00
Zhi You Tan ffd40fef6d Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard
# Conflicts:
#	src/main/java/seng302/controllers/RaceController.java
#	src/main/java/seng302/controllers/StartScreenController.java
2017-05-23 11:31:18 +12:00
William Muir e1b8e19966 Initial work on finding next marks for boats as from mark rounding messages
Marks were very difficult to extend, need large refactor

Marks now have a compound ID as well as their sourceID. They need this compound ID to be identifiable by the
mark rounding messages.

tags: #story[956]  #pair[wmu16, mra106]
2017-05-22 18:54:07 +12:00
William Muir dec742cf54 Merge remote-tracking branch 'origin/develop' into 38b_LayLines 2017-05-22 18:15:58 +12:00
Zhi You Tan 6f1b0b06c3 Fixed est time to next mark and time from last mark annotation update after commit e0750f53 in BoatGroup.java
#story[924] #story[927]
2017-05-22 16:17:52 +12:00
Michael Rausch 408d70c420 Merge branch 'issue#10_unifying_marks' into 'develop'
Issue#10 unifying marks

Marks work properly, reading boat position packets and moving.

First and last marks are colored green and red respectively.

See merge request !35
2017-05-22 16:07:50 +12:00
Alistair McIntyre e51c966969 Made sure only boats in participant list are replaced. Also removed mark coloring as it wasn't as accurate as was necessary.
#story[923]
2017-05-22 16:05:24 +12:00
William Muir 08eacacfd4 Polar table is now parsed form the stored file in resources on startup in main
story[956]
2017-05-22 15:45:16 +12:00
William Muir 87150b3c72 Merge remote-tracking branch 'origin/develop' into 38b_LayLines 2017-05-22 15:40:11 +12:00
William Muir e385ac5c09 Initial work on static parser class for polar files
story[956]
2017-05-22 15:39:53 +12:00
Alistair McIntyre c30629542b Merged develop into issue10 branch. Fixed merge issues and completed manual testing before merge request.
#story[923]
2017-05-22 15:27:23 +12:00
Alistair McIntyre 3f9fa24c69 Merge branch 'develop' into issue#10_unifying_marks
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
2017-05-22 15:13:48 +12:00
Alistair McIntyre 78573fa837 Made start/finish lines a different color.
#story[923]
2017-05-22 15:10:05 +12:00
Michael Rausch d4837cacda Merge branch 'Issue#4_boat_movement' into 'develop'
Issue#4 boat movement

This merge will break several things:
1 boat wakes and trails will be the most effective and will have to be manually re-implemented in the re-engineered boatgroup
2 boat annotations in Raceviewcontroller have been effected
3 boat selection (on click) in Raceviewcontroller has been effected

See merge request !34
2017-05-22 15:03:11 +12:00
Peter Galloway 0367805f0f fixed bug from merge where est time to next mark was null on some boats #story[923] 2017-05-22 15:01:04 +12:00
Peter Galloway e0750f5341 Merge branch 'develop' into Issue#4_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Race.java
2017-05-22 14:56:08 +12:00
Calum 80528c9c42 fixed bug in canvas controller
#bug
2017-05-22 14:30:31 +12:00
Michael Rausch e26f2af93d Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-22 13:30:48 +12:00
alistairjmcintyre be633c0e60 Marks display correctly on the canvas, no double ups or anything like that left.
#[issue10]
2017-05-22 13:22:55 +12:00
cir27 97f1ccb6c1 Fixed a bug where an error was caused when attempting to flip the race around the prime meridian.
All x values are now flipped horizontally if the difference between the smallest lon, e.g. -179.5 and the largest e.g. 179.5 is greater than 180.

#story[923] #bug
2017-05-22 05:41:57 +12:00
cir27 68a243725b Fixed a bug where an error was caused when attempting to flip the race around the prime meridian.
All x values are now flipped horizontally if the difference between the smallest lon, e.g. -179.5 and the largest e.g. 179.5 is greater than 180.

#story[923] #bug
2017-05-22 05:39:36 +12:00
Kusal Ekanayake 8f81060a18 Managed to make the sparklines coordinate with their colour.
However the symbols had to be removed. The sparkline can also be intialised at any time now but this might need to be tested further to ensure that it works.

#story[952]
2017-05-21 19:49:11 +12:00
Kusal Ekanayake 07c76f12e1 Merge branch 'develop' into Story34_Sparklines 2017-05-21 17:37:41 +12:00
cir27 c6ab96a86f Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-21 16:05:37 +12:00
Kusal Ekanayake 059c0de1fa Quickfix for null pointer in boatObject
#pair[ptg19,kre39]
2017-05-21 15:58:17 +12:00
Kusal Ekanayake 8e147bd1bd Making the boat sparkline initialise after the race screen is shown.
#story[952]
2017-05-21 14:56:05 +12:00
Michael Rausch 4d3cfe71f7 Merge branch 'develop' into Issue#4_boat_movement
Conflicts:
	src/main/java/seng302/models/BoatGroup.java
	src/main/java/seng302/models/Wake.java
2017-05-19 21:40:16 +12:00
Michael Rausch 5adb7c3762 Fixed failing ColorsTest
#story[923]
2017-05-19 20:57:40 +12:00
Michael Rausch 937b309b07 Added boat trails to the boat group, fixed annotations
- Set colours for the annotations
- Added boat trails back into the boat group
- Re-added time until next mark and time since last mark rounding

#story[923]
2017-05-19 20:55:02 +12:00
Kusal Ekanayake 48d58ea660 Sparklines now update when a boat passes a mark.
There are issue of initialising the sparkline before the race starts and when the race is being viewed when it starts. It works great though when you join a race halfway.
#story[952]
2017-05-19 18:08:26 +12:00
Michael Rausch aaf2e6a3f0 Added boat wakes back to the visualiser
#story[923]
2017-05-19 16:57:29 +12:00
Kusal Ekanayake 422dcd4501 Sparklines now update with boat position change.
Need to make it work for any amount of boats in the race and to work when the race first starts as it currently only shows one boat.
#story[952]
2017-05-19 15:15:05 +12:00
Kusal Ekanayake 951a726309 The line chart now has series which display on it.
Need to get the right data to display on the linechart when it updates.

#story[952]
2017-05-18 20:56:42 +12:00
Alistair McIntyre b692ddcbe6 Marks and gates correctly displaying from XML messages and mark(boat) position packets. Will need a little polishing but marks and gates are moving around.
#story[923] #pair[ajm412, ptg19]
2017-05-18 18:39:23 +12:00
Alistair McIntyre 5d6b356602 all marks displaying except currently gate marks are displaying as a single dot
#story[923] #pair[ajm412, ptg19]
2017-05-18 18:02:25 +12:00
Alistair McIntyre 08057edb28 Merge branch 'Issue#4_boat_movement' into issue#10_unifying_marks
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/mark/MarkGroup.java
2017-05-18 17:04:00 +12:00
Kusal Ekanayake 390aabc78f Adding race yacht series to the sparkline.
Next step is to make the series of the boats update when new position information is received.

#story[952]
2017-05-18 14:44:10 +12:00
William Muir a2123df0c5 Fixed Boat selection and lost annotations (Estimated and Leg Time)
Trails and Wakes, however,  are still not re implemented yet.

story[923]
2017-05-18 14:41:08 +12:00
Michael Rausch 6a6ed3ed44 Server sends mark locations to test
- Added a timer to send boat location messages containing the mark locations to test the receiver

#story[891]
2017-05-18 13:32:24 +12:00
Kusal Ekanayake b87008f590 Merge branch 'develop' into Story34_Sparklines 2017-05-18 13:22:51 +12:00
Kusal Ekanayake ecc0e722b5 Started sparkline prototype
#story[952]
2017-05-18 13:19:59 +12:00
Zhi You Tan 6f9a8e5581 Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard 2017-05-18 12:20:02 +12:00
Zhi You Tan b17bba3629 Fixed leaderboard on start screen and race view to show all boats correctly during pre-race.
#story[923]
2017-05-18 12:18:51 +12:00
Michael Rausch cf4d7e03f5 Merge branch 'issue#1_wakes_3.0' into 'develop'
Issue#1 wakes 3.0

Changed methods in the wake class to fix issues caused by unexpected velocities making the wakes go crazy.
Improved performance.
Made wakes look different.

See merge request !33
2017-05-18 11:45:28 +12:00
Michael Rausch 73eeeb0ef9 Removed extra print stmt and changed default server back to official 2017-05-18 11:45:06 +12:00
Peter Galloway 0f79353936 fixes from merge #story[923] 2017-05-17 20:15:47 +12:00
Peter Galloway 38b44fa92b Merge branch 'develop' into Issue#4_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/mark/MarkGroup.java
#	src/main/java/seng302/models/stream/StreamParser.java
#	src/test/java/seng302/models/stream/StreamReceiverTest.java
2017-05-17 20:05:40 +12:00
alistairjmcintyre 3fd13ddc0a Adjusted XMLParser to use model Mark objects rather than the simple datatype that existed in the XMLParser previously. Began attempting to implement them into the canvas controller but have issues
#[issue10]
2017-05-17 19:46:05 +12:00
Peter Galloway 2e375978bd cleaned up code for merging back to develop #story[923] 2017-05-17 19:18:31 +12:00
Calum 45db731a60 Fixed a merge issue.
#story[923] #bug #refactor
2017-05-17 18:06:36 +12:00
Peter Galloway 95e353c14e fixed initialization bug #story[923] 2017-05-17 17:55:21 +12:00
Calum 8a3a41294a Fixed error causing build failure.
#story[923] #bug #refactor
2017-05-17 17:41:45 +12:00
Calum f41858e2c7 Tidied variable names. 2017-05-17 17:35:59 +12:00
Peter Galloway aaa3dc93f1 boat movement working reliably now, still need to fix one bug at initialization #story[923] 2017-05-17 17:34:47 +12:00
Calum e5eab0a6c8 Merge remote-tracking branch 'origin/develop' into issue#1_wakes_3.0
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/models/BoatGroup.java
2017-05-17 17:27:34 +12:00
Calum 7c39368126 Removed unnecessary constant 2017-05-17 17:23:10 +12:00
Calum ade926e2f2 StreamParser class now only shares thread safe classes.
#story[923] #bug
2017-05-17 16:44:05 +12:00
Calum c63c8e4d73 Stripped out excess code in Wake class. 2017-05-17 16:33:45 +12:00
Michael Rausch 3c418b2aa4 Changed boat location message to milliseconds 2017-05-17 14:57:31 +12:00
Michael Rausch d34a158c34 Merge branch '38a_Select_Boats' into 'develop'
38a select boats

Selection of boats now possible by selecting a boat from a drop down menu to focus that boat singularly or clicking a boat on the canvas to toggle that boats annotations.

Changed the annotations slider to remove the low annotation option. Only now includes None, Important and All. I think this makes much more sense.

Refactor of the race view controller class to combine all timelines for polling the Stream Parser to update race view data such as tables into one timeline for polling. In the future this should be improved further from polling to an observer model.

See merge request !31
2017-05-17 14:38:23 +12:00
Michael Rausch 6e3d037021 Merge branch 'develop' into 38a_Select_Boats 2017-05-17 14:34:10 +12:00
Zhi You Tan 7d160eaf54 Removed unused imports from controller.java 2017-05-17 13:16:00 +12:00
Peter Galloway 4fc99edbd6 Merge branch 'issue#14_split_start_screen_view&controller' into 'develop'
Split Start Screen out from Main View view and controller.

The start screen has its own view and controller now. Returned main view to its original function.

(This solves issue#14)

See merge request !32
2017-05-17 13:07:48 +12:00
Zhi You Tan 4da8c1645e Split Start Screen out from Main View view and controller.
#story[923]
2017-05-17 12:56:13 +12:00
William Muir 1c01aab1e7 Merge on to develop
story[955]
2017-05-17 10:34:00 +12:00
William Muir 6a85b0800f Merge on to develop
story[955]
2017-05-17 10:06:50 +12:00
William Muir 1acb0fbac4 Merge on to develop
story[955]
2017-05-17 10:04:28 +12:00
William Muir 2b294702a9 Merge remote-tracking branch 'origin/develop' into 38a_Select_Boats
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/BoatGroup.java
2017-05-17 09:53:18 +12:00
William Muir afd97d6e05 Reformating of code to google style.
#story[955]
2017-05-17 00:34:36 +12:00
William Muir 2d5a7a8a49 Large tidying of RaceViewController class. Fixing updating for combo boxes
#story[955]
2017-05-16 15:06:01 +12:00
Peter Galloway 9e3036e134 Stripped down the boatgroup to the basic movement components and then tried to adjust how position updates are being dealt with to make everything more logically understandable. I made some progress in terms of understanding but the position update is still not as reliable as I would like. I will be explaining to other team members how this part of the code is working so the time I have spent is not completely wasted #story[923] 2017-05-15 23:17:36 +12:00
Haoming Yin 8dec458ba9 Added methods to calculate optimal map size given a geo boundary.
- From zoom level 20 to 1, once find a size that contains the whole boundary, then the size will be used to retrieve map image from google

#story[928]
2017-05-15 19:57:23 +12:00
Michael Rausch da07d885da Merge branch 'Story47CourseLimits' into 'develop'
Story47 course limits

The idea was to have the course canvas update whenever we receive a new xml packet for the race data. Specifically for a few seconds after the race where the course boundaries change, going from a boundary which contains the start line to cutting off the start line as soon as the boats start.

See merge request !30
2017-05-15 18:43:49 +12:00
Michael Rausch a9de005e1a Removed a extraneous print stmt 2017-05-15 18:41:44 +12:00
Michael Rausch e03e121da4 Merge branch 'develop' into Story47CourseLimits
Conflicts:
	src/main/java/seng302/App.java
2017-05-15 18:37:31 +12:00
Peter Galloway fc3ca70e5d Merge branch 'issue#10_unifying_marks' into Issue#4_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/stream/StreamPacket.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-15 18:09:47 +12:00
Kusal Ekanayake 9c7144c918 Removed dud print statement
#story[889]
2017-05-15 17:55:06 +12:00
Peter Galloway ed8d70c3b3 commented out buffered code and cleaned up some other areas to try and make the boatgroup clear enough to modify #story[923] 2017-05-15 17:53:11 +12:00
Kusal Ekanayake fa501460cb Cleaned up code for merge.
#story[889]
2017-05-15 17:21:56 +12:00
Kusal Ekanayake 110143ae6e Added a dynamically updating course limits file to the model.
This will make manual testing of the course limits easier. When the race starts, the limits encapsulate the start line but soon after the race starts the course limits close off around the start line (this is for the model). This means the model acts in a very similar way to the actual live stream.

#story[889]
2017-05-15 17:09:12 +12:00
Haoming Yin eda3d76077 Added get map size (width and height) method in canvasMap with given boundary
#story[928]
2017-05-15 17:06:28 +12:00
William Muir 51f090324a CSS on annotation selection and minor bug fixes. Changed annotation slider to only have None, important and All
#story[955]
2017-05-15 16:44:28 +12:00
Michael Rausch 335540ff4a Merge branch 'story36_elapsed_time' into 'develop'
Story36 elapsed time

Implemented time elapsed since last mark on annotation

See merge request !29
2017-05-15 16:33:21 +12:00
Zhi You Tan 5fa47ff65b Implemented elapsed time since last mark on annotation.
#story[927]
2017-05-15 16:31:04 +12:00
Zhi You Tan 3a1c1a5e43 Merge remote-tracking branch 'origin/develop' into story36_elapsed_time
# Conflicts:
#	src/main/java/seng302/models/BoatGroup.java
2017-05-15 15:54:44 +12:00
Michael Rausch 4c7f530458 Merge branch 'story33_add_estimated_time_annotation' into 'develop'
Story33 add estimated time annotation

Added estimated time to next mark to annotation

See merge request !28
2017-05-15 15:50:46 +12:00
Zhi You Tan 2e914a7704 Fixed estimate time to next mark to match acceptance criteria which is countdown in minutes and seconds.
#story[924]
2017-05-15 15:49:21 +12:00
Calum 8fbb9d6d4e Added/improved documentation
#chore
2017-05-15 15:36:18 +12:00
Calum 23bc643c91 Removed some unused functions and imports caused by code refactor.
#chore
2017-05-15 15:20:21 +12:00
Calum c4fe116267 MarkGroups refactored to be independent of BoatGroups as their functionality has diverged.
#issue[10] #refactor
2017-05-15 15:17:54 +12:00
Kusal Ekanayake 081d7e3dcb Fixed the parsing of bytes being off by one byte in the stream parser fr the race status packet.
#story[924] #pair[kre39,zyt10]
2017-05-15 14:52:57 +12:00
William Muir 764ae37ce4 Gave the boatgroups a selection attribute, allowing them to be highlighted upon clicking
Boats can be clicked on canvas or from selection drop down on the side

#story[955]
2017-05-15 14:09:09 +12:00
Zhi You Tan e62a609b6b Prepared StreamParser.java, BoatGroup.java, Yacht.java for leg timer annotation (story 36)
#story[927]
2017-05-15 13:47:30 +12:00
Haoming Yin 4b1a4aae87 Added unit tests for Mercator projection class.
- changed its methods to static
- add some documentation for its methods

#story[928]
2017-05-15 13:23:04 +12:00
Peter Galloway ccda5f2a2e changing branch name #story[923] 2017-05-15 12:40:11 +12:00
Haoming Yin 3fd8b1b855 Created Mercator projection to convert between Geo location and planar projection point.
- MapGeo and MapPoint encapsulate geo location and planar projection point into classes.

#story[928]
2017-05-15 12:24:36 +12:00
Kusal Ekanayake 94d1982670 Shifted the canvas to the back of the window when updated.
This is to preserve the way the boats are still shown on the front when the course i reloaded.
As the main live stream has been down, this still needs to be tested.
2017-05-15 10:46:28 +12:00
Zhi You Tan 39efafc75f Fixed Annotation.java, ImportantAnnotationController.java, RaceViewController.java to include estimate time to next mark after merge
#story[924]
2017-05-15 10:36:20 +12:00
Kusal Ekanayake afe0c9f1a6 Merge branch 'develop' into Story47CourseLimits
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-05-15 10:33:22 +12:00
Zhi You Tan 04b105d74b Merge branch 'develop' into story33_add_estimated_time_annotation
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
2017-05-15 10:19:58 +12:00
Zhi You Tan 1ab6351d48 Fixed estimated time to next mark (annotation) to update correctly
#story[924]
2017-05-15 10:11:19 +12:00
Michael Rausch 9c348df5a5 Merge branch 'merge_request_test' into 'master'
Added .codeclimate.yml for static code analysis



See merge request !26
2017-05-15 01:28:57 +12:00
Peter Galloway 256ec046fc adjusted givePointsXY to try and use the marks from the XML parser, stopped partway as the marks from the XML parser and the standard marks need to be merged before this can be fixed properly #story[923] 2017-05-14 22:12:16 +12:00
Peter Galloway 85d4d63287 Merge branch 'story35_create_annotation_view' into 'develop'
Story35 create annotation view

## Addresses issue #9 and story 35

# Change Log
* Styled application using CSS (Issue #9)
* User can select to only show important annotations using the annotation slider
* Added view to select important annotations
* Important annotations are updated in real-time

# Testing
* Unit tests in test/java/seng302/visualizer/annotations/*
* Controllers have been tested manually

# Acceptance Criteria

* There is a mechanism to chose the ‘important’ annotations while viewing a race.

* Any number of annotations (from zero to the number of annotations) can be chosen.

* If the partial annotations toggle is active, choosing an annotation makes that annotation immediately visible for all boats, and deselecting an annotation should likewise remove it from all boats.

* When the partial annotations toggle becomes active, all annotations chosen as ‘important’, and only those annotations, should be shown.

See merge request !27
2017-05-14 22:00:02 +12:00
Michael Rausch 213d36ed56 Merge remote-tracking branch 'origin/develop' into story35_create_annotation_view 2017-05-14 21:05:48 +12:00
Michael Rausch ff6bfc9516 Important annotations are displayed correctly when updated.
- Important annotations can be selected, the live view updates in real time
- Change local time text colour to white
- Split the important annotations state into a separate class

Tags: #story[926]
2017-05-14 20:27:25 +12:00
Peter Galloway ae5678482b changed package "parsers" to be called "stream" #story[923] 2017-05-14 20:26:49 +12:00
Peter Galloway 63514cfafb deleted duplicated packet type class #story[923] 2017-05-14 20:20:14 +12:00
Peter Galloway 03ca60f2e1 deleted a whole bunch of legacy code, primarily the old controllers and old parsers #story[923] 2017-05-14 20:12:35 +12:00
Calum 89ef6e5277 Wake calculation now changed to be based off of the separation between wakes.
This allows wakes to auto correct their position better and stops the system reliance on "realistic data".
Wakes have several options for behaviour until the ideal settings are decided upon.
Note that MarkGroup position updating is currently disabled.

#implement #refactor #issue[1] #story[923]
2017-05-14 17:24:15 +12:00
Zhi You Tan 47880d09bc Implemented estimated time to next mark. Added checkbox for estimated time to next mark. To be fix: change from long to human readable time, update time every second
#story[923]
2017-05-12 20:04:09 +12:00
Zhi You Tan 5472765b95 Removed unused methods in RaceViewController.java
#story[923]
2017-05-12 18:24:28 +12:00
Zhi You Tan 14d975dce4 Fixed a bug where clicking "watch race" before race starts will cause boats to travel in unexpected ways.
#story[923]
2017-05-12 17:41:47 +12:00
Michael Rausch a23bdd0c53 Added functionality to select important annotations and ui changes
- Added CSS for the race view & welcome screen
- Added view to select important annotations
- Annotations are enabled/disabled when the user changes the important annotations

Tags: #story[926]
2017-05-11 21:44:56 +12:00
cir27 b0d8c3db0a Merge remote-tracking branch 'origin/develop' into develop 2017-05-11 16:40:50 +12:00
Kusal Ekanayake 4a75c062ce Making the course limits change when a new xml packet is received..
#story[889]
2017-05-11 15:49:49 +12:00
Michael Rausch fe90a3bf13 Merge branch 'master' into story35_create_annotation_view 2017-05-11 13:43:02 +12:00
Michael Rausch 711f6f4c45 Merge branch 'master' into develop 2017-05-11 13:29:51 +12:00
Haoming Yin 8fa7829a3c Created a canvas map class to fetch map image from google
- also added Bound class to encapsulate map boundary.
- created TestMapView and its controller just for testing.

#story[928]
2017-05-10 20:52:46 +12:00
Peter Galloway 6d7697a0eb commented out the problematic buffering functionality #story[923] 2017-05-10 13:58:10 +12:00
Michael Rausch cdd80af27b Added .codeclimate.yml for static code analysis 2017-05-10 11:56:18 +12:00
Zhi You Tan 2cb09b81f8 Updated .mailmap for everyone 2017-05-04 16:53:16 +12:00
Peter Galloway b529d621e9 changed course size to 720x720 2017-05-04 16:42:23 +12:00
William Muir 24667991f1 Merge on to master
Final Push
2017-05-04 14:39:29 +12:00
Zhi You Tan ac3f3bfd55 Added a check if race started, then start screen switches to race view automatically.
#story[572]
2017-05-04 14:32:06 +12:00
William Muir f2c4929656 Merge remote-tracking branch 'origin/master' 2017-05-04 14:22:49 +12:00
William Muir 4c038a823a Merge branch 'master' into develop
# Conflicts:
#	.mailmap
2017-05-04 14:22:20 +12:00
Alistair McIntyre 3080c1bf27 Merge remote-tracking branch 'origin/Story28_GetCorrectTimeZone' into develop
# Conflicts:
#	src/main/java/seng302/controllers/Controller.java
2017-05-04 14:15:05 +12:00
Alistair McIntyre 59809c39ea Began figuring out how to implement XML data in place of mock data.
#story[820] #pair[ajm412, wmu16]
2017-05-04 14:13:03 +12:00
Calum 9b063190ce Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/MarkGroupTest.java
2017-05-04 14:04:52 +12:00
Calum 49f2398669 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-04 14:01:32 +12:00
Calum d7a4d20ceb Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/MarkGroupTest.java
2017-05-04 14:01:19 +12:00
Michael Rausch 0855e268c8 Removed another test that was causing failing due CI not having disply 2017-05-04 14:01:06 +12:00
Calum 49b8d75aea Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/BoatGroupTest.java
2017-05-04 14:00:44 +12:00
Calum b1a9a7845b Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/BoatGroupTest.java
2017-05-04 14:00:24 +12:00
Michael Rausch b1e749bafb Removed test that was failing due to CI not having a display 2017-05-04 13:59:44 +12:00
Michael Rausch f6fc6c0693 Merge remote-tracking branch 'origin/develop' into develop 2017-05-04 13:53:49 +12:00
Calum ad1371bce0 Commented out tests that were causing build failures.
Issue is related to tests failing to get the source code from our project needed to build. They run
fine when run from the IDE.
2017-05-04 13:52:47 +12:00
Michael Rausch e77df0a5dd Changed port number and added standalone flag
- can use -standalone to just run the server
- Changed port number as to not conflict with some web servers
2017-05-04 13:52:36 +12:00
Zhi You Tan 6935bd514e Reimplemented race position in the race view. Removed Boat class and Yacht class is replaced completely. Race position "-" shows properly on start screen. Removed BoatTest and TeamsParserTest. Fixed estimated time till finish on server.
#story[818]
2017-05-04 13:50:06 +12:00
Kusal Ekanayake a4cc5f222c Refactored course boundary to be a shade rather than a line and made the stream parser and stream receiver exit gracefully before the app closes.
#story[820]
2017-05-04 13:29:53 +12:00
Calum a0bb7b85b4 potential fix for build failures 2017-05-04 13:26:29 +12:00
Calum fd8ed92f88 Fix for wakes on internal data
#bug
2017-05-04 13:20:50 +12:00
Calum c7b6261602 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-04 12:46:07 +12:00
Calum 3aefb14faf Added testing. 2017-05-04 12:46:01 +12:00
Michael Rausch 8521b68855 Server thread stops when window has been closed 2017-05-04 12:35:36 +12:00
Michael Rausch e37b244f45 Fixed a bug where boat statuses were being sent incorrectly
- Boats now have an estimated time until finish
- Boat packets are being sent correctly

Tags: #story[829]
2017-05-04 12:27:59 +12:00
Calum 7f40fb6283 Fixed bug left in last commit 2017-05-04 11:00:57 +12:00
Calum 83316f7a17 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/Controller.java
2017-05-04 10:48:06 +12:00
Calum a56e55ae70 Fix for some movement on racestart issues
#bug
2017-05-04 10:38:04 +12:00
Zhi You Tan 07234ee33a Updated start screen team list to show position in race. Created Yacht class to replace Boat class. Removed Boat class from XMLParser. Removed unused BoatParser.java.
#story[572]
2017-05-04 04:16:16 +12:00
Michael Rausch 145d59df45 Removed unused imports 2017-05-04 01:18:41 +12:00
Michael Rausch 7e8c3af9ce Removed extra 'minutes' labels 2017-05-04 01:08:18 +12:00
Michael Rausch aa6ef72670 Merge remote-tracking branch 'origin/develop' into develop 2017-05-03 23:27:35 +12:00
Haoming Yin ed8781b382 Added method to check if a boat is finished the race or not.
- updated server if all boats finish the race, then start sending race finished message.
- race simulator will terminate if all boats finish the race, and prints log.

#story[715]
2017-05-03 22:44:25 +12:00
Michael Rausch 7a5f4e8f8c Removed minutes label from timestamp
Tags: #story[820]
2017-05-03 22:16:03 +12:00
Michael Rausch d992422efd Various bug fixes
- Made canvas fill entire screen
- Made window scale to screens that aren't 1920x1080
- Changed boat speeds in mock so they aren't too fast
- Added command line options to switch server

Tags: #story[829]
2017-05-03 21:56:51 +12:00
Haoming Yin f0d6312fa5 Fix bugs that server doesn't send boat location before and after the race.
- server will sends boat location not only during the race, but also before the race and after all boats have finished the race.
- refactored simulator so that it runs at the begining to send boat location, and if its "isStarted" set to true, then it starts moving the boats.

#story[715]
2017-05-02 22:51:35 +12:00
Zhi You Tan 772ece25a0 Implemented real time race clock on the start screen using data from stream.
#story[594]
2017-05-02 22:39:33 +12:00
Zhi You Tan d063a41ad4 Merge remote-tracking branch 'origin/wake_remake' into develop
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-02 22:09:03 +12:00
William Muir 6d02f05f05 Quick change to stop adding course markers as they are dynamic and add them in the previous way 2017-05-02 21:51:04 +12:00
cir27 3a72409fb8 Merge remote-tracking branch 'origin/develop' into develop 2017-05-02 21:50:30 +12:00
William Muir b3a89279d6 Quick change to stop adding course markers as they are dynamic and add them in the previous way 2017-05-02 21:31:38 +12:00
William Muir 4432ba26e5 Merge remote-tracking branch 'origin/develop' into develop 2017-05-02 21:12:22 +12:00
William Muir fe824a8f71 Finished implementing drawing of border from XML
Border is drawn onto canvas, it is static.

#story[469]
2017-05-02 21:05:53 +12:00
Zhi You Tan 8233b75e05 Fixed the start screen team list after merging. Team list shows boats competing in event again.
#story[572]
2017-05-02 18:52:31 +12:00
Zhi You Tan 3af15b2b95 Updated wind direction on race view controller so it responds to the stream.
#story[818]
2017-05-02 18:02:44 +12:00
cir27 fd092bb7e1 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-02 16:41:49 +12:00
cir27 9c60521d00 Merge branch 'develop' of C:\Users\CJIRWIN\Documents\team-13 with conflicts. 2017-05-02 16:41:40 +12:00
Haoming Yin a4dfcca302 Fixed a boat bearing bug.
- server should send LastPastCorner's heading bearing, instead of headingCorner's bearing

#story[715]
2017-05-02 14:51:44 +12:00
Kusal Ekanayake 04ce6f6103 Removed white behind fps counter 2017-05-02 14:18:40 +12:00
Kusal Ekanayake 178af141f0 Refactoring and documentation
#story[820]
2017-05-02 14:08:25 +12:00
William Muir c5c2d4375d Changed the canvas controller to read marks from the XML as received from the server (PROTOTYPE)
Gates appear to draw in the correct places although a bit jittery for some reason

Boundary marks are a bit sporadic and still need lines drawn in between

Large refactor of canvas controller and XMLParser still required if these two are going to work together well. Currently canvas controller is a bit of a mess.

#story[469]
2017-05-01 23:10:15 +12:00
William Muir 3aa183042f Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 22:00:15 +12:00
William Muir 944755fde1 Merge branch 'wake_remake' of /home/cosc/student/wmu16/Documents/300/SENG302/team-13 with conflicts. 2017-05-01 22:00:12 +12:00
Peter cd78c35bf6 Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 20:37:58 +12:00
Peter Galloway f8d003002b fixing error from merge #story[820] 2017-05-01 20:37:46 +12:00
Peter Galloway 56fab768f3 fixing error from merge #story[820] 2017-05-01 20:37:26 +12:00
Peter Galloway c92744f21c Merge branch 'wake_remake' into develop 2017-05-01 20:35:15 +12:00
Peter Galloway 1f71fd1967 Merge branch 'wake_remake' into Story30b_correcting_boat_movement
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/RaceObject.java
#	src/main/java/seng302/models/mark/MarkGroup.java
#	src/main/java/seng302/models/parsers/StreamParser.java
2017-05-01 20:33:09 +12:00
Peter Galloway 33ae7beeb4 merging with wake remake #story[820] 2017-05-01 20:09:51 +12:00
Peter Galloway 130efa3a51 Merge branch 'wake_remake' into Story30b_correcting_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/parsers/StreamParser.java
2017-05-01 19:09:09 +12:00
Peter Galloway 7df55fc1a3 problems appear to be fixed and the boats are updating properly from the timeValid field of the boat location. #story[820] 2017-05-01 18:55:21 +12:00
Zhi You Tan 3adadcc1e1 Updated the timer to show two decimals for the seconds.
#story[572]
2017-05-01 18:49:58 +12:00
Kusal Ekanayake 5b027a29d8 Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 18:35:56 +12:00
Kusal Ekanayake 2a9d0fb82c Removed 2 bad import statements that halted the build form building. 2017-05-01 18:35:48 +12:00
Zhi You Tan ee6a543f8d Fixed timer separator instead of dot to semicolon, and fixed the timer position in race canvas, and updated the start screen so it does not grow vertically
#story[572]
2017-05-01 18:35:17 +12:00
Kusal Ekanayake d927531354 Removed broken time extracting method and replaced it with currently existing long extractor. Added speed to the setDestination method for the raceObject abstract class.
#story[820]
2017-05-01 18:22:08 +12:00
Haoming Yin 1d47df09eb Merge branch 'merge_branch_front' into wake_remake
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-01 17:46:16 +12:00
Haoming Yin a9709c4f84 Merged the mock server to visualisation.
#story[715] #story[716]
2017-05-01 17:42:07 +12:00
Alistair McIntyre a06806c42d Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 17:35:51 +12:00
Alistair McIntyre 57de058582 Began figuring out how to implement XML data in place of mock data.
#story[820] #pair[ajm412, wmu16]
2017-05-01 17:35:39 +12:00
Haoming Yin 978493853d Merge branch 'Story29' into merge_branch_front
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Boat.java
#	src/main/java/seng302/models/Colors.java
#	src/main/java/seng302/models/Event.java
#	src/main/java/seng302/models/Race.java
#	src/main/java/seng302/models/mark/GateMark.java
#	src/main/java/seng302/models/mark/Mark.java
#	src/main/java/seng302/models/mark/MarkType.java
#	src/main/java/seng302/models/mark/SingleMark.java
#	src/main/java/seng302/models/parsers/CourseParser.java
#	src/main/java/seng302/models/parsers/TeamsParser.java
#	src/main/resources/views/MainView.fxml
#	src/main/resources/views/RaceView.fxml
#	src/test/java/seng302/BoatTest.java
#	src/test/java/seng302/ColorsTest.java
#	src/test/java/seng302/EventTest.java
#	src/test/java/seng302/models/mark/MarkTest.java
#	src/test/java/seng302/models/parsers/CourseParserTest.java
2017-05-01 17:06:17 +12:00
Peter Galloway a5ca9218da Discovered the time valid timestamp in the boat location packet is quite inconsistent and either the stream or my implementation is making the display really buggy. Because the way it was before I changed things is more reliable at the moment, I have decided to wait until our mock stream is merged before continuing this development. #story[820] 2017-05-01 16:56:53 +12:00
Kusal Ekanayake 5fe330bfbb Boat trials and wakes now work with both fast and slow data sets.
Instead of fixed, hard coded thresholds and scale factors dynamically changing values
that scale with the onscreen movement are used to determine how graphical objects
are drawn.

#implement #story[816]
2017-05-01 16:41:58 +12:00
Zhi You Tan c80cff87f7 Updated import statements after merge 2017-05-01 16:02:09 +12:00
Zhi You Tan 9a864cc2bd Merge remote-tracking branch 'origin/Mark_to_MarkGroup' into wake_remake
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Wake.java
#	src/main/java/seng302/models/parsers/StreamParser.java
#	src/main/resources/views/MainView.fxml
2017-05-01 15:59:14 +12:00
Alistair McIntyre c07f13180f Merged Story 30b branch into 30c branch
#story[820]
2017-05-01 15:32:23 +12:00
Alistair McIntyre f672eafd6d Merge remote-tracking branch 'origin/Story30A_InputStreamCheckingHead' into wake_remake
# Conflicts:
#	src/main/java/seng302/models/parsers/StreamPacket.java
#	src/main/java/seng302/models/parsers/StreamParser.java
#	src/main/java/seng302/models/parsers/StreamReceiver.java
2017-05-01 15:27:51 +12:00
Calum 6a361c0d4b Documented some RaceObject classes.
#documentation
2017-05-01 14:30:41 +12:00
alistairjmcintyre b597f010dc Added some missing fields. Added missing getters.
#story[820]
2017-05-01 00:36:10 +12:00
Michael Rausch a670f677e9 Added other email to .mailmap 2017-05-01 00:10:07 +12:00
Michael Rausch a77423b937 Added other email to .mailmap 2017-05-01 00:07:56 +12:00
Michael Rausch 50083a9297 Added tests for reverse and intToBytes
#story[829]
2017-04-30 23:54:32 +12:00
Michael Rausch d5aa430d4a Fixed a method that was converting the timestamp bytes incorrectly
- If an argument is passed to the application, it will use the internal mock server
Tags: #story[820]
2017-04-30 23:41:21 +12:00
Michael Rausch e7f9954970 Removed unneeded files, also fixed heading calculation
Tags #story[829]
2017-04-30 23:29:15 +12:00
William Muir ec57851de2 Created annotation slider to display different levels of annotations
Removed toggleAnnotations method as abstract from race object and made only for boat group as it didnt make sense for markgroup, at least not currently as they have no annotations to show

#story[558]
2017-04-30 23:24:24 +12:00
Calum 0eb767b615 Finished tweaking wakes. Made marker movement smooth.
#implement #story[818]
2017-04-30 22:34:49 +12:00
Calum 45b77c05d4 Improvements to wake 2017-04-30 19:32:29 +12:00
Calum b9900925b8 Fixing wakes, bug caused by attempting to fix a issue with jittery boats actually caused by parser.
#bug
2017-04-30 19:00:07 +12:00
cir27 d94290c58d Merge branch 'Mark_to_MarkGroup' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into Mark_to_MarkGroup 2017-04-30 18:15:24 +12:00
cir27 f50aabff7b Documentation added to graphics classes.
#document
2017-04-30 18:15:16 +12:00
Michael Rausch d07c660eb9 Merge remote-tracking branch 'origin/Story29' into Story29 2017-04-30 17:47:05 +12:00
Michael Rausch 6491efec4c Fixed race status sent in race status messages
#story[829]
2017-04-30 17:46:56 +12:00
Zhi You Tan 25038da2a1 Created BoatsParser.java to parse boats from server boat.xml and created a table on the start screen to display all the teams from server
#story[572]
2017-04-30 17:17:47 +12:00
William Muir 85f461c88c Fixed bug so XML messages are located and sent properly on the server
Imported appache commons to read inputstream to a bytearray

#story[829]
2017-04-30 17:08:06 +12:00
Michael Rausch 1cf55f3e96 Fixed an issue where buffers aren't being sent properly
Tags #Story[829]
2017-04-30 16:16:44 +12:00
Michael Rausch 9a995ddcc1 Boat status changes to finished when a boat finishes the race
Tags: #story[829]
2017-04-30 01:55:49 +12:00
alistairjmcintyre 0b2ef3de00 XML data types are done. Easily navigated for future use. Some documentation has been done, tests aren't yet completed just yet.
#story[820]
2017-04-30 00:52:18 +12:00
Peter 99e50aa7ac Merge remote-tracking branch 'origin/Story30b_correcting_boat_movement' into Story30b_correcting_boat_movement 2017-04-29 20:21:49 +12:00
Peter Galloway a898290c0b partway through fixing boat movement to be updated from the data valid timestamp rather than the data sent timestamp #pair[kre39, ptg19] #story[820] 2017-04-29 20:21:26 +12:00
Kusal Ekanayake 6cbff1097b Fixed error'd tests which were based off old xml data. 2017-04-29 20:16:14 +12:00
Peter Galloway 246083460e partway through fixing boat movement to be updated from the data valid timestamp rather than the data sent timestamp #story[820] 2017-04-29 20:13:34 +12:00
Kusal Ekanayake 1d28334346 Removed the parser from queing packets incorrectly. Used the marker boat location packets to get the live updates of the marker positions and implemented the markers being updated much like the boats currently are. This means the course more closely resembles the actual intended course design.
#story[820]
2017-04-29 20:10:39 +12:00
Michael Rausch 3e97f016d5 Getting boat locations from race simulator & bug fixes
- Boat locations that are generated by the simulator are sent to the client as they happen
- Fixed heading and lat/lon encoding
- Fixed a bug where the header wasn't included in the sent byte stream
- Fixed the format of data as it's sent to the client.
- Data is now sent using a channel
- Removed tests that don't work with channels

Tags: #story[829]
2017-04-29 19:38:21 +12:00
Alistair McIntyre ab0d4634d6 Moved XML parsing to non static class to create objects. Changed the abstraction as using generics in maps lead to more headaches than anything. Still not quite completed. Needs documentation and validation for tags too.
#story[820]
2017-04-29 19:02:30 +12:00
Peter Galloway 1e1e482b79 Added a delay for reading packets from the packet buffer so packets that are recieved out of order have time to order by timestamp in the priority queue #story[820] 2017-04-29 18:56:41 +12:00
cir27 a0624cfef6 Merge branch 'wake_remake' into Mark_to_MarkGroup
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Wake.java
#	src/main/java/seng302/models/parsers/StreamParser.java
#	src/main/resources/views/RaceView.fxml
2017-04-29 14:29:12 +12:00
cir27 02a35b4c02 Reduced the cost of updating wakes. Improved the logic for controlling indices.
#implement #story[820]
2017-04-29 14:20:52 +12:00
cir27 80409c08a6 Removed a graphical object added for testing. 2017-04-29 02:17:55 +12:00
cir27 6149f7be60 Wakes no longer become out of sync with boats after extended periods of time. Added in
a limit to the length of boat trails.

#implement
2017-04-29 02:14:55 +12:00
Calum 474f0ee427 Further work on new wake system. Wakes turn correctly but need to scale with velocity and
eventually desync with the boats. Needs to reset to the boats position on straights.
2017-04-28 23:25:49 +12:00
Zhi You Tan f3ee618900 Fixed broken race timer and timer now using stream timer data.
#story[818]
2017-04-28 21:44:23 +12:00
Zhi You Tan b939086e10 Updated welcome screen to show if race is finished or starting
#story[572]
2017-04-28 21:28:34 +12:00
Zhi You Tan ffdfc24e65 Created a start screen with a timer which shows the race progress
#story[572]
2017-04-28 20:09:17 +12:00
Peter Galloway 07bbd7e06d Added reasonable testing for StreamReciever, further testing would probably need StreamReciever to be rewritten #story[817] 2017-04-28 18:29:35 +12:00
Calum 765f27f987 Starting new wake implementation. 2017-04-28 17:08:08 +12:00
Kusal Ekanayake d204bee55d Started documentation on the stream parser.
#story[820]
2017-04-28 17:01:28 +12:00
Kusal Ekanayake 0f4ad48de0 Fixed and enables the old wakes. Enabled the fps counter by implementing the team-27s fps counter from their code, fixed trails from starting at the start of the startline no matter at what point in the race the stream is connected to (this is means the map starts a lot cleaner). Added live tracked speeds which are taken from the boat location packet. Linked the speeds coming in to their specified boats and allowed the onscreen speed tracker to keep up with the speeds. Linked the current speeds to the wakes so the wakes are redrawn for each change in speed and size to match the speed. Also added the toggle functionality back to the fps counter so they can be toggled on an off.
#story[818]
2017-04-28 16:41:35 +12:00
Alistair McIntyre fe480d5cb6 Finished parsing the Race XML data. Began making some optimizations to hopefully make parsing the Boat Data a quicker and simpler task.
#story[820]
2017-04-28 15:41:12 +12:00
Haoming Yin 8a04a0e5b7 Added documents for Boat, RaceParser and Simulator classes.
#story[828]
2017-04-28 14:53:26 +12:00
Haoming Yin 705a0a2eaf Added document and unit tests for GeoUtility class.
- three methods in GeoUtility have been tested and passed.

#story[828]
2017-04-28 14:34:24 +12:00
Kusal Ekanayake d1289b0de1 Fixed boats moving in the correct heading as according to the stream and attempted to fix the wakes direction.
#story[818]
2017-04-27 18:52:37 +12:00
Alistair McIntyre e1de5e0989 Parsed more course data from XML messages
#story[820]
2017-04-27 18:31:13 +12:00
Alistair McIntyre f5b9160304 Started parsing the different types of XML messages to Map objects so that we can extract the relevant data for the visualizer.
#story[820]
2017-04-27 17:22:46 +12:00
Kusal Ekanayake 0a22812165 Got the live ac35 data working in paralell with the parse and the app. Boats move and the live data works correctly. We need to fix the markers now
#story[818]
2017-04-27 16:18:33 +12:00
Kusal Ekanayake 104fd86179 Got the live ac35 data working in paralell with the parse and the app.
#story[820]
2017-04-27 14:40:26 +12:00
Calum 67a702ffcd Wakes still broken. Implemented dashed lines that track the progress of individual boats.
#implement #story[483]
2017-04-27 13:57:19 +12:00
Calum 65c0e6f77d Fixed markergroup bugs. Improved wakes. Still WIP 2017-04-27 11:58:50 +12:00
cir27 245bd184b4 Mark drawing moved to MarkGroup class. RaceObject and it's sub classes now describe
all functionality required for a on screen object. Improved wakes. Branch currently
untested.

#story[812, 820] #refactor #implement.
2017-04-27 02:44:25 +12:00
Haoming Yin 8c8f253233 Created simulator to generate mock data.
- simulator runs as a background thread and sleep for a given time lapse.
- simulator extends observable, so it can notify all its observers when boats positions have been updated

#story[828]
2017-04-26 22:58:13 +12:00
Haoming Yin 8b8422de3a Renamed course parser to race parser
- because in AC35 spec. race xml file contain course set up and all other general race settings

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 7bf2d4c40e Added Position class to better use GeoUtility.
- mark now inherit from Position

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 2a67f04d15 Create GeoUtility to process all geo calculations.
- calculate distance between two geo positions
- calculate the bearing from one geo position to another
- calculate the new geo position by passing original position, bearing and
distance

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin b2ea8196d5 Fixed a bug of getCourse method as it didn't parse xml correctly.
- a typo 'CompoundmarkID'(should be 'CompoundMarkID') which caused parser failed to parse file.
- add typeOf method in RoundingType to convert strings to types

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 7f38191d03 Rewrote course parser to parse race xml file specified in AC35 spec.
#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin f6b7a3042f Rewrote all kind of marks to fit marks specified in AC35 spec.
- added compound mark
- added corner
- rewrote mark as a single mark
- added rounding type enum

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 3bdc6ce5cc Created a new course parser to parse race xml file specified in AC35 spec.
#story[828]
2017-04-26 22:52:18 +12:00
Michael Rausch bc31987f96 Added Boat location messages to the mock streaming data interface
- Added static methods to convert between binary packed lat/longs and floating point numbers

Tags: #story[829]
2017-04-26 22:38:39 +12:00
cir27 eaff4c5aac Added abstract class for all javafx object that a displayed during race. Began refactoring of mark implementation to be a subclass of the aforementioned abstract class. 2017-04-26 21:16:22 +12:00
Calum 95bafdc0d1 Fixed bug which caused boats to all travel to the same position. 2017-04-26 19:19:03 +12:00
Kusal Ekanayake c776d22941 Linking up course stream with visualiser. Boats moving, and course drawing. Boats however are not moving as intended. Needs to be fixed/looked into.
#story[820] #pair[kre39,cir27]
2017-04-26 18:45:58 +12:00
Calum 749c6b7fef Fixed bugs caused by horizontally scaling maps 2017-04-26 17:18:33 +12:00
Kusal Ekanayake 912c081606 Added marks with the test data coords to the xml
#story[820]
2017-04-26 15:55:51 +12:00
Kusal Ekanayake c73bf7dd3e Started merging received packets from the sample stream and reading lats and lons to move boats from them.
#story[820]
2017-04-26 15:51:15 +12:00
Kusal Ekanayake a3ae015be8 Merge remote-tracking branch 'origin/30b/30c_boat_animation' into StreamReceiverMergeWithVisualiser 2017-04-26 15:06:55 +12:00
Michael Rausch 1f8f1f0f86 Added boat location, and race start messages to the mock data interface
- Added proper support for signed and unsigned types. This includes automatic conversion to the correct data type (long to int, short, or byte).

- Moved code related to adding values into the byte buffer into the abstract Message class

Tags: #story[29]
2017-04-25 21:49:51 +12:00
Kusal Ekanayake 5eebab2748 Completed data extractors for: heartbeat, racestatus, display text msg, race start status, yacht event code, yacht action acode, chatter text, boat location, mark rounding, course wind and average wind. Some of the methods need to be validated but others have been tested. Will now need to link the parses with the model.
#story[820]
2017-04-25 17:57:22 +12:00
Alistair McIntyre 8cbd1cc4aa Added support to import XML packet to XML Document object.
#story[820]
2017-04-25 17:38:26 +12:00
Kusal Ekanayake 00c1a89f58 Fixed error in the switch (missing breaks)
#story[820]
2017-04-25 15:39:28 +12:00
Kusal Ekanayake d51825ffb7 Created separate streams for each different data type to be parsed into so the exact needed data would be able to be extracted.
#story[820]
2017-04-25 15:32:04 +12:00
cir27 42569e6ad7 Changed BoatPolygon is now a group instead of a polygon and is called BoatGroup.
BoatPolygon's functionality was more maintainable and scalable by having it extend Group.

 #story30c
2017-04-25 04:30:44 +12:00
cir27 ef874b4245 Added a transition time to rotational movement.
The aim is to make animations smoother when the boat turns. Unsure if current
 implementation will look good without testing on a datastream.

 #story30c
2017-04-25 03:12:30 +12:00
Calum 46037b5aea Refactored Boat class to better fit the MVC model by moving all GUI parts to BoatPolygon. Changed the way animation works so that it will work with a constantly updated set of lats and lons.
TODO - Change Mark class to no longer store XY pixel data.
TODO - Add in a timer force updates boat position if a packet has not been recieved for a while.

#story30b #story30c #implement #refactor
2017-04-24 23:06:30 +12:00
Michael Rausch 6874f288ee Added Race Status messages to the mock streaming data interface
Tags: #story[29]
2017-04-24 21:53:42 +12:00
Zhi You Tan b6fd90e9d7 Updated .mailmap for Zhi You Tan's information 2017-04-24 18:33:06 +12:00
Peter Galloway f078c34bf9 the stream receiver can now be passed a threadsafe priorityQueue that it will add the packets to as they are received (note the priority queue passed should be initialized with a comparitor for "StreamPacket"s) #story[817] 2017-04-24 18:29:50 +12:00
Peter Galloway c1e4a6156c re-engineered stream receiver to make it cleaner and ready to be used with the rest of the program. #story[817] 2017-04-24 17:38:29 +12:00
Kusal Ekanayake 71e14259f6 Started looking into boat location packets, am able to extract the lats an lons but needs validations. Can also see the device type, timestamp, and sequence number. Code needs to be cleaned up and will need to start looking into the set up packets, specifically the packets containing xml data so the course can be created.
#story[820]
2017-04-24 16:47:41 +12:00
Kusal Ekanayake 403dc480c4 Created packet enum to class packets and started progress on how the packets are read and parsed according to the type of packet.
#story[820]
2017-04-24 15:50:21 +12:00
Kusal Ekanayake 3dc1a7f9c0 StreamPacket class created so that we can store all packets generically. The timestamp has also been extracted and stored with the packet so that in the future we may turn the current ArrayList into a priority que.
#story[817]
2017-04-23 20:14:41 +12:00
Peter Galloway 672194adb4 changed Peter Galloway's name to specified format 2017-04-23 19:04:45 +12:00
Haoming Yin fdb84b6675 Update .mailmap for Haoming Yin's information. 2017-04-23 18:51:36 +12:00
Peter ba352183bf added functionality to check the CRC for the packet. I ran into a lot of trouble with this regarding everything in java being signed by twos compliment #story[817] 2017-04-23 16:53:35 +12:00
Kusal Ekanayake dd480080c9 Updated .mailmap to have the correct username for Kusal Ekanayake. 2017-04-23 14:57:26 +12:00
kre39 4047978ea2 Renamed file to match its functionality more accurately .
#story[30a]
2017-04-23 14:43:18 +12:00
kre39 a649b11bbf Reading relevant information (boats and race related info) from the stream so it can be moved to a parse and turned into objects for the actual race.
#story[30a]
2017-04-23 14:42:10 +12:00
Peter 247560ee43 converted prototype to be reading the stream byte by byte rather than by lines and characters which was very confusing and unreliable. currently extracting message type and payload length. #story[817] 2017-04-22 16:39:03 +12:00
Kusal Ekanayake 50e7ece477 Checking for the header of each packet as the stream parser checks for each byte to see if it matches with the desired header sequence. 2017-04-20 19:17:12 +12:00
Calum b5129c5c80 Moved the canvas drawing implementation from team27's codebase to team13's.
#story30b
2017-04-20 19:06:32 +12:00
Alistair McIntyre 6a27dedd74 Simple test to get stream data 2017-04-20 15:08:50 +12:00
Michael Rausch edc306da22 Created AC35 Streaming server
- Sends heartbeat messages every 5 seconds
- Sends XML at beginning

Tags: #story[29]
2017-04-19 19:05:19 +12:00
Kusal Ekanayake 15ded667fe Started to implement the group over the canvas in the code. Removed basic boat redrawing and timeline and replaced with boats being placed into a group and given coordinates. 2017-04-11 17:46:02 +12:00
William Muir 34872a822b Stripped back codebase to make to create basic model for streaming data
Removed many classes involved with visualisation such as controllers and multiple fxmls. Now there is just one for debugging

Merged in Boat updating pattern from team 27

#story[828]
2017-04-08 17:49:50 +12:00
Michael Rausch 9817fc9093 Fixed JavaDoc errors by adding missing @params 2017-04-04 19:29:05 +12:00
Michael Rausch dde4b2fcba gitlab ci test (passing) 2017-04-04 19:15:41 +12:00
Michael Rausch 623600a8a9 gitlab ci test (failing) 2017-04-04 19:15:01 +12:00
Michael Rausch bff4986242 Gitlab CI Build Test 2017-04-04 19:10:01 +12:00
Michael Rausch c689530068 Gitlab CI Build Test 2017-04-04 19:03:45 +12:00
Haoming Yin 7022be1979 Fixed unit test which failed due to the change of configure file.
#story[445]
2017-03-30 16:16:30 +13:00
Haoming Yin 971a3920a3 Fixed race timer to display real race time, and made race time scalable.
#story[445]
2017-03-30 16:12:01 +13:00
Michael Rausch b252797e9b Removed test case that failed when the config file was changed 2017-03-30 14:48:47 +13:00
Michael Rausch c758afe3e3 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-30 14:46:22 +13:00
Peter f8d3f53158 changed boat speed to a lower value 2017-03-30 14:45:33 +13:00
Michael Rausch 4a8672a20b Updated race timer test to use new controller
Tags:story[455]
2017-03-30 14:44:57 +13:00
Peter 32109e8565 Merge branch 'controller-refacto' into origin/master 2017-03-29 15:03:38 +13:00
Peter 5d6060c690 Merge remote-tracking branch 'origin/master' into controller-refacto 2017-03-29 15:01:38 +13:00
Peter a95d030817 Controllers and Fxml nicely refactored, tests still broken #story[463] 2017-03-29 14:59:37 +13:00
Michael Rausch ffa84c6e87 Removed testNextColor for now as the test seems to be broken.. 2017-03-29 13:28:49 +13:00
Peter 4a6978ff79 Fxml refactored, partway through refactoring controllers (app does not run) #story[463] 2017-03-29 12:58:49 +13:00
Haoming Yin 1497858cc0 Deleted the print exception stack statement to make terminal more tidy.
- the exception is aim to happen, so no need to print it out.

#story[377]
2017-03-28 19:38:57 +13:00
Michael Rausch 2c125d4ce0 Fixed test case for the boat heading #story[466] 2017-03-28 19:25:04 +13:00
Zhi You Tan 8bf5455f42 Updated fxml and canvas controller to display a resizable canvas.
#story[377]
2017-03-27 17:05:49 +13:00
Zhi You Tan 48f7e41905 Merge branch 'master' of /home/cosc/student/zyt10/Documents/[SENG302]/team-13 with conflicts. 2017-03-27 16:52:31 +13:00
Peter b50ac62a4b Merge branch 'add-fps-counter' 2017-03-27 16:47:34 +13:00
Peter 6fc55bb82c Added toggle for fps #story[463] 2017-03-27 16:45:02 +13:00
Peter 65ac864bf2 added count for fps inside handle loop and displayed fps on the canvas #story[463] 2017-03-27 16:34:54 +13:00
Haoming Yin cf6bbdd1f1 Fixed boat heading calculation method to get a correct direction
#story[480] #pair[hyi25, ptg19]
2017-03-27 14:29:03 +13:00
Haoming Yin bbe7cbee8f Use canvas polygon to draw a triangle
#story[480]
2017-03-27 14:26:38 +13:00
Michael Rausch 55ba343426 Merge branch 'change-to-short-names' into 'master'
Changed the team names to their abbreviated versions

- Also halved the size of the wake lines
- Updated tests to support the shorter team names
- Wake lines are now hidden with the other annotations

Tags: #story[23,21]

See merge request !25
2017-03-24 21:01:55 +13:00
Michael Rausch e6ace5fb2f Changed the team names to their abbreviated versions
- Also halved the size of the wake lines
- Updated tests to support the shorter team names
- Wake lines are now hidden with the other annotations

Tags: #story[23,21]
2017-03-24 20:56:52 +13:00
Michael Rausch 550ab59231 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-24 20:45:49 +13:00
Michael Rausch 213303d674 Merge branch 'create-wake-line' 2017-03-24 20:39:21 +13:00
Michael Rausch 74c81eb7b3 Removed a character that was accidentally inserted 2017-03-24 20:37:32 +13:00
Michael Rausch c33586e7f5 Changed test to work with the new getHeading method, and removed an unused import that was causing tests to fail
- There was a com.sun import that we were no longer using that was causing issues

Tags: story[466]
2017-03-24 20:33:53 +13:00
Michael Rausch 5dd5e50738 Implemented wake lines
- Changed heading calculation in event class
- The boats now go to the marker, rather than the center of a gate

Tags: #story[466]
2017-03-24 20:27:17 +13:00
Haoming Yin 304f30ece6 Reformatted and refactored the fileparser to get xml from resource folder
#fix #refactor #story[377] #pair[xyi25, zyt10]
2017-03-24 12:55:11 +13:00
zyt10 e8b1720fee Created a toggle checkbox to show and hide all annotation
#story[477]
2017-03-24 12:15:39 +13:00
Peter e0cea098c1 Merge remote-tracking branch 'origin/master' 2017-03-24 11:52:17 +13:00
Peter 50ab481b18 added knots beside boat name #story[447] 2017-03-24 11:51:07 +13:00
zyt10 d39aacba83 RaceController now uses new parsers & deleted OldApp and OldFileParser
#story[377]
2017-03-24 11:34:34 +13:00
Michael Rausch 798fe4da0e Changed FPS to 30 2017-03-24 11:15:46 +13:00
Peter e19f110f19 Merge branch 'origin/addBoatPositionsInSideBar' 2017-03-24 11:06:03 +13:00
Peter 59a4a74a97 changed boatPositionController to also add velocity to the boat ordering shown in the side bar #story[447] 2017-03-23 22:50:04 +13:00
Peter 590ef557d3 fixed bugs from last commit and updated some tests #story[426] 2017-03-23 22:24:42 +13:00
Peter 7bc5c8f8a4 Merge remote-tracking branch 'refs/remotes/origin/master'
Conflicts:
	src/main/java/seng302/controllers/CanvasController.java
	src/main/java/seng302/models/Boat.java
2017-03-23 22:17:45 +13:00
Peter bf8244ce49 tried to test BoatPositionController but as it's connected to the fxml I am unsure if it is possible to test (test code is commented out) #story[426] 2017-03-23 22:04:25 +13:00
Peter a860cc0ec1 connected up all the javafx components and got team positions displaying on the sidebar #story[426] 2017-03-23 21:21:54 +13:00
Peter bb8c681270 added a markpos value to event for use in displaying the team positions #story[426] 2017-03-23 21:19:39 +13:00
Michael Rausch 9872095e50 Merge branch 'boat-labels' into 'master'
Display team name and speed beside boat

- Also slowed down the AnimationTimer
- Removed the need to scale the canvas

Tags: #implement #story[18] #story[19]

See merge request !23
2017-03-23 18:06:31 +13:00
Michael Rausch 403aaa76ae Display team name and speed beside boat
- Also slowed down the AnimationTimer
- Removed the need to scale the canvas

Tags: #implement #story[18] #story[19]
2017-03-23 18:04:00 +13:00
Peter 8578bc4a5b changed the "sea" color of the canvas to be actually drawn on the canvas rather than the pane behind #story[377] 2017-03-23 16:18:42 +13:00
Peter ed004d1423 Merge branch 'master'
Conflicts:
	src/main/java/seng302/controllers/CanvasController.java
	src/main/resources/RaceView.fxml
2017-03-23 15:16:11 +13:00
Michael Rausch a91b2b4f7e Merge branch 'create-race-timer' into 'master'
Create race timer

Created race timer controller

#implement #test #story[16]

See merge request !22
2017-03-23 14:20:25 +13:00
Michael Rausch 24f9607e5a Added tests for the RaceTimerController #test #story[16] 2017-03-23 14:18:27 +13:00
Michael Rausch 2384013139 Created race timer
- Race starts when timer reaches 1 second
- Race waits 10 seconds before it starts

Tags: #implement #story[16]
2017-03-23 14:11:12 +13:00
Haoming Yin 42ffd1b1f8 Add rotated wind direction arrow to race view.
#story[422]
2017-03-23 01:10:07 +13:00
Haoming Yin a2d06909c9 Finished config parser to read race info from external xml file
- created config parser unit test.
- modified config.xml file
- write unit test for config parser

#story[422]
2017-03-23 00:21:18 +13:00
Michael Rausch c8b7b95df8 Merge branch 'finish-race-results' into 'master'
Added the race results to the RaceResultController. Also fixed some bugs

- Fixed a bug where the race results would be out of order.
- Changed the colour of the start and finish gates
- Added the race results to the RaceResultController and updated view

Tags: #fix #implement #story[13, 10, 11]

See merge request !21
2017-03-22 22:42:05 +13:00
Michael Rausch 9e22eac4d8 Added the race results to the RaceResultController. Also fixed some bugs
- Fixed a bug where the race results would be out of order.
- Changed the colour of the start and finish gates
- Added the race results to the RaceResultController and updated view

Tags: #fix #implement #story[13, 10, 11]
2017-03-22 22:30:49 +13:00
Michael Rausch a41f2e4bde Changed marks from circles to squares #fix 2017-03-22 15:10:57 +13:00
Michael Rausch 039e61def6 Fixed broken JavaFX file #fix 2017-03-22 14:50:28 +13:00
Peter 54d329c5cf changed raceView javafx structure and added an example of how the handle function works with keyframes #story[426] 2017-03-22 14:44:40 +13:00
Michael Rausch f46a98fad9 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-22 14:39:37 +13:00
Michael Rausch 6a6816dda1 Merge branch 'create-finish-display' 2017-03-22 14:31:06 +13:00
Michael Rausch f6ea2953e9 Changed lat/long for gates to midpoint and added colours for the marks
Tags: #fix #implement #story[10,11]
2017-03-22 14:28:51 +13:00
Michael Rausch 7591a79323 Fixed course rotation #fix 2017-03-22 14:05:55 +13:00
Michael Rausch c12760b70a Inserted missing } #fix 2017-03-22 12:53:42 +13:00
Michael Rausch a526b0d65a Rotated map by 180 degrees #fix 2017-03-22 12:52:43 +13:00
Michael Rausch ef098e63d7 Added finishing events #implement and rotated the map by 180 degrees#fix 2017-03-22 12:51:03 +13:00
zyt10 29a0b23670 Reformatted double value lat and lon to final double VIEW_CORNER_LAT and VIEW_CORNER_LON 2017-03-22 12:03:46 +13:00
zyt10 44b5b0b771 Added side pane to GUI and resize to 1280x960
#story[426]
2017-03-22 11:46:39 +13:00
Michael Rausch 00f9cc4698 Merge branch 'fix-boat-postions-and-keyframes' into 'master'
Fixed x and y coordinates #fix #story[9]



See merge request !19
2017-03-21 17:42:13 +13:00
Michael Rausch 3d4d5a3dab Fixed x and y coordinates #fix #story[9] 2017-03-20 21:13:30 +13:00
Peter ed577fad6a adding files missed in previous commit 2017-03-20 17:58:40 +13:00
Peter ae61260665 created a javafx view for the race finish and created a rough wireframe for the controller to change the view from race to finish #story[414] 2017-03-20 17:56:14 +13:00
zyt10 0e4bb0f942 Changed getMark to getThisMark. Added start point to KeyFrame. Race now starts from start point and ends at finish point
#story[377]
2017-03-20 17:34:24 +13:00
Haoming Yin ee34e5028f Reformatted and refactored the canvas controller
#fix #refactor #story[377]
2017-03-20 17:23:33 +13:00
Peter 3b8dd11758 Added drawBoats for use in animation timer, also fixed gate marks only drawing one mark #story[378] 2017-03-20 16:11:04 +13:00
zyt10 cc04e2dd6d Fixed boat colour, was in the wrong constructor
#story[377]
2017-03-20 16:03:07 +13:00
Haoming Yin b88cf6a101 Merge branch 'refactor-file-parser' 2017-03-18 21:38:38 +13:00
Haoming Yin d10985f890 Finished team parser to read team info from external xml file
- created team parser unit test
- refactored team parser functions

#fix #refactor #implement
2017-03-18 21:32:12 +13:00
zyt10 c08504293b Created canvas and race controllers to display boats on canvas and modified marks and parsers to support them.
#story[377] #pair[zyt10, ptg19]
2017-03-17 18:21:11 +13:00
zyt10 4bc49da10d Merge remote-tracking branch 'origin/master' 2017-03-17 16:18:24 +13:00
zyt10 683f4ba94e Mostly got boats going to marks on the canvas (code currently broken) #story[377] #pair[zyt10, ptg19] 2017-03-17 16:18:01 +13:00
Haoming Yin 8fd06c84ac Merged the refactored mark related class with course parser.
#fix #refactor #story[9] #story[10] #story[11]
2017-03-17 15:56:37 +13:00
Haoming Yin 23b163e6c1 Merge branch 'master' into refactor-file-parser
# Conflicts:
#	src/main/java/seng302/models/GateMark.java
2017-03-17 15:44:16 +13:00
Haoming Yin 6383b9a6f8 Deleted the old Mark and GateMark files 2017-03-17 15:26:38 +13:00
Haoming Yin 0b3ebf229f Refactor mark related classes.
- Mark is an abstract class which containing its name and type
- Single Mark is a sub class of Mark which containing only one GPS location
- Gate Mark is a sub class of Mark which containing two Single Marks

#refactor #fix #story[10] #story[11] #story[12]
2017-03-17 15:21:04 +13:00
Michael Rausch d6fe155d4d Changed distance calculation to use latitude and longitude
Tags: #fix
2017-03-17 11:08:35 +13:00
Michael Rausch 8829728f19 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-17 10:27:04 +13:00
Michael Rausch a6c9156ae5 Changed Main-Class in pom.xml to our new main class
Tags: #fix
2017-03-17 10:26:09 +13:00
Haoming Yin 121f996a43 created config, teams xml file, and teamsParser class
#implement
2017-03-17 01:04:10 +13:00
Ryan Tan 44d4f25413 Implemented Color Enum & boat will call function from enum to get next color. Using static colour cycling for now.
#story[377]
2017-03-17 00:57:50 +13:00
Haoming Yin 16abfcffda Created course parser as a subclass of file parser
- refactored file parser as an abstract class
- created course parser to parse course xml file

#implement #fix #refactor #story[9] #story[10]
2017-03-17 00:54:43 +13:00
Haoming Yin e7ba9d962d Deleted and modified previous sprint parser to cater the sprint2’s new requirement
- also added new method for GateMark

#story[9] #story[10] #fix
2017-03-17 00:53:08 +13:00
Haoming Yin b7631c0b46 Create unit test for course parser, and modified course xml file
#implement #fix #story[10] #story[9]
2017-03-17 00:51:16 +13:00
Michael Rausch 0039703f03 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-16 20:50:41 +13:00
Michael Rausch d439c9673f removed intellij files #chore 2017-03-16 20:50:27 +13:00
Haoming Yin f255b0ea6d Add intellij file type to .gitignore. 2017-03-16 20:47:55 +13:00
Michael Rausch 9d01ed8eb3 Merge branch 'refactor-events-to-update-boat-positions' into 'master'
Removed Legs from the race, using coordinates instead

Tags: #implement #refactor #test #story[9]

See merge request !17
2017-03-16 20:30:48 +13:00
Michael Rausch 94e4e853c3 Removed Legs from the race, using coordinates instead
Tags: #implement #refactor #test #story[9]
2017-03-16 20:29:17 +13:00
Ryan Tan ffa39e6a9c Changed size of canvas to 720 x 360
#story[377]
2017-03-16 17:40:31 +13:00
Ryan Tan 9e4ae60885 Added a function to draw boat on canvas
#story[377]
2017-03-16 17:25:17 +13:00
Peter 2cd4366d07 added course xml file #story[378] 2017-03-16 17:15:45 +13:00
Peter 11c5e1e9ba Added gate mark and refactored marker to mark #story[378] 2017-03-16 16:11:48 +13:00
Peter 550812d8e1 Currently displaying basic javafx window with canvas. Also changed the file structure a bit.
At this point the javafx is not tied to the old code in any way #story[377]
2017-03-15 18:16:43 +13:00
163 changed files with 13108 additions and 1153 deletions
+17
View File
@@ -0,0 +1,17 @@
engines:
pmd:
enabled: true
channel: "beta"
fixme:
enabled: true
config:
strings:
- FIXME
- TODO
- BUG
- FIX
ratings:
paths:
- "**.java"
+7
View File
@@ -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
@@ -177,3 +180,7 @@ local.properties
.recommenders/ .recommenders/
Makefile Makefile
infer-out/
infer.txt
log.log
+9 -1
View File
@@ -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@gmail.com>
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
+1 -1
View File
@@ -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 -1
View File
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
- Configuration file - Configuration file
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file. We decided to store the team information including team names and boat currentVelocity, as well as race configuration setting in external file.
To read external files, "Json-simple" library has been used to parse information. To read external files, "Json-simple" library has been used to parse information.
By using this library, we did not have to write our json parser and benefited from the flexibility of json files. By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
-31
View File
@@ -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
}
]
}
+1 -1
View File
@@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja
## The config file ## The config file
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second). The teams/boats are specified in the config file under 'teams', each team requires a team name, and a currentVelocity (in meters per second).
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc. The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
+45
View File
@@ -20,11 +20,56 @@
<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>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.26-incubating</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
+85 -94
View File
@@ -1,111 +1,102 @@
package seng302; package seng302;
import java.lang.reflect.Array; import ch.qos.logback.classic.Level;
import java.util.ArrayList; import javafx.application.Application;
import java.util.Collections; import javafx.fxml.FXMLLoader;
import java.util.Map; import javafx.scene.Parent;
import java.util.Random; import javafx.scene.Scene;
import java.io.FileNotFoundException; import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.PolarTable;
public class App { public class App extends Application {
/** private static Logger logger = LoggerFactory.getLogger(App.class);
* Builds a race object for the AC35 course
*
* @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 public static void parseArgs(String[] args) throws ParseException {
try{ Options options = new Options();
fp = new FileParser(configFile); CommandLineParser parser = new DefaultParser();
} CommandLine cmd;
catch (FileNotFoundException e){
System.out.println("Config file does not exist"); ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
return null; .getLogger(Logger.ROOT_LOGGER_NAME);
options.addOption("debugLevel", true, "Set the application debug level");
cmd = parser.parse(options, args);
if (cmd.hasOption("debugLevel")) {
switch (cmd.getOptionValue("debugLevel")) {
case "DEBUG":
rootLogger.setLevel(Level.DEBUG);
break;
case "ALL":
rootLogger.setLevel(Level.ALL);
break;
case "WARNING":
rootLogger.setLevel(Level.WARN);
break;
case "ERROR":
rootLogger.setLevel(Level.ERROR);
break;
case "INFO":
rootLogger.setLevel(Level.INFO);
case "TRACE":
rootLogger.setLevel(Level.TRACE);
default:
rootLogger.setLevel(Level.ALL);
}
} else {
rootLogger.setLevel(Level.WARN);
} }
}
ArrayList<String> boatNames = new ArrayList<>(); @Override
ArrayList<Map<String, Object>> teams = fp.getTeams(); public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
primaryStage.setTitle("Party Parrots at Sea");
Scene scene = new Scene(root, 1530, 960);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
primaryStage.setScene(scene);
// primaryStage.setMaxWidth(1530);
// primaryStage.setMaxHeight(960);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
// primaryStage.setMaximized(true);
//get race size primaryStage.show();
int numberOfBoats = (int) fp.getRaceSize();
//get time scale primaryStage.setOnCloseRequest(e -> {
double timeScale = fp.getTimeScale(); // ClientPacketParser.appClose();
race.setTimeScale(timeScale); // ClientPacketParser.appClose();
System.exit(0);
});
for (Map<String, Object> team : teams) { // ClientState.primaryStage = primaryStage;
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;
String raceConfigFile;
if (args.length == 2 && args[0].equals("-f")){
raceConfigFile = args[1];
}
else{
// Use default config
raceConfigFile = "doc/examples/config.json";
}
try { try {
race = createRace(raceConfigFile); parseArgs(args);
} catch (Exception e) { } catch (ParseException e) {
System.out.println("There was an error creating the race."); logger.error("Could not parse command line arguments");
} }
// If race was created launch(args);
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.");
}
} }
} }
-62
View File
@@ -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;
}
}
-140
View File
@@ -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 + "°");
}
}
-132
View File
@@ -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;
}
}
}
-112
View File
@@ -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;
}
}
-57
View File
@@ -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()]);
}
}
-249
View File
@@ -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,17 @@
package seng302.gameServer;
import seng302.model.Player;
public interface ClientConnectionDelegate {
/**
* A player has connected to the server
* @param serverToClientThread The player that has connected
*/
void clientConnected(ServerToClientThread serverToClientThread);
/**
* A player has disconnected from the server
* @param player The player that has disconnected
*/
void clientDisconnected(Player player);
}
@@ -0,0 +1,24 @@
package seng302.gameServer;
/**
* An enum describing the states of the game
* Created by wmu16 on 11/07/17.
*/
public enum GameStages {
LOBBYING(0),
PRE_RACE(1),
RACING(2),
FINISHED(3),
CANCELLED(4);
private long code;
GameStages(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,702 @@
package seng302.gameServer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javafx.scene.paint.Color;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.MarkRoundingMessage;
import seng302.gameServer.messages.MarkType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoundingBoatStatus;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLParser;
/**
* A Static class to hold information about the current state of the game (model)
* Also contains logic for updating itself on regular time intervals on its own thread
* Created by wmu16 on 10/07/17.
*/
public class GameState implements Runnable {
@FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
private Logger logger = LoggerFactory.getLogger(GameState.class);
private static final Integer STATE_UPDATES_PER_SECOND = 60;
public static Integer MAX_PLAYERS = 8;
public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
public static final Double MARK_COLLISION_DISTANCE = 15d;
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
public static final Double BOUNCE_DISTANCE_MARK = 20.0;
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
public static final Double COLLISION_VELOCITY_PENALTY = 0.3;
private static Long previousUpdateTime;
public static Double windDirection;
private static Double windSpeed;
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
private static String hostIpAddress;
private static List<Player> players;
private static Map<Integer, ServerYacht> yachts;
private static Boolean isRaceStarted;
private static GameStages currentStage;
private static MarkOrder markOrder;
private static long startTime;
private static Set<Mark> marks;
private static List<Limit> courseLimit;
private static List<NewMessageListener> markListeners;
private static Map<Player, String> playerStringMap = new HashMap<>();
/*
Ideally I would like to make this class an object instantiated by the server and given to
it's created threads if necessary. Outside of that I think the dependencies on it
(atm only Yacht & GameClient) can be removed from most other classes. The observable list of
players could be pulled directly from the server by the GameClient since it instantiates it
and it is reasonable for it to pull data. The current setup of publicly available statics is
pretty meh IMO because anything can change it making it unreliable and like people did with
the old ServerParser class everything that needs shared just gets thrown in the static
collections and things become a real mess.
*/
public GameState(String hostIpAddress) {
windDirection = 180d;
windSpeed = 10000d;
this.hostIpAddress = hostIpAddress;
yachts = new HashMap<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
//set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis();
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
markListeners = new ArrayList<>();
resetStartTime();
new Thread(this, "GameState").start(); //Run the auto updates on the game state
marks = new MarkOrder().getAllMarks();
setCourseLimit("/server_config/race.xml");
}
private void setCourseLimit(String url) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder;
Document document = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url)));
} catch (Exception e) {
// sorry, we have to catch general one, otherwise we have to catch five different exceptions.
logger.trace("Failed to load course limit for boundary collision detection.", e);
}
courseLimit = XMLParser.parseRace(document).getCourseLimit();
}
public static String getHostIpAddress() {
return hostIpAddress;
}
public static Set<Mark> getMarks() {
return Collections.unmodifiableSet(marks);
}
public static List<Player> getPlayers() {
return players;
}
public static void addPlayer(Player player) {
players.add(player);
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
+ " " + player.getYacht().getCountry();
playerStringMap.put(player, playerText);
}
public static void removePlayer(Player player) {
players.remove(player);
playerStringMap.remove(player);
}
public static void addYacht(Integer sourceId, ServerYacht yacht) {
yachts.put(sourceId, yacht);
}
public static void removeYacht(Integer yachtId) {
yachts.remove(yachtId);
}
public static Boolean getIsRaceStarted() {
return isRaceStarted;
}
public static GameStages getCurrentStage() {
return currentStage;
}
public static void setCurrentStage(GameStages currentStage) {
GameState.currentStage = currentStage;
}
public static MarkOrder getMarkOrder() {
return markOrder;
}
public static long getStartTime() {
return startTime;
}
public static void resetStartTime(){
startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START;
}
public static Double getWindDirection() {
return windDirection;
}
public static void setWindDirection(Double newWindDirection) {
windDirection = newWindDirection;
}
public static void setWindSpeed(Double newWindSpeed) {
windSpeed = newWindSpeed;
}
public static Double getWindSpeedMMS() {
return windSpeed;
}
public static Double getWindSpeedKnots() {
return GeoUtility.mmsToKnots(windSpeed); // TODO: 26/07/17 cir27 - remove magic numbers
}
public static Map<Integer, ServerYacht> getYachts() {
return yachts;
}
/**
* Generates a new ID based off the size of current players + 1
*
* @return a playerID to be allocated to a new connetion
*/
public static Integer getUniquePlayerID() {
// TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on.
return yachts.size() + 1;
}
/**
* A thread to have the game state update itself at certain intervals
*/
@Override
public void run() {
while (currentStage != GameStages.FINISHED) {
try {
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
} catch (InterruptedException e) {
System.out.println("[GameState] interrupted exception");
}
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
update();
}
if (currentStage == GameStages.RACING) {
update();
}
}
}
public static void updateBoat(Integer sourceId, BoatAction actionType) {
ServerYacht playerYacht = yachts.get(sourceId);
switch (actionType) {
case VMG:
playerYacht.turnToVMG();
break;
case SAILS_IN:
playerYacht.toggleSailIn();
break;
case SAILS_OUT:
playerYacht.toggleSailIn();
break;
case TACK_GYBE:
playerYacht.tackGybe(windDirection);
break;
case UPWIND:
playerYacht.turnUpwind();
break;
case DOWNWIND:
playerYacht.turnDownwind();
break;
}
}
/**
* Called periodically in this GameState thread to update the GameState values
*/
public void update() {
Boolean raceFinished = true;
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
previousUpdateTime = System.currentTimeMillis();
if (System.currentTimeMillis() > startTime) {
GameState.setCurrentStage(GameStages.RACING);
}
for (ServerYacht yacht : yachts.values()) {
updateVelocity(yacht);
yacht.runAutoPilot();
yacht.updateLocation(timeInterval);
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
checkCollision(yacht);
checkForLegProgression(yacht);
raceFinished = false;
}
}
if (raceFinished) {
currentStage = GameStages.FINISHED;
}
}
/**
* Check if the yacht has crossed the course limit
*
* @param yacht the yacht to be tested
* @return a boolean value of if there is a boundary collision
*/
private static Boolean checkBoundaryCollision(ServerYacht yacht) {
for (int i = 0; i < courseLimit.size() - 1; i++) {
if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
}
}
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
}
return false;
}
public static void checkCollision(ServerYacht serverYacht) {
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
if (collidedYacht != null) {
GeoPoint originalLocation = serverYacht.getLocation();
serverYacht.setLocation(
calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
collidedYacht.setLocation(
calculateBounceBack(collidedYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
);
collidedYacht.setCurrentVelocity(
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
} else {
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedMark != null) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
}
else{
if (checkBoundaryCollision(serverYacht)) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, serverYacht.getLocation(),
BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
}
}
}
}
private void updateVelocity(ServerYacht yacht) {
Double velocity = yacht.getCurrentVelocity();
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
// TODO: 15/08/17 remove magic numbers from these equations.
if (yacht.getSailIn()) {
if (velocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
} else if (velocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-velocity / 200);
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
}
} else {
if (velocity > 3000) {
yacht.changeVelocity(-velocity / 200);
} else if (velocity > 100) {
yacht.changeVelocity(-velocity / 50);
} else if (velocity <= 100) {
yacht.setCurrentVelocity(0d);
}
}
}
/**
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes of
* mark rounding
*
* @return A distance in metres. Returns -1 if there is no next mark
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
*/
private Double calcDistanceToCurrentMark(ServerYacht yacht) throws IndexOutOfBoundsException {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint location = yacht.getLocation();
if (currentMark.isGate()) {
Mark sub1 = currentMark.getSubMark(1);
Mark sub2 = currentMark.getSubMark(2);
Double distance1 = GeoUtility.getDistance(location, sub1);
Double distance2 = GeoUtility.getDistance(location, sub2);
if (distance1 < distance2) {
yacht.setClosestCurrentMark(sub1);
return distance1;
} else {
yacht.setClosestCurrentMark(sub2);
return distance2;
}
} else {
yacht.setClosestCurrentMark(currentMark.getSubMark(1));
return GeoUtility.getDistance(location, currentMark.getSubMark(1));
}
}
/** lobbyController.setPlayerListSource(clientLobbyList);
* 4 Different cases of progression in the race 1 - Passing the start line 2 - Passing any
* in-race Gate 3 - Passing any in-race Mark 4 - Passing the finish line
*
* @param yacht the current yacht to check for progression
*/
private void checkForLegProgression(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
Boolean hasProgressed;
if (currentMarkSeqID == 0) {
hasProgressed = checkStartLineCrossing(yacht);
} else if (markOrder.isLastMark(currentMarkSeqID)) {
hasProgressed = checkFinishLineCrossing(yacht);
} else if (currentMark.isGate()) {
hasProgressed = checkGateRounding(yacht);
} else {
hasProgressed = checkMarkRounding(yacht);
}
if (hasProgressed) {
yacht.incrementLegNumber();
sendMarkRoundingMessage(yacht);
logMarkRounding(yacht);
yacht.setHasPassedLine(false);
yacht.setHasEnteredRoundingZone(false);
yacht.setHasPassedThroughGate(false);
if (!markOrder.isLastMark(currentMarkSeqID)) {
yacht.incrementMarkSeqID();
}
}
}
/**
* If we pass the start line gate in the correct direction, progress
*
* @param yacht The current yacht to check for
*/
private Boolean checkStartLineCrossing(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.RACING);
return true;
}
}
return false;
}
/**
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
* of the yacht if so. A visual representation of this algorithm can be seen on the Wiki under
* 'mark passing algorithm'
*
* @param yacht The current yacht to check for
*/
private Boolean checkMarkRounding(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
GeoPoint nextPoint = markOrder.getNextMark(currentMarkSeqID).getMidPoint();
GeoPoint prevPoint = markOrder.getPreviousMark(currentMarkSeqID).getMidPoint();
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
if (calcDistanceToCurrentMark(yacht) < ROUNDING_DISTANCE) {
yacht.setHasEnteredRoundingZone(true);
}
//In case current mark is a gate, loop through all marks just in case
for (Mark thisCurrentMark : currentMark.getMarks()) {
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
yacht.setHasPassedLine(true);
}
}
return yacht.hasPassedLine() && yacht.hasEnteredRoundingZone();
}
/**
* Checks if a gate line has been crossed and in the correct direction
*
* @param yacht The current yacht to check for
*/
private Boolean checkGateRounding(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
//We have crossed the line
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
//Check we cross the line in the correct direction
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setHasPassedThroughGate(true);
}
}
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (yacht.hasPassedThroughGate()) {
//Check if we need to round this gate after passing through
if (prevMarkSide == nextMarkSide) {
return checkMarkRounding(yacht);
} else {
return true;
}
}
return false;
}
/**
* If we pass the finish gate in the correct direction
*
* @param yacht The current yacht to check for
*/
private Boolean checkFinishLineCrossing(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.FINISHED);
return true;
}
}
return false;
}
/**
* Handles player customization.
*
* @param playerID The ID of the player being modified.
* @param requestType the type of player customization the player wants
* @param customizeData the data related to the customization (color, name, shape)
*/
public static void customizePlayer(long playerID, CustomizeRequestType requestType,
byte[] customizeData) {
ServerYacht playerYacht = yachts.get((int) playerID);
if (requestType.equals(CustomizeRequestType.NAME)) {
String name = new String(customizeData);
playerYacht.setBoatName(name);
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
int red = customizeData[0] & 0xFF;
int green = customizeData[1] & 0xFF;
int blue = customizeData[2] & 0xFF;
Color yachtColor = Color.rgb(red, green, blue);
playerYacht.setBoatColor(yachtColor);
}
}
private static Mark checkMarkCollision(ServerYacht yacht) {
Set<Mark> marksInRace = GameState.getMarks();
for (Mark mark : marksInRace) {
if (GeoUtility.getDistance(yacht.getLocation(), mark)
<= MARK_COLLISION_DISTANCE) {
return mark;
}
}
return null;
}
/**
* Calculate the new position of the boat after it has had a collision
*
* @return The boats new position
*/
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith,
Double bounceDistance) {
Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith);
// Invert heading
heading -= 180;
Integer newHeading = Math.floorMod(heading.intValue(), 360);
return GeoUtility
.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
}
/**
* Collision detection which iterates through all the yachts and check if any yacht collided
* with this yacht. Return collided yacht or null if no collision.
*
* @return yacht to compare to all other yachts.
*/
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
for (ServerYacht otherYacht : GameState.getYachts().values()) {
if (otherYacht != yacht) {
Double distance = GeoUtility
.getDistance(otherYacht.getLocation(), yacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
return otherYacht;
}
}
}
return null;
}
private void sendMarkRoundingMessage(ServerYacht yacht) {
Integer sourceID = yacht.getSourceId();
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
MarkType markType = (currentMark.isGate()) ? MarkType.GATE : MarkType.ROUNDING_MARK;
Mark roundingMark = yacht.getClosestCurrentMark();
// TODO: 13/8/17 figure out the rounding side, rounded mark source ID and boat status.
Message markRoundingMessage = new MarkRoundingMessage(0, 0,
sourceID, RoundingBoatStatus.RACING, roundingMark.getRoundingSide(), markType,
currentMarkSeqID + 1);
notifyMessageListeners(markRoundingMessage);
}
private static void notifyMessageListeners(Message message) {
for (NewMessageListener mpl : markListeners) {
mpl.notify(message);
}
}
private void logMarkRounding(ServerYacht yacht) {
Mark roundingMark = yacht.getClosestCurrentMark();
logger.debug(
String.format("Yacht srcID(%d) passed Mark srcID(%d)", yacht.getSourceId(),
roundingMark.getSourceID()));
}
public static void addMarkPassListener(NewMessageListener listener) {
markListeners.add(listener);
}
public static void setCustomizationFlag() {
customizationFlag = true;
}
public static Boolean getCustomizationFlag() {
return customizationFlag;
}
public static void resetCustomizationFlag() {
customizationFlag = false;
}
}
@@ -0,0 +1,82 @@
package seng302.gameServer;
import java.io.IOException;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;
import seng302.model.Player;
import seng302.gameServer.messages.Heartbeat;
import seng302.gameServer.messages.Message;
/**
* Send Heartbeat messages to connected player at a specified interval
* Will call .clientDisconnected on the delegate when a heartbeat message
* cannot be sent to a player
*/
public class HeartbeatThread implements Runnable {
private final int HEARTBEAT_PERIOD = 200;
private ClientConnectionDelegate delegate;
private Integer seqNum;
private Stack<Player> disconnectedPlayers;
public HeartbeatThread(ClientConnectionDelegate delegate){
this.delegate = delegate;
seqNum = 0;
disconnectedPlayers = new Stack<>();
Thread thread = new Thread(this, "HeartBeat");
thread.start();
}
/**
* A player has lost connection to the server
* The player is added to a stack so that the delegate
* can be notified
*
* @param player The player that has disconnected
*/
private void playerLostConnection(Player player){
disconnectedPlayers.push(player);
}
/**
* Sends a heartbeat message to each connected player
* The delegate is notified if a player has disconnected
*/
private void sendHeartbeatToAllPlayers(){
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
playerLostConnection(player);
}
}
updateDelegate();
seqNum++;
}
/**
* Notifies the delegate about
* each disconnected player
*/
private void updateDelegate() {
while (!disconnectedPlayers.empty()){
delegate.clientDisconnected(disconnectedPlayers.pop());
}
}
public void run(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
sendHeartbeatToAllPlayers();
}
}, 0, HEARTBEAT_PERIOD);
}
}
@@ -0,0 +1,317 @@
package seng302.gameServer;
import seng302.gameServer.messages.*;
import seng302.model.GeoPoint;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.utilities.GeoUtility;
import java.io.IOException;
import java.net.ServerSocket;
import java.time.LocalDateTime;
import java.util.*;
/**
* A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17.
*/
public class MainServerThread implements Runnable, ClientConnectionDelegate {
private static final int PORT = 4942;
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
private static final int LOG_LEVEL = 1;
private static final int WARNING_TIME = 10 * -1000;
private static final int PREPATORY_TIME = 5 * -1000;
public static final int TIME_TILL_START = 10 * 1000;
private static final int MAX_WIND_SPEED = 12000;
private static final int MIN_WIND_SPEED = 8000;
public static int windSpeed = 1000;
private boolean terminated;
private Thread thread;
private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
public MainServerThread() {
new GameState("localhost");
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
}
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMarkPassListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this, "MainServer");
startUpdatingWind();
thread.start();
}
public void run() {
new HeartbeatThread(this);
new ServerListenThread(serverSocket, this);
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
while (!terminated) {
try {
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
} catch (InterruptedException e) {
serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
}
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
.getCustomizationFlag()) {
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
for (ServerToClientThread thread : serverToClientThreads) {
thread.sendSetupMessages();
}
GameState.resetCustomizationFlag();
}
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
updateClients();
}
//RACING
if (GameState.getCurrentStage() == GameStages.RACING) {
updateClients();
}
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
terminate();
}
}
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
try {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.terminate();
}
serverSocket.close();
return;
} catch (IOException e) {
System.out.println("IO error in server thread handler upon closing socket");
}
}
public void updateClients() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendBoatLocationPackets();
}
}
private void broadcastMessage(Message message) {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendMessage(message);
}
}
private static void updateWind(){
Integer direction = GameState.getWindDirection().intValue();
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
Random random = new Random();
if (Math.floorMod(random.nextInt(), 2) == 0){
direction += random.nextInt(4);
windSpeed += random.nextInt(20) + 50;
}
else{
direction -= random.nextInt(4);
windSpeed -= random.nextInt(20) + 50;
}
direction = Math.floorMod(direction, 360);
if (windSpeed > MAX_WIND_SPEED){
windSpeed -= random.nextInt(1000);
}
if (windSpeed <= MIN_WIND_SPEED){
windSpeed += random.nextInt(1000);
}
GameState.setWindSpeed(Double.valueOf(windSpeed));
GameState.setWindDirection(direction.doubleValue());
}
private static void startUpdatingWind(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
updateWind();
}
}, 0, 500);
}
static void serverLog(String message, int logLevel) {
if (logLevel <= LOG_LEVEL) {
System.out.println(
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
}
}
/**
* A client has tried to connect to the server
*
* @param serverToClientThread The player that connected
*/
@Override
public void clientConnected(ServerToClientThread serverToClientThread) {
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(() -> {
for (ServerToClientThread thread : serverToClientThreads) {
thread.sendSetupMessages();
}
});
serverToClientThread.addDisconnectListener(this::clientDisconnected);
}
/**
* A player has left the game, remove the player from the GameState
*
* @param player The player that left
*/
@Override
public void clientDisconnected(Player player) {
// try {
// player.getSocket().close();
// } catch (Exception e) {
// serverLog("Cannot disconnect the socket for the disconnected player.", 0);
// }
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
GameState.removeYacht(player.getYacht().getSourceId());
GameState.removePlayer(player);
ServerToClientThread closedConnection = null;
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
if (serverToClientThread.getSocket() == player.getSocket()) {
closedConnection = serverToClientThread;
} else if (GameState.getCurrentStage() != GameStages.RACING){
serverToClientThread.sendSetupMessages();
}
}
serverToClientThreads.remove(closedConnection);
closedConnection.terminate();
}
public void startGame() {
initialiseBoatPositions();
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
broadcastMessage(makeRaceStatusMessage());
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
broadcastMessage(makeRaceStartMessage());
}
}
}, 0, 500);
}
private RaceStartStatusMessage makeRaceStartMessage() {
Long raceStartTime = GameState.getStartTime();
return new RaceStartStatusMessage(1, raceStartTime ,
1, RaceStartNotificationType.SET_RACE_START_TIME);
}
private RaceStatusMessage makeRaceStatusMessage() {
// variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
y.getLegNumber(),
0, 0, 1234L,
1234L);
boatSubMessages.add(m);
}
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
raceStatus = RaceStatus.PRESTART;
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
raceStatus = RaceStatus.PRESTART;
if (timeTillStart > WARNING_TIME) {
raceStatus = RaceStatus.WARNING;
}
if (timeTillStart > PREPATORY_TIME) {
raceStatus = RaceStatus.PREPARATORY;
}
} else {
raceStatus = RaceStatus.STARTED;
}
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages);
}
public void terminate() {
terminated = true;
}
/**
* Initialise boats to specific spaced out geopoints behind starting line.
*/
private void initialiseBoatPositions() {
// Getting the start line compound marks
// if (gameClient== null) {
// return;
// }
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
GeoPoint startMark1 = cm.getSubMark(1);
GeoPoint startMark2 = cm.getSubMark(2);
// Calculating midpoint
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
Double length = GeoUtility.getDistance(startMark1, startMark2);
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
// Setting each boats position side by side
double DISTANCE_FACTOR = 50.0; // distance apart in meters
int boatIndex = 0;
for (ServerYacht yacht : GameState.getYachts().values()) {
int distanceApart = boatIndex / 2;
if (boatIndex % 2 == 1 && boatIndex != 0) {
distanceApart++;
distanceApart *= -1;
}
GeoPoint spawnMark = GeoUtility
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
if (yacht.getHeading() < perpendicularAngle) {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
} else {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
}
yacht.setLocation(spawnMark);
boatIndex++;
}
}
}
@@ -0,0 +1,45 @@
package seng302.gameServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* A class for a thread to listen to connections
* Created by wmu16 on 11/07/17.
*/
public class ServerListenThread implements Runnable {
private ServerSocket serverSocket;
private ClientConnectionDelegate delegate;
public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){
this.serverSocket = serverSocket;
this.delegate = delegate;
Thread thread = new Thread(this, "ServerListen");
thread.start();
}
/**
* Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState
*/
private void acceptConnection() {
try {
Socket thisClient = serverSocket.accept();
if (thisClient != null && GameState.getCurrentStage().equals(GameStages.LOBBYING)) {
ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
delegate.clientConnected(thisConnection);
} else {
thisClient.close();
}
} catch (IOException e) {
e.getMessage();
}
}
public void run(){
while (serverSocket != null && !serverSocket.isClosed()){
acceptConnection();
}
}
}
@@ -0,0 +1,32 @@
package seng302.gameServer;
import java.util.Arrays;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.model.stream.packets.StreamPacket;
public class ServerPacketParser {
public static BoatAction extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
return BoatAction.getType((int) actionTypeValue);
}
public static ClientType extractClientType(StreamPacket packet){
byte[] payload = packet.getPayload();
long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
return ClientType.getClientType((int) value);
}
public static CustomizeRequestType extractCustomizationType(StreamPacket packet) {
byte[] payload = packet.getPayload();
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
return CustomizeRequestType.getRequestType((int) type);
}
}
@@ -0,0 +1,389 @@
package seng302.gameServer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta;
import seng302.utilities.XMLGenerator;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta;
import seng302.utilities.XMLGenerator;
/**
* A class describing a single connection to a Client for the purposes of sending and receiving on
* its own thread. All server threads created and owned by the server thread handler which can
* trigger client updates on its threads Created by wmu16 on 13/07/17.
*/
public class ServerToClientThread implements Runnable, Observer {
/**
* Called to notify listeners when this thread receives a connection correctly.
*/
@FunctionalInterface
interface ConnectionListener {
void notifyConnection ();
}
// TODO: 17/08/17 this is only temporary disconnects should be handled consistently
@FunctionalInterface
interface DisconnectListener {
void notifyDisconnect (Player player);
}
private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class);
private Thread thread;
private InputStream is;
private OutputStream os;
private Socket socket;
private ByteArrayOutputStream crcBuffer;
private Integer seqNo;
private Integer sourceId;
private ClientType clientType;
private Boolean isRegistered = false;
private XMLGenerator xml;
private List<ConnectionListener> connectionListeners = new ArrayList<>();
private DisconnectListener disconnectListener;
private ServerYacht yacht;
private Player player;
public ServerToClientThread(Socket socket) {
this.socket = socket;
seqNo = 0;
try{
is = socket.getInputStream();
os = socket.getOutputStream();
} catch (IOException e) {
return;
}
thread = new Thread(this, "ServerToClient");
thread.start();
}
private void setUpPlayer(){
BufferedReader fn;
String fName = "";
BufferedReader ln;
String lName = "";
fn = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_First_Names.csv"
)
)
);
List<String> all = fn.lines().collect(Collectors.toList());
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ln = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_Last_Names.csv"
)
)
);
all = ln.lines().collect(Collectors.toList());
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ServerYacht yacht = new ServerYacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17
player = new Player(socket, yacht);
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(player);
}
@Override
public void update(Observable o, Object arg) {
if (arg != null) {
sendMessage((Message) arg);
} else {
sendSetupMessages();
}
}
private void completeRegistration(ClientType clientType) throws IOException {
// Fail if not a player
if (!clientType.equals(ClientType.PLAYER)){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL);
os.write(responseMessage.getBuffer());
return;
}
if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
os.write(responseMessage.getBuffer());
return;
}
Integer sourceId = GameState.getUniquePlayerID();
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING);
this.clientType = clientType;
this.sourceId = sourceId;
isRegistered = true;
os.write(responseMessage.getBuffer());
setUpPlayer();
for (ConnectionListener listener : connectionListeners) {
listener.notifyConnection();
}
}
public void run() {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while (socket.isConnected() && !socket.isClosed()) {
try {
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if (sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6));
skipBytes(4);
long payloadLength = Message.bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
switch (PacketType.assignPacketType(type, payload)) {
case BOAT_ACTION:
BoatAction actionType = ServerPacketParser
.extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.updateBoat(sourceId, actionType);
break;
case RACE_REGISTRATION_REQUEST:
ClientType requestedType = ServerPacketParser.extractClientType(
new StreamPacket(type, payloadLength, timeStamp, payload));
completeRegistration(requestedType);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
CustomizeRequestType requestType = ServerPacketParser
.extractCustomizationType(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.customizePlayer(sourceID, requestType,
Arrays.copyOfRange(payload, 6, payload.length));
GameState.setCustomizationFlag();
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
break;
}
} else {
logger.warn("Packet has been dropped", 1);
}
}
} catch (Exception e) {
closeSocket();
return;
}
}
logger.warn("Closed serverToClientThread" + thread, 1);
}
public void sendSetupMessages() {
xml = new XMLGenerator();
Race race = new Race();
for (ServerYacht yacht : GameState.getYachts().values()) {
race.addBoat(yacht);
}
//@TODO calculate lat/lng values
xml.setRegatta(
new Regatta(
"Party Parrot Test Server", "Bermuda Test Course",
57.6679590, 11.8503233)
);
xml.setRace(race);
XMLMessage xmlMessage;
xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xml.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
xml.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
xml.getRaceAsXml().length());
sendMessage(xmlMessage);
}
private void closeSocket() {
try {
socket.close();
} catch (IOException e) {
System.out.println("IO error in server thread upon trying to close socket");
}
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = is.read();
crcBuffer.write(currentByte);
} catch (SocketException se) {
disconnectListener.notifyDisconnect(this.player);
} catch (IOException e) {
disconnectListener.notifyDisconnect(this.player);
logger.warn("Socket read failed", 1);
}
if (currentByte == -1) {
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws Exception {
for (int i = 0; i < n; i++) {
readByte();
}
}
public void sendMessage(Message message) {
try {
os.write(message.getBuffer());
} catch (SocketException e) {
logger.warn("Player " + sourceId + " side socket disconnected", 1);
} catch (IOException e) {
logger.warn("Message send failed", 1);
}
}
private int getSeqNo() {
seqNo++;
return seqNo;
}
public void sendBoatLocationPackets() {
ArrayList<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
for (ServerYacht yacht : yachts) {
BoatLocationMessage boatLocationMessage =
new BoatLocationMessage(
yacht.getSourceId(),
getSeqNo(),
yacht.getLocation().getLat(),
yacht.getLocation().getLng(),
yacht.getHeading(),
yacht.getCurrentVelocity().longValue());
sendMessage(boatLocationMessage);
}
}
public Thread getThread() {
return thread;
}
public Socket getSocket() {
return socket;
}
public ServerYacht getYacht() {
return yacht;
}
public void sendCollisionMessage(Integer yachtId) {
sendMessage(new YachtEventCodeMessage(yachtId));
}
public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
public void removeConnectionListener(ConnectionListener listener) {
connectionListeners.remove(listener);
}
public void terminate () {
try {
socket.close();
} catch (IOException ioe) {
logger.warn("IOException attempting to terminate serverToClientThread " + this.thread);
}
}
public void addDisconnectListener(DisconnectListener disconnectListener) {
this.disconnectListener = disconnectListener;
}
}
@@ -0,0 +1,39 @@
package seng302.gameServer.messages;
import java.util.HashMap;
import java.util.Map;
/**
* Created by kre39 on 12/07/17.
*/
public enum BoatAction {
VMG(1),
SAILS_IN(2),
SAILS_OUT(3),
TACK_GYBE(4),
UPWIND(5),
DOWNWIND(6),
MAINTAIN_HEADING(7);
private final int type;
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
static {
for (BoatAction type : BoatAction.values()) {
intToTypeMap.put(type.getValue(), type);
}
}
BoatAction(int type){
this.type = type;
}
public static BoatAction getType(int value) {
return intToTypeMap.get(value);
}
public int getValue() {
return this.type;
}
}
@@ -0,0 +1,29 @@
package seng302.gameServer.messages;
/**
* Created by kre39 on 12/07/17.
*/
public class BoatActionMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
private final int MESSAGE_SIZE = 1;
private BoatAction actionType;
public BoatActionMessage(BoatAction actionType) {
this.actionType = actionType;
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putInt(actionType.getValue(), 1);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -0,0 +1,162 @@
package seng302.gameServer.messages;
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.gameServer.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.gameServer.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.gameServer.messages;
/**
* Created by kre39 on 20/07/17.
*/
public class ChatterMessage extends Message {
private final long MESSAGE_VERSION_NUMBER = 1;
private final int MESSAGE_SIZE = 3;
private int message_type;
private int message_size = 21;
private String message;
public ChatterMessage(int message_type, int message_size, String message) {
this.message_type = message_type;
this.message_size = message_size;
this.message = message;
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putByte((byte) MESSAGE_VERSION_NUMBER);
putInt(message_type, 1);
putInt(message_size, 1);
putBytes(message.getBytes());
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_SIZE + message_size;
}
}
@@ -0,0 +1,33 @@
package seng302.gameServer.messages;
public enum ClientType {
SPECTATOR(0x00),
PLAYER(0x01),
CONTROL_TUTORIAL(0x02),
GHOST_MODE(0x03);
private int type;
ClientType(int type){
this.type = type;
}
public int getCode(){
return type;
}
public static ClientType getClientType(int typeCode){
switch (typeCode){
case 0x00:
return SPECTATOR;
case 0x01:
return PLAYER;
case 0x02:
return CONTROL_TUTORIAL;
case 0x03:
return GHOST_MODE;
default:
return PLAYER;
}
}
}
@@ -0,0 +1,35 @@
package seng302.gameServer.messages;
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
public class CustomizeRequestMessage extends Message {
private static int MESSAGE_LENGTH = 6;
//Message fields
private CustomizeRequestType customizeType;
private Integer payloadLength;
public CustomizeRequestMessage(CustomizeRequestType customizeType, double sourceID,
byte[] payload) {
payloadLength = payload.length;
setHeader(new Header(MessageType.CUSTOMIZATION_REQUEST, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt((int) sourceID, 4);
putInt((int) customizeType.getType(), 2);
putBytes(payload);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_LENGTH + payloadLength; // placeholder
}
}
@@ -0,0 +1,31 @@
package seng302.gameServer.messages;
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
public enum CustomizeRequestType {
NAME(0x00),
COLOR(0x01),
SHAPE(0x02);
private int type;
CustomizeRequestType(int type) {
this.type = type;
}
int getType() {
return this.type;
}
public static CustomizeRequestType getRequestType(int typeCode) {
switch (typeCode) {
case 0x00:
return NAME;
case 0x01:
return COLOR;
case 0x02:
return SHAPE;
default:
return null;
}
}
}
@@ -0,0 +1,28 @@
package seng302.gameServer.messages;
/**
* Created by ajm412 on 14/08/17.
*/
public class CustomizeResponseMessage extends Message {
private static int MESSAGE_LENGTH = 2;
public CustomizeResponseMessage(CustomizeResponseType responseType) {
setHeader(new Header(MessageType.CUSTOMIZATION_RESPONSE, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(responseType.getType(), 2);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_LENGTH; // placeholder
}
}
@@ -0,0 +1,34 @@
package seng302.gameServer.messages;
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
public enum CustomizeResponseType {
SUCCESS(0x00),
FAILURE(0x01),
FAILURE_MALFORMED_DATA(0x02),
FAILURE_INCOMPATIBLE(0x03);
private int type;
CustomizeResponseType(int type) {
this.type = type;
}
int getType() {
return this.type;
}
public static CustomizeResponseType getResponseType(int typeCode) {
switch (typeCode) {
case 0x00:
return SUCCESS;
case 0x01:
return FAILURE;
case 0x02:
return FAILURE_MALFORMED_DATA;
case 0x03:
return FAILURE_INCOMPATIBLE;
default:
return null;
}
}
}
@@ -0,0 +1,16 @@
package seng302.gameServer.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,80 @@
package seng302.gameServer.messages;
import java.nio.ByteBuffer;
public class Header {
// From API spec
private final int syncByte1 = 0x47;
private final int syncByte2 = 0x83;
private MessageType messageType;
private int timeStamp;
private int sourceId;
private short messageLength;
private static final int MESSAGE_LEN = 15;
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,27 @@
package seng302.gameServer.messages;
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,58 @@
package seng302.gameServer.messages;
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
* @param ackNumber ackNumber
* @param raceId raceId
* @param sourceId boatSourceId
* @param roundingBoatStatus roundingBoatStatus
* @param roundingSide roundingSide
* @param markId markId
*/
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
RoundingSide roundingSide, MarkType markType, int markId) {
this.time = System.currentTimeMillis();
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) markType.getCode());
putByte((byte) markId);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -0,0 +1,20 @@
package seng302.gameServer.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,221 @@
package seng302.gameServer.messages;
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 A byte array to be sent
*/
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
*
* @param bytes the bytes to be converted to long
* @return a positive long if there is less than 8 bytes -1 otherwise
*/
public static long bytesToLong(byte[] bytes){
long partialLong = 0;
int index = 0;
for (byte b: bytes){
if (index > 6){
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
/**
* Reverse an array of bytes
* @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,42 @@
package seng302.gameServer.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),
REGISTRATION_REQUEST(101),
REGISTRATION_RESPONSE(102),
CUSTOMIZATION_REQUEST(103),
CUSTOMIZATION_RESPONSE(104);
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.gameServer.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,48 @@
package seng302.gameServer.messages;
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.gameServer.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,79 @@
package seng302.gameServer.messages;
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+100.0;
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.gameServer.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,22 @@
package seng302.gameServer.messages;
public class RegistrationRequestMessage extends Message {
private static int MESSAGE_LENGTH = 2;
public RegistrationRequestMessage(ClientType type){
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(type.getCode(), 2);
writeCRC();
}
@Override
public int getSize() {
return MESSAGE_LENGTH;
}
}
@@ -0,0 +1,20 @@
package seng302.gameServer.messages;
public class RegistrationResponseMessage extends Message{
public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){
setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(clientSourceID, 4);
putInt(status.getCode(), 1);
writeCRC();
}
@Override
public int getSize() {
return 5;
}
}
@@ -0,0 +1,44 @@
package seng302.gameServer.messages;
public enum RegistrationResponseStatus {
SUCCESS_SPECTATING(0x00),
SUCCESS_PLAYING(0x01),
SUCCESS_TUTORIAL(0x02),
SUCCESS_GHOSTING(0x03),
FAILURE_GENERAL(0x10),
FAILURE_FULL(0x11);
private int code;
RegistrationResponseStatus(int code){
this.code = code;
}
/**
* Get the message code (From the API Spec)
* @return the message code
*/
int getCode(){
return this.code;
}
public static RegistrationResponseStatus getResponseStatus(int typeCode){
switch (typeCode){
case 0x00:
return SUCCESS_SPECTATING;
case 0x01:
return SUCCESS_PLAYING;
case 0x02:
return SUCCESS_TUTORIAL;
case 0x03:
return SUCCESS_GHOSTING;
case 0x10:
return FAILURE_GENERAL;
case 0x11:
return FAILURE_FULL;
default:
return FAILURE_GENERAL;
}
}
}
@@ -0,0 +1,21 @@
package seng302.gameServer.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,52 @@
package seng302.gameServer.messages;
/**
* The side the boat rounded the mark
*/
public enum RoundingSide {
UNKNOWN(0, "Unknown"),
PORT(1, "Port"),
STARBOARD(2, "Stbd"),
SP(3, "SP"),
PS(4, "PS");
private long code;
private String name;
RoundingSide(long code, String name) {
this.code = code;
this.name = name;
}
public long getCode(){
return code;
}
public String getName() {
return name;
}
public static RoundingSide getRoundingSide(String identifier) {
RoundingSide roundingSide = UNKNOWN;
switch (identifier) {
case "Unknown":
roundingSide = UNKNOWN;
break;
case "Port":
roundingSide = PORT;
break;
case "Stbd":
roundingSide = STARBOARD;
break;
case "SP":
roundingSide = SP;
break;
case "PS":
roundingSide = PS;
break;
}
return roundingSide;
}
}
@@ -0,0 +1,53 @@
package seng302.gameServer.messages;
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
* @param sequenceNum sequenceNum
*/
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.gameServer.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,52 @@
package seng302.gameServer.messages;
/**
* Created by zyt10 on 10/08/17.
*/
public class YachtEventCodeMessage extends Message {
private final MessageType MESSAGE_TYPE = MessageType.YACHT_EVENT_CODE;
private final int MESSAGE_VERSION = 1; //Always set to 1
private final int MESSAGE_SIZE = 22;
// Message fields
private long timeStamp;
private long ack = 0x00; //Unused
private int raceId;
private int destSourceId;
private int incidentId;
private int eventId;
public YachtEventCodeMessage(Integer subjectId) {
timeStamp = System.currentTimeMillis() / 1000L;
ack = 0;
raceId = 1;
destSourceId = subjectId; // collision boat source id
incidentId = 0;
eventId = 33;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putUnsignedByte((byte) MESSAGE_VERSION);
putInt((int) timeStamp, 6);
putInt((int) ack, 2);
putInt((int) raceId, 4);
putInt((int) destSourceId, 4);
putInt((int) incidentId, 4);
putInt((int) eventId, 1);
writeCRC();
rewind();
}
/**
* @return The length of this message
*/
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -0,0 +1,298 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.mark.CompoundMark;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
* not used anymore.
*/
public class ClientYacht extends Observable {
@FunctionalInterface
public interface YachtLocationListener {
void notifyLocation(ClientYacht clientYacht, double lat, double lon, double heading,
Boolean sailsIn, double velocity);
}
@FunctionalInterface
public interface MarkRoundingListener {
void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber);
}
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
private String boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private Integer position;
private Long estimateTimeAtFinish;
private Boolean sailIn = true;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Long timeTillNext;
private Double heading;
private Integer legNumber = 0;
private GeoPoint location;
private Integer boatStatus;
private Double currentVelocity;
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
private CompoundMark lastMarkRounded;
private Color colour;
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.location = new GeoPoint(57.670341, 11.826856);
this.heading = 120.0; //In degrees
this.currentVelocity = 0d;
this.boatStatus = 1;
this.colour = Color.rgb(0, 0, 0, 1.0);
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
}
public String getBoatType() {
return boatType;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) {
return 0;
}
return sourceId;
}
public String getHullID() {
if (hullID == null) {
return "";
}
return hullID;
}
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) {
return "";
}
return country;
}
public Integer getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(Integer boatStatus) {
this.boatStatus = boatStatus;
}
public Integer getLegNumber() {
return legNumber;
}
public void setLegNumber(Integer legNumber) {
this.legNumber = legNumber;
}
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
timeTillNext = estimateTimeTillNextMark;
}
public String getEstimateTimeAtFinish() {
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return format.format(estimateTimeAtFinish);
}
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
this.estimateTimeAtFinish = estimateTimeAtFinish;
}
public Integer getPlacing() {
return placingProperty.get();
}
public void setPlacing(Integer position) {
placingProperty.set(position);
}
public ReadOnlyIntegerProperty placingProperty() {
return placingProperty.getReadOnlyProperty();
}
public void updateVelocityProperty(double velocity) {
this.velocityProperty.set(velocity);
}
public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundTime = markRoundingTime;
}
public ReadOnlyDoubleProperty getVelocityProperty() {
return velocityProperty.getReadOnlyProperty();
}
public ReadOnlyLongProperty timeTillNextProperty() {
return timeTillNextProperty.getReadOnlyProperty();
}
public Long getTimeTillNext() {
return timeTillNext;
}
public Long getMarkRoundTime() {
return markRoundTime;
}
public CompoundMark getLastMarkRounded() {
return lastMarkRounded;
}
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
this.lastMarkRounded = lastMarkRounded;
}
public GeoPoint getLocation() {
return location;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
public void toggleSail() {
sailIn = !sailIn;
}
//// TODO: 15/08/17 asd
/**
* Sets the current location of the boat in lat and long whilst preserving the last location
*
* @param lat Latitude
* @param lng Longitude
*/
public void setLocation(Double lat, Double lng) {
location.setLat(lat);
location.setLng(lng);
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
@Override
public String toString() {
return boatName;
}
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
}
public ReadOnlyLongProperty timeSinceLastMarkProperty() {
return timeSinceLastMarkProperty.getReadOnlyProperty();
}
public void setTimeTillNext(Long timeTillNext) {
this.timeTillNext = timeTillNext;
}
public Color getColour() {
return colour;
}
public void setColour(Color colour) {
this.colour = colour;
}
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
// this.currentVelocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
}
}
public void addLocationListener(YachtLocationListener listener) {
locationListeners.add(listener);
}
public void addMarkRoundingListener(MarkRoundingListener listener) {
markRoundingListeners.add(listener);
}
public void removeMarkRoundingListener(MarkRoundingListener listener) {
markRoundingListeners.remove(listener);
}
public boolean getSailIn () {
return sailIn;
}
public void roundMark(CompoundMark mark, long markRoundTime, long timeSinceLastMark) {
this.markRoundTime = markRoundTime;
timeSinceLastMarkProperty.set(timeSinceLastMark);
lastMarkRounded = mark;
legNumber++;
for (MarkRoundingListener listener : markRoundingListeners) {
listener.notifyRounding(this, lastMarkRounded, legNumber);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
package seng302.model;
import javafx.scene.paint.Color;
/**
* Enum for generating colours.
*/
public enum Colors {
RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY;
public static Color getColor(Integer index) {
return Color.valueOf(values()[index].toString());
}
}
+36
View File
@@ -0,0 +1,36 @@
package seng302.model;
/**
* A class represent Geo location (latitude, lnggitude).
* Created by Haoming on 15/5/2017
*/
public class GeoPoint {
private 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;
}
@Override
public String toString() {
return "lat: " + lat + " lng: " + lng;
}
}
+18
View File
@@ -0,0 +1,18 @@
package seng302.model;
/**
* Stores data on the border of a race
*/
public class Limit extends GeoPoint {
private Integer seqID;
public Limit(Integer seqID, Double lat, Double lng) {
super(lat, lng);
this.seqID = seqID;
}
public Integer getSeqID() {
return seqID;
}
}
+68
View File
@@ -0,0 +1,68 @@
package seng302.model;
import java.net.Socket;
/**
* A Class defining a player and their respective details in the game as held by the model
* Created by wmu16 on 10/07/17.
*/
public class Player {
private Socket socket;
private ServerYacht yacht;
private Integer lastMarkPassed;
public Player(Socket socket, ServerYacht yacht) {
this.socket = socket;
this.yacht = yacht;
}
public Socket getSocket() {
return socket;
}
public Integer getLastMarkPassed() {
return lastMarkPassed;
}
public void setLastMarkPassed(Integer lastMarkPassed) {
this.lastMarkPassed = lastMarkPassed;
}
public ServerYacht getYacht() {
return yacht;
}
@Override
public String toString() {
String playerAddress = null;
if (socket == null){
return "Disconnected Player";
}
playerAddress = socket.getRemoteSocketAddress().toString();
return playerAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null){
return false;
}
if (!(obj instanceof Player)){
return false;
}
return ((Player) obj).socket.equals(socket);
}
@Override
public int hashCode(){
return socket.hashCode();
}
}
+182
View File
@@ -0,0 +1,182 @@
package seng302.model;
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
* @param polarFile polarFile to be parsed
*/
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;
}
}
+122
View File
@@ -0,0 +1,122 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Observable;
import java.util.TimeZone;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seng302.model.stream.parser.RaceStartData;
import seng302.model.stream.parser.RaceStatusData;
/**
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
* Calculates the state of critical race attributes when relevant data is added.
*/
public class RaceState {
@FunctionalInterface
public interface CollisionListener {
void notifyCollision(GeoPoint location);
}
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
private ReadOnlyDoubleWrapper windSpeed = new ReadOnlyDoubleWrapper();
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
private long serverSystemTime;
private long expectedStartTime;
private boolean isRaceStarted = false;
long timeTillStart;
private ObservableList<ClientYacht> playerPositions;
private List<ClientYacht> collisions = new ArrayList<>();
private List<CollisionListener> collisionListeners = new ArrayList<>();
public RaceState() {
playerPositions = FXCollections.observableArrayList();
}
public void updateState (RaceStatusData data) {
this.windSpeed.set(data.getWindSpeed());
this.windDirection.set(data.getWindDirection());
this.serverSystemTime = data.getCurrentTime();
this.expectedStartTime = data.getExpectedStartTime();
this.isRaceStarted = data.isRaceStarted();
}
public void setTimeZone (TimeZone timeZone) {
DATE_TIME_FORMAT.setTimeZone(timeZone);
}
public void updateState (RaceStartData data) {
this.timeTillStart = data.getRaceStartTime();
}
public String getRaceTimeStr () {
long raceTime = serverSystemTime - expectedStartTime;
if (raceTime < 0) {
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
} else {
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
}
}
public long getTimeTillStart () {
return (expectedStartTime - serverSystemTime);
}
public double getWindSpeed() {
return windSpeed.doubleValue();
}
public ReadOnlyDoubleProperty windSpeedProperty() {
return windSpeed.getReadOnlyProperty();
}
public ReadOnlyDoubleProperty windDirectionProperty() {
return windDirection.getReadOnlyProperty();
}
public long getRaceTime() {
return serverSystemTime;
}
public boolean isRaceStarted () {
return isRaceStarted;
}
public void setBoats(Collection<ClientYacht> clientYachts) {
playerPositions.setAll(clientYachts);
}
public void sortPlayers() {
playerPositions.sort((yacht1, yacht2) -> Integer.compare(yacht2.getLegNumber(),
yacht1.getLegNumber()));
}
public ObservableList<ClientYacht> getPlayerPositions() {
return playerPositions;
}
public void storeCollision(ClientYacht yacht) {
collisions.add(yacht);
for (CollisionListener collisionListener : collisionListeners) {
collisionListener.notifyCollision(yacht.getLocation());
}
}
public void addCollisionListener(CollisionListener collisionListener) {
collisionListeners.add(collisionListener);
}
public void removeCollisionListener(CollisionListener collisionListener) {
collisionListeners.remove(collisionListener);
}
}
@@ -0,0 +1,410 @@
package seng302.model;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
* not used anymore.
*/
public class ServerYacht extends Observable {
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
public static final Double TURN_STEP = 5.0;
//Boat info
private String boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private BoatStatus boatStatus;
private Color boatColor;
//Location
private Double lastHeading;
private Boolean sailIn;
private Double heading;
private GeoPoint lastLocation;
private GeoPoint location;
private Double currentVelocity;
private Boolean isAuto;
private Double autoHeading;
private Integer legNumber;
//Mark Rounding
private Integer currentMarkSeqID;
private Boolean hasEnteredRoundingZone;
private Mark closestCurrentMark;
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.boatStatus = BoatStatus.PRESTART;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.sailIn = false;
this.isAuto = false;
this.location = new GeoPoint(57.67046, 11.83751);
this.lastLocation = location;
this.heading = 120.0; //In degrees
this.currentVelocity = 0d; //in mms-1
this.currentMarkSeqID = 0;
this.legNumber = 0;
this.boatColor = Colors.getColor(sourceId - 1);
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
}
/**
* Changes the boats current currentVelocity by a set amount, positive or negative
*
* @param velocityChange The ammount to change the currentVelocity by, in mms-1
*/
public void changeVelocity(Double velocityChange) {
currentVelocity += velocityChange;
}
/**
* Updates the boat to a new GeoPoint whilst preserving the last location
*
* @param secondsElapsed The seconds elapsed since the last update of this yacht
*/
public void updateLocation(Double secondsElapsed) {
lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed);
}
public void setLocation(GeoPoint geoPoint) {
location = geoPoint;
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
}
/**
* Adjusts the heading of the boat by a given amount, while recording the boats last heading.
*
* @param amount the amount by which to adjust the boat heading.
*/
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
lastHeading = heading;
heading = (double) Math.floorMod(newVal.longValue(), 360L);
}
/**
* Swaps the boats direction from one side of the wind to the other.
*/
public void tackGybe(Double windDirection) {
if (isAuto) {
disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double newVal = (-2 * normalizedHeading) + heading;
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
setAutoPilot(newHeading);
}
}
/**
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
*
* @param thisHeading The heading to move the boat towards.
*/
private void setAutoPilot(Double thisHeading) {
isAuto = true;
autoHeading = thisHeading;
}
/**
* Disables the auto pilot function.
*/
public void disableAutoPilot() {
isAuto = false;
}
/**
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
* in the event that the boat is within the range of 1 turn step of its goal.
*/
public void runAutoPilot() {
if (isAuto) {
turnTowardsHeading(autoHeading);
if (Math.abs(heading - autoHeading)
<= TURN_STEP) { //Cancel when within 1 turn step of target.
isAuto = false;
}
}
}
public void toggleSailIn() {
sailIn = !sailIn;
}
public void turnUpwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
}
public void turnDownwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
/**
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
* and uses this to calculate a heading to move the yacht towards.
*/
public void turnToVMG() {
if (isAuto) {
disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double optimalHeading;
HashMap<Double, Double> optimalPolarMap;
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
} else {
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
}
optimalHeading = optimalPolarMap.keySet().iterator().next();
if (normalizedHeading > 180) {
optimalHeading = 360 - optimalHeading;
}
// Take optimal heading and turn into a boat heading rather than a wind heading.
optimalHeading =
optimalHeading + GameState.getWindDirection();
setAutoPilot(optimalHeading);
}
}
/**
* Takes a given heading and rotates the boat towards that heading. This does not care about
* being upwind or downwind, just which direction will reach a given heading faster.
*
* @param newHeading The heading to turn the yacht towards.
*/
private void turnTowardsHeading(Double newHeading) {
Double newVal = heading - newHeading;
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
adjustHeading(TURN_STEP / 5);
} else {
adjustHeading(-TURN_STEP / 5);
}
}
/**
* Returns a heading normalized for the wind direction. Heading direction into the wind is 0,
* directly away is 180.
*
* @return The normalized heading accounting for wind direction.
*/
private Double normalizeHeading() {
Double normalizedHeading = heading - GameState.windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) {
return 0;
}
return sourceId;
}
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
public String getHullID() {
if (hullID == null) {
return "";
}
return hullID;
}
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) {
return "";
}
return country;
}
public void setBoatName(String name) {
boatName = name;
shortName = name.split(" ")[0];
}
public GeoPoint getLocation() {
return location;
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
public Boolean getSailIn() {
return sailIn;
}
@Override
public String toString() {
return boatName;
}
public Double getCurrentVelocity() {
return currentVelocity;
}
public void setCurrentVelocity(Double currentVelocity) {
this.currentVelocity = currentVelocity;
}
public Integer getCurrentMarkSeqID() {
return currentMarkSeqID;
}
public GeoPoint getLastLocation() {
return lastLocation;
}
public Mark getClosestCurrentMark() {
return closestCurrentMark;
}
public void setClosestCurrentMark(Mark closestCurrentMark) {
this.closestCurrentMark = closestCurrentMark;
}
public void setHasEnteredRoundingZone(Boolean hasEnteredRoundingZone) {
this.hasEnteredRoundingZone = hasEnteredRoundingZone;
}
public void setHasPassedLine(Boolean hasPassedLine) {
this.hasPassedLine = hasPassedLine;
}
public void setHasPassedThroughGate(Boolean hasPassedThroughGate) {
this.hasPassedThroughGate = hasPassedThroughGate;
}
public BoatStatus getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(BoatStatus boatStatus) {
this.boatStatus = boatStatus;
}
public void incrementMarkSeqID() {
currentMarkSeqID++;
}
public Boolean hasEnteredRoundingZone() {
return hasEnteredRoundingZone;
}
public Boolean hasPassedThroughGate() {
return hasPassedThroughGate;
}
public Boolean hasPassedLine() {
return hasPassedLine;
}
public void incrementLegNumber() {
legNumber++;
}
public Integer getLegNumber() {
return legNumber;
}
public void setBoatColor(Color color) {
this.boatColor = color;
}
public Color getBoatColor() {
return boatColor;
}
}
@@ -0,0 +1,130 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
public class CompoundMark {
private int compoundMarkId;
private String name;
private List<Mark> marks = new ArrayList<>();
private GeoPoint midPoint;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
this.marks.addAll(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
this.midPoint = marks.get(0);
}
}
/**
* Prints out compoundMark's info and its marks, good for testing
* @return a string showing its details
*/
@Override
public String toString(){
String info = String.format(
"CompoundMark: %d (%s), [%s", compoundMarkId, name, marks.get(0).toString()
);
if (marks.size() > 1) {
info += String.format(", %s", marks.get(1).toString());
}
return info + "]";
}
public int getId() {
return compoundMarkId;
}
public void setId (int markID) {
this.compoundMarkId = markID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setRoundingSide(RoundingSide roundingSide) {;
switch (roundingSide) {
case SP:
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
getSubMark(2).setRoundingSide(RoundingSide.PORT);
break;
case PS:
getSubMark(1).setRoundingSide(RoundingSide.PORT);
getSubMark(2).setRoundingSide(RoundingSide.STARBOARD);
break;
case PORT:
getSubMark(1).setRoundingSide(RoundingSide.PORT);
break;
case STARBOARD:
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
break;
}
}
/**
* Returns the mark contained in the compound mark. Marks are numbered 1 to n;
* @param singleMarkId the id of the desired mark contained in this compound mark.
* @return the desired mark. Returns null if the ID is not in range (1, NUM_MARKS)
*/
public Mark getSubMark(int singleMarkId) {
try {
return marks.get(singleMarkId - 1);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
/**
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
*
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
*/
public GeoPoint getMidPoint() {
return midPoint;
}
/**
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
* specific singleMark or the list of marks.
*
* @return True if the compound mark is a gate, false otherwise.
*/
public boolean isGate () {
return marks.size() > 1;
}
/**
* Returns the list of marks in the compoundMark
*
* @return All marks contained in this mark.
*/
public List<Mark> getMarks () {
return marks;
}
@Override
public int hashCode() {
int hash = 0;
for (Mark mark : marks) {
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
}
return hash + getName().hashCode() + Integer.hashCode(getId());
}
}
@@ -0,0 +1,35 @@
package seng302.model.mark;
/**
* Stores the data for the cornering of a mark.
*/
public class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) {
this.seqID = seqID;
this.compoundMarkID = compoundMarkID;
this.rounding = rounding;
this.zoneSize = zoneSize;
}
public Integer getSeqID() {
return seqID;
}
public Integer getCompoundMarkID() {
return compoundMarkID;
}
public String getRounding() {
return rounding;
}
public Integer getZoneSize() {
return zoneSize;
}
}
@@ -0,0 +1,86 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
/**
* An abstract class to represent general marks
* Created by Haoming Yin (hyi25) on 17/3/17.
*/
public class Mark extends GeoPoint {
@FunctionalInterface
public interface PositionListener {
void notifyPositionChange(Mark mark, double lat, double lon);
}
private int seqID;
private String name;
private int sourceID;
private List<PositionListener> positionListeners = new ArrayList<>();
private RoundingSide roundingSide;
public Mark(String name, int seqID, double lat, double lng, int sourceID) {
super(lat, lng);
this.name = name;
this.sourceID = sourceID;
this.seqID = seqID;
}
/**
* Prints out mark's info and its geo location, good for testing
* @return a string showing its details
*/
@Override
public String toString() {
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, getLat(), getLng());
}
public int getSeqID() {
return seqID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSourceID() {
return sourceID;
}
public RoundingSide getRoundingSide() {
return roundingSide;
}
public void setRoundingSide(RoundingSide roundingSide) {
this.roundingSide = roundingSide;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
public void updatePosition (double lat, double lon) {
this.setLat(lat);
this.setLng(lon);
for (PositionListener listener : positionListeners) {
listener.notifyPositionChange(this, lat, lon);
}
}
public void addPositionListener (PositionListener listener) {
positionListeners.add(listener);
}
public void removePositionListener (PositionListener listener) {
positionListeners.remove(listener);
}
}
@@ -0,0 +1,138 @@
package seng302.model.mark;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.util.*;
/**
* Class to hold the order of the marks in the race.
*/
public class MarkOrder {
private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private Set<Mark> allMarks;
public MarkOrder(){
loadRaceProperties();
}
/**
* @return An ordered list of marks in the race
* OR null if the mark order could not be loaded
*/
public List<CompoundMark> getMarkOrder() {
if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated");
return null;
}
return Collections.unmodifiableList(raceMarkOrder);
}
/**
* @param seqID The seqID of the current mark the boat is heading to
* @return A Boolean indicating if this coming mark is the last one (finish line)
*/
public Boolean isLastMark(Integer seqID) {
return seqID == raceMarkOrder.size() - 1;
}
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
*/
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID - 1);
}
public CompoundMark getCurrentMark(Integer currentSeqID) {
return raceMarkOrder.get(currentSeqID);
}
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark following the mark that the boat is heading to
* @throws IndexOutOfBoundsException if there is no next mark. Check using {@link
* #isLastMark(Integer)}
*/
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1);
}
public Set<Mark> getAllMarks(){
return Collections.unmodifiableSet(allMarks);
}
/**
* Loads the race order from an XML string
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
allMarks = new HashSet<>();
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xml)));
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.error("Failed to read generated race XML");
return null;
}
RaceXMLData data = XMLParser.parseRace(doc);
if (data != null){
logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
course.add(compoundMark);
allMarks.addAll(compoundMark.getMarks());
}
return course;
}
return null;
}
/**
* Load the raceXML and mark order
*/
private void loadRaceProperties(){
XMLGenerator generator = new XMLGenerator();
generator.setRace(new Race());
String raceXML = generator.getRaceAsXml();
if (raceXML == null){
logger.error("Failed to generate raceXML (for race properties)");
return;
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
@@ -0,0 +1,72 @@
package seng302.model.stream.packets;
public enum PacketType {
HEARTBEAT,
RACE_STATUS,
DISPLAY_TEXT_MESSAGE,
RACE_XML,
REGATTA_XML,
BOAT_XML,
RACE_START_STATUS,
YACHT_EVENT_CODE,
YACHT_ACTION_CODE,
CHATTER_TEXT,
BOAT_LOCATION,
MARK_ROUNDING,
COURSE_WIND,
AVG_WIND,
BOAT_ACTION,
OTHER,
RACE_REGISTRATION_REQUEST,
RACE_REGISTRATION_RESPONSE,
RACE_CUSTOMIZATION_REQUEST,
RACE_CUSTOMIZATION_RESPONSE;
public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){
case 1:
return HEARTBEAT;
case 12:
return RACE_STATUS;
case 20:
return DISPLAY_TEXT_MESSAGE;
case 26:
switch (payload[9]) { //The type of XML message
case 5:
return REGATTA_XML;
case 6:
return RACE_XML;
case 7:
return BOAT_XML;
}
case 27:
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;
case 101:
return RACE_REGISTRATION_REQUEST;
case 102:
return RACE_REGISTRATION_RESPONSE;
case 103:
return RACE_CUSTOMIZATION_REQUEST;
case 104:
return RACE_CUSTOMIZATION_RESPONSE;
default:
}
return OTHER;
}
}
@@ -0,0 +1,37 @@
package seng302.model.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, payload);
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,36 @@
package seng302.model.stream.parser;
/**
* Simple data wrapper for mark rounding data packet.
*/
public class MarkRoundingData {
private int boatId;
private int markId;
private int roundingSide;
private long timeStamp;
public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
this.boatId = boatId;
this.markId = markId;
this.roundingSide = roundingSide;
this.timeStamp = timeStamp;
}
public int getBoatId() {
return boatId;
}
public int getMarkId() {
return markId;
}
public int getRoundingSide() {
return roundingSide;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,50 @@
package seng302.model.stream.parser;
public class PositionUpdateData {
public enum DeviceType {
YACHT_TYPE,
MARK_TYPE
}
private int deviceId;
private DeviceType type;
private double lat;
private double lon;
private double heading;
private double groundSpeed;
public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
double heading, double groundSpeed) {
this.deviceId = deviceId;
this.type = type;
this.lat = lat;
this.lon = lon;
this.heading = heading;
this.groundSpeed = groundSpeed;
}
public int getDeviceId() {
return deviceId;
}
public DeviceType getType() {
return type;
}
public double getLat() {
return lat;
}
public double getLon() {
return lon;
}
public double getHeading() {
return heading;
}
public double getGroundSpeed() {
return groundSpeed;
}
}
@@ -0,0 +1,35 @@
package seng302.model.stream.parser;
/**
* Class for storing data parsed from race start status packet
*/
public class RaceStartData {
private long raceId;
private long raceStartTime;
private int notificationType;
private long timeStamp;
public RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) {
this.raceId = raceId;
this.raceStartTime = raceStartTime;
this.notificationType = notificationType;
this.timeStamp = timeStamp;
}
public long getRaceId() {
return raceId;
}
public long getRaceStartTime() {
return raceStartTime;
}
public int getNotificationType() {
return notificationType;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,65 @@
package seng302.model.stream.parser;
import java.util.ArrayList;
import java.util.List;
/**
* Stores parsed data from race status packets
*/
public class RaceStatusData {
//CONVERSION CONSTANTS
private static final double WIND_DIR_FACTOR = 0x4000 / 90; //0x4000 is 90 degrees
private static final double MS_TO_KNOTS = 1.94384;
private double windDirection;
private double windSpeed;
private boolean raceStarted = false;
private long currentTime;
private long expectedStartTime;
private List<long[]> boatData = new ArrayList<>();
public RaceStatusData(
long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) {
windDirection = windDir / WIND_DIR_FACTOR;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
raceStarted = raceStatus == 3;
this.currentTime = currentTime;
this.expectedStartTime = expectedStartTime;
}
public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg, int boatStatus) {
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg, boatStatus});
}
public double getWindDirection() {
return windDirection;
}
public double getWindSpeed() {
return windSpeed;
}
public boolean isRaceStarted() {
return raceStarted;
}
public long getCurrentTime() {
return currentTime;
}
public long getExpectedStartTime() {
return expectedStartTime;
}
/**
* Returns the data for boats collected form race status packets.
*
* @return A list of boat data. Boat data is in the form
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
*/
public List<long[]> getBoatData () {
return boatData;
}
}
@@ -0,0 +1,34 @@
package seng302.model.stream.parser;
/**
* Stores parsed data from yacht event code packet
*/
public class YachtEventData {
private Long subjectId;
private Long incidentId;
private Integer eventId;
private Long timeStamp;
public YachtEventData(Long subjectId, Long incidentId, Integer eventId, Long timeStamp) {
this.subjectId = subjectId;
this.incidentId = incidentId;
this.eventId = eventId;
this.timeStamp = timeStamp;
}
public Long getSubjectId() {
return subjectId;
}
public Long getIncidentId() {
return incidentId;
}
public Integer getEventId() {
return eventId;
}
public Long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,53 @@
package seng302.model.stream.xml.generator;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.model.ServerYacht;
/**
* A Race object that can be parsed into XML
*/
public class Race {
private List<ServerYacht> yachts;
private LocalDateTime startTime;
public Race(){
yachts = new ArrayList<>();
startTime = LocalDateTime.now();
}
/**
* Add a boat to the race
* @param yacht The boat to add
*/
public void addBoat(ServerYacht yacht) {
yachts.add(yacht);
}
/**
* Get a list of boats in the race
* @return A List of boats
*/
public List<ServerYacht> getBoats() {
return Collections.unmodifiableList(yachts);
}
/**
* Set the time until the race starts
* @param seconds The time in seconds until the race starts
*/
public void setRaceStartDelay(Integer seconds){
startTime = startTime.plusMinutes(seconds);
}
/**
* Get the time the race starts
* @return The time the race starts
*/
public String getRaceStartTime(){
return startTime.toString();
}
}
@@ -0,0 +1,77 @@
package seng302.model.stream.xml.generator;
/**
* A Race regatta that can be parsed into XML
*/
public class Regatta {
private final Double DEFAULT_ALTITUDE = 0d;
private final Integer DEFAULT_REGATTA_ID = 0;
private Integer id;
private String name;
private String courseName;
private Double latitude;
private Double longitude;
private Double altitude;
private Integer utcOffset;
private Double magneticVariation;
public Regatta(String name, String courseName, Double latitude, Double longitude) {
this.name = name;
this.id = DEFAULT_REGATTA_ID;
this.courseName = courseName;
this.latitude = latitude;
this.longitude = longitude;
this.altitude = DEFAULT_ALTITUDE;
this.utcOffset = 0;
this.magneticVariation = 0d;
}
public void setMagneticVariation(Double magneticVariation){
this.magneticVariation = magneticVariation;
}
public void setUtcOffset(Integer offset){
this.utcOffset = offset;
}
/*
NOTE!! The following getters must follow the JavaBean standard (getPropertyName()), and must be public.
*/
public String getName(){
return name;
}
public String getCourseName(){
return courseName;
}
public Integer getRegattaId(){
return id;
}
public Double getLatitude() {
return latitude;
}
public Double getLongitude() {
return longitude;
}
public Double getAltitude() {
return altitude;
}
public Integer getUtcOffset(){
return utcOffset;
}
public Double getMagneticVariation(){
return magneticVariation;
}
}
@@ -0,0 +1,46 @@
package seng302.model.stream.xml.parser;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
/**
* Process a Document object containing race data in XML format and stores the data.
*/
public class RaceXMLData {
private List<Integer> participants;
private Map<Integer, CompoundMark> compoundMarks;
private List<Corner> markSequence;
private List<Limit> courseLimit;
public RaceXMLData(List<Integer> participants, List<CompoundMark> compoundMarks,
List<Corner> markSequence, List<Limit> courseLimit) {
this.participants = participants;
this.markSequence = markSequence;
this.courseLimit = courseLimit;
this.compoundMarks = new HashMap<>();
for (CompoundMark cMark : compoundMarks) {
this.compoundMarks.put(cMark.getId(), cMark);
}
}
public List<Integer> getParticipants() {
return participants;
}
public Map<Integer, CompoundMark> getCompoundMarks() {
return compoundMarks;
}
public List<Corner> getMarkSequence() {
return markSequence;
}
public List<Limit> getCourseLimit() {
return courseLimit;
}
}
@@ -0,0 +1,49 @@
package seng302.model.stream.xml.parser;
/**
* Stores data from regatta xml packet.
*/
public class RegattaXMLData {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
public RegattaXMLData (Integer regattaID, String regattaName, String courseName,
Double centralLat, Double centralLng, Integer utcOffset) {
this.regattaID = regattaID;
this.regattaName = regattaName;
this.courseName = courseName;
this.centralLat = centralLat;
this.centralLng = centralLng;
this.utcOffset = utcOffset;
}
public Integer getRegattaID() {
return regattaID;
}
public String getRegattaName() {
return regattaName;
}
public String getCourseName() {
return courseName;
}
public Double getCentralLat() {
return centralLat;
}
public Double getCentralLng() {
return centralLng;
}
public Integer getUtcOffset() {
return utcOffset;
}
}
@@ -0,0 +1,254 @@
package seng302.utilities;
import javafx.geometry.Point2D;
import seng302.model.GeoPoint;
public class GeoUtility {
private static double EARTH_RADIUS = 6378.137;
private static Double MS_TO_KNOTS = 1.943844492;
/**
* 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) {
return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0;
}
/**
* WARNING: this function DOES NOT account for wrapping around on lats / longs etc.
* SO BE CAREFUL IN USING THIS FUNCTION
*
* @param p1 GeoPoint 1
* @param p2 GeoPoint 2
* @return GeoPoint midPoint
*/
public static GeoPoint getDirtyMidPoint(GeoPoint p1, GeoPoint p2) {
return new GeoPoint((p1.getLat() + p2.getLat()) / 2, (p1.getLng() + p2.getLng()) / 2);
}
/**
* Calculates the angle between to angular co-ordinates on a sphere in radians.
*
* @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 getBearingRad(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);
return Math.atan2(y, x);
}
/**
* 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;
}
}
/**
* Checks if the line formed by lastLocation and location doesn't intersect the line segment
* formed by mark1 and mark2 See the wiki Mark Rounding algorithm for more info
*
* @param mark1 One mark of the line
* @param mark2 The second mark of the line
* @param lastLocation The last location of the point crossing this line
* @param location The current location of the point crossing this line
* @return 0 if two line segment doesn't intersect, otherwise 1 if they intersect and
* lastLocation is on RHS of the line segment (mark1 to mark2) or 2 if lastLocation on LHS of
* the line segment (mark1 to mark2)
*/
public static Integer checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation,
GeoPoint location) {
boolean enteredDirection = isClockwise(mark1, mark2, lastLocation);
boolean exitedDirection = isClockwise(mark1, mark2, location);
if (enteredDirection != exitedDirection) {
if (!isPointInTriangle(mark1, lastLocation, location, mark2)
&& !isPointInTriangle(mark2, lastLocation, location, mark1)) {
return enteredDirection ? 1 : 2;
}
}
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);
}
/**
* Define vector v1 = p1 - p0 to v2 = p2- p0. This function returns the difference of bearing
* from v1 to v2. For example, if bearing of v1 is 30 deg and bearing of v2 is 90 deg, then the
* difference is 60 deg.
*
* @param bearing1 the bearing of v1
* @param bearing2 the bearing of v2
* @return the difference of bearing from v1 to v2
*/
private static Double getBearingDiff(double bearing1, double bearing2) {
return ((360 - bearing1) + bearing2) % 360;
}
/**
* Check if a geo point ins on the right hand side of the line segment, which
* formed by two geo points v1 to v2. (Algorithm: point is clockwise to the
* line if the bearing difference is less than 180 deg.)
*
* @param v1 one end of the line segment
* @param v2 another end of the line segment
* @param point the point to be tested
* @return true if the point is on the RHS of the line
*/
public static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) {
return getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180;
}
/**
* Given three geo points to form a triangle, the method returns true if the fourth point is
* inside the triangle
*
* @param v1 the vertex of the triangle
* @param v2 the vertex of the triangle
* @param v3 the vertex of the triangle
* @param point the point to be tested
* @return true if the fourth point is inside the triangle
*/
public static Boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) {
// true, if diff of bearing from (v1 to v2) to (v1 to p) is less than 180 deg
boolean isCW = isClockwise(v1, v2, point);
if (isClockwise(v2, v3, point) != isCW) {
return false;
}
if (isClockwise(v3, v1, point) != isCW) {
return false;
}
return true;
}
/**
* @param boatSpeedInKnots Speed in knots
* @return The Boat speed in millimeters per second
*/
public static Double knotsToMMS(Double boatSpeedInKnots) {
return boatSpeedInKnots / MS_TO_KNOTS * 1000;
}
/**
* @param boatSpeedInMMS Speed in millimeters per second
* @return The Boat speed in knots
*/
public static Double mmsToKnots(Double boatSpeedInMMS) {
return boatSpeedInMMS / 1000 * MS_TO_KNOTS;
}
}
@@ -0,0 +1,434 @@
package seng302.utilities;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.*;
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
/**
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
* protocol, and parsing it into basic data types or collections.
*
* Created by kre39 on 23/04/17.
*/
public class StreamParser {
/**
* Extracts and returns the seq num used in the heartbeat packet.
*
* @param packet Packet parsed in to use the payload
* @return the packet sequence number if the packet is of type HEARTBEAT, null otherwise.
*/
public static Long extractHeartBeat(StreamPacket packet) {
if (packet.getType() != PacketType.HEARTBEAT) {
return null;
}
long heartbeat = bytesToLong(packet.getPayload());
System.out.println("heartbeat = " + heartbeat);
return heartbeat;
}
/**
* 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
* @return null if the packet type is not RACE_STATUS, otherwise an instance of RaceStatusData
* containing the parsed packet data.
*/
public static RaceStatusData extractRaceStatus(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_STATUS) {
return null;
}
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));
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// currentTime = format.format((new Date(currentTime)))
RaceStatusData data = new RaceStatusData(
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
);
// long timeTillStart =
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
//
// if (timeTillStart > 0) {
// timeSinceStart = timeTillStart;
// } else {
// if (raceStatus == 4 || raceStatus == 8) {
// raceFinished = true;
// raceStarted = false;
// } else if (!raceStarted) {
// raceStarted = true;
// raceFinished = false;
// }
// timeSinceStart = timeTillStart;
// }
//
//
int noBoats = payload[22];
int raceType = payload[23];
long boatID, estTimeAtNextMark, estTimeAtFinish;
int leg, boatStatus;
for (int i = 0; i < noBoats; i++) {
boatID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
boatStatus = (int) payload[28 + (i * 20)];
estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
leg = (int) payload[29 + (i * 20)];
// boat.setEstimateTimeAtFinish(estTimeAtFinish);
data.addBoatData(boatID, estTimeAtNextMark, estTimeAtFinish, leg, boatStatus);
}
return data;
}
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
// Integer placing = 1;
// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
// for (Yacht boat : boats.values()) {
// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
// placing += 1;
// }
// }
// updatingBoat.setPlacing(placing.toString());
// updatingBoat.setLegNumber(leg);
// boatsPos.putIfAbsent(placing, updatingBoat);
// boatsPos.replace(placing, updatingBoat);
// } else if(updatingBoat.getLegNumber() == null){
// updatingBoat.setPlacing("1");
// updatingBoat.setLegNumber(leg);
// }
// }
/**
* Parses and returns the text from a StreamPacket containing text data for display.
*
* @param packet Packet parsed in to use the payload
* @return A list containing all display message text. Is null if the packet is not of type
* DISPLAY_TEXT_MESSAGE.
*/
public static List<String> extractDisplayMessage(StreamPacket packet) {
if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) {
return null;
}
List<String> message = new ArrayList<>();
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);
message.add(new String(messageTextBytes));
totalLen += 2 + textLength;
}
return message;
}
/**
* Parses and returns an XMLParser containing XML data sent in the given StreamPacket. XML data
* can be for races, boats or the regatta.
*
* @param packet Packet parsed in to use the payload
* @return XMLParse containing xmldata. Returns null if the StreamPacket is not of type
* XML_MESSAGE.
*/
public static Document extractXmlMessage(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_XML &&
packet.getType() != PacketType.REGATTA_XML &&
packet.getType() != PacketType.BOAT_XML) {
return null;
}
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;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
return doc;
}
/**
* Extracts the race start status from the packet and returns it as a long array.
*
* @param packet Packet parsed in to use the payload
* @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if the
* packet type is not of RACE_START_STATUS.
*/
public static RaceStartData extractRaceStartStatus(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_START_STATUS) {
return null;
}
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];
return new RaceStartData(raceId, raceStartTime, notificationType, timeStamp);
}
/**
* Parses the the byte array in a StreamPacket for yacht events to retrieve the necessary info
* and returns it as YachtEventData.
*
* @param packet Packet parsed in to use the payload
* @return the event data in the form of YachtEventData. Returns null if the packet is not of
* type YACHT_EVENT_CODE.
*/
public static YachtEventData extractYachtEventCode(StreamPacket packet) {
if (packet.getType() != PacketType.YACHT_EVENT_CODE) {
return null;
}
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long ackNumber = bytesToLong(Arrays.copyOfRange(payload, 7, 9));
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];
return new YachtEventData(subjectId, incidentId, eventId, timeStamp);
}
/**
* Parses data from a StreamPacket for yacht actions and returns it in a long array.
*
* @param packet Packet parsed in to use the payload
* @return long array of packet data in the form [subjectID, incidentID, eventID, timeStamp].
* Returns null if the packet is not of type YACHT_ACTION_CODE.
*/
public static long[] extractYachtActionCode(StreamPacket packet) {
if (packet.getType() != PacketType.YACHT_ACTION_CODE) {
return null;
}
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];
return new long[]{subjectId, incidentId, eventId, timeStamp};
}
/**
* Strips the message from the chatter text type packets.
*
* @param packet Packet parsed in to use the payload
* @return Chatter text message as a string. Returns null if the packet is not of type
* CHATTER_TEXT.
*/
public static String extractChatterText(StreamPacket packet) {
if (packet.getType() != PacketType.CHATTER_TEXT) {
return null;
}
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
return new String(Arrays.copyOfRange(payload, 3, 3 + length));
}
/**
* Takes the data from a bot location stream packet and parses the id, timeValid, lat, lon,
* heading and groundspeed into a BoatPositionPacket which is returned.
*
* @param packet Packet parsed in to use the payload
* @return BoatPositionPacket containing important boat information. Returns null if the packet
* is not of type BOAT_LOCATION.
*/
public static PositionUpdateData extractBoatLocation(StreamPacket packet) {
if (packet.getType() != PacketType.BOAT_LOCATION) {
return null;
}
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));
double heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
heading = 360.0 / 0xffff * heading; //Convert to degrees.
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
DeviceType type;
if (deviceType == 1) {
type = DeviceType.YACHT_TYPE;
} else {
type = DeviceType.MARK_TYPE;
}
return new PositionUpdateData((int) boatId, type, lat, lon, heading, groundSpeed);
}
/**
* Processes a stream packet for a mark rounding and returns the boatID, markID and timestamp.
*
* @param packet The packet containing the payload
* @return an array containing longs. The values are [boatID, markID, timeStamp]. Returns null
* if packet is not of type MARK_ROUNDING.
*/
public static MarkRoundingData extractMarkRounding(StreamPacket packet) {
if (packet.getType() != PacketType.MARK_ROUNDING) {
return null;
}
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];
return new MarkRoundingData((int) subjectId, markId, roundingSide, timeStamp);
}
/**
* Returns a list containing the string value of data within the given stream packet for course
* wind.
*
* @param packet The packet containing the payload
* @return the string values of the wind packet. Returns null if the packet is not of type
* COURSE_WIND.
*/
public static List<String> extractCourseWind(StreamPacket packet) {
if (packet.getType() != PacketType.COURSE_WIND) {
return null;
}
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int selectedWindId = payload[1];
int loopCount = payload[2];
List<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);
}
return windInfo;
}
/**
* Returns the parsed data from a StreamPacket for average wind data.
*
* @param packet The packet containing the payload
* @return The wind data in the form [rawPeriod, rawSamplePeriod, period2, speed2, period3,
* speed3, period4, speed4, timestamp] or null if the packet is not of type AVG_WIND.
*/
public static long[] extractAvgWind(StreamPacket packet) {
if (packet.getType() != PacketType.AVG_WIND) {
return null;
}
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));
return new long[]{
rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timeStamp
};
}
public static void extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
if (actionType == 1) {
System.out.println("VMG");
} else if (actionType == 2) {
System.out.println("SAILS IN");
} else if (actionType == 3) {
System.out.println("SAILS OUT");
} else if (actionType == 4) {
System.out.println("TACK/GYBE");
} else if (actionType == 5) {
System.out.println("UPWIND");
} else if (actionType == 6) {
System.out.println("DOWNWIND");
}
}
/**
* takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
*
* @param bytes the byte array to conver to Long
* @return a positive long if there is less than 7 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;
}
}
@@ -0,0 +1,163 @@
package seng302.utilities;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta;
import seng302.gameServer.messages.XMLMessageSubType;
/**
* 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,294 @@
package seng302.utilities;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.scene.paint.Color;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.model.ClientYacht;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
/**
* Utilities for parsing XML documents
*/
public class XMLParser {
/**
* 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;
}
}
/**
* Produces a mapping of boat sourceIDS to boat objects created from the given xml document.
* @param doc XML Document Object
* @return Mapping of sourceIds to Boats.
*/
public static Map<Integer, ClientYacht> parseBoats(Document doc) {
Map<Integer, ClientYacht> competingBoats = new HashMap<>();
Element docEle = doc.getDocumentElement();
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);
ClientYacht yacht = new ClientYacht(
XMLParser.getNodeAttributeString(currentBoat, "Type"),
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
XMLParser.getNodeAttributeString(currentBoat, "Country"));
yacht.setColour(Color.web(getNodeAttributeString(currentBoat, "Color")));
if (yacht.getBoatType().equals("Yacht")) {
competingBoats.put(yacht.getSourceId(), yacht);
}
}
}
return competingBoats;
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
* @param doc XML Document Object
* @return Object containing regatta data
*/
public static RegattaXMLData parseRegatta(Document doc) {
Element docEle = doc.getDocumentElement();
Integer regattaID = XMLParser.getElementInt(docEle, "RegattaID");
String regattaName = XMLParser.getElementString(docEle, "RegattaName");
String courseName = XMLParser.getElementString(docEle, "CourseName");
Double centralLat = XMLParser.getElementDouble(docEle, "CentralLatitude");
Double centralLng = XMLParser.getElementDouble(docEle, "CentralLongitude");
Integer utcOffset = XMLParser.getElementInt(docEle, "UtcOffset");
return new RegattaXMLData(
regattaID, regattaName, courseName, centralLat, centralLng, utcOffset
);
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
* @param doc XML document
* @return object containing race data
*/
public static RaceXMLData parseRace(Document doc) {
Element docEle = doc.getDocumentElement();
return new RaceXMLData(
extractParticpantIDs(docEle),
extractCompoundMarks(docEle),
extractMarkOrder(docEle),
extractCourseLimit(docEle)
);
}
/**
* Extracts course limit data
*/
private static List<Limit> extractCourseLimit(Element docEle) {
List<Limit> 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")) {
courseLimit.add(
new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
)
);
}
}
return courseLimit;
}
/**
* Extracts course order data
*/
private static List<Corner> extractMarkOrder (Element docEle) {
List<Corner> 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")) {
compoundMarkSequence.add(
new Corner(
XMLParser.getNodeAttributeInt(cornerNode, "SeqID"),
XMLParser.getNodeAttributeInt(cornerNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cornerNode, "Rounding"),
XMLParser.getNodeAttributeInt(cornerNode, "ZoneSize")
)
);
}
}
return compoundMarkSequence;
}
/**
* Extracts course participants data
*/
private static List<Integer> extractParticpantIDs (Element docEle) {
List<Integer> boatIDs = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
Node pNode = pList.item(i);
if (pNode.getNodeName().equals("Yacht")) {
boatIDs.add(XMLParser.getNodeAttributeInt(pNode, "SourceID"));
}
}
return boatIDs;
}
/**
* Extracts course mark data
*/
private static List<CompoundMark> extractCompoundMarks(Element docEle) {
List<CompoundMark> allMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
CompoundMark cMark;
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
cMark = new CompoundMark(
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cMarkNode, "Name"),
createMarks(cMarkNode)
);
allMarks.add(cMark);
}
}
return allMarks;
}
/**
* Creates marks objects from the given node
*/
private static List<Mark> createMarks(Node compoundMark) {
List<Mark> subMarks = new ArrayList<>();
Integer compoundMarkID = XMLParser.getNodeAttributeInt(compoundMark, "CompoundMarkID");
String cMarkName = XMLParser.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 seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
subMarks.add(mark);
}
}
return subMarks;
}
}
@@ -0,0 +1,365 @@
package seng302.visualiser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatActionMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestMessage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationRequestMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
/**
* 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 {
/**
* Functional interface for receiving packets from client socket.
*/
@FunctionalInterface
public interface ClientSocketListener {
void newPacket();
}
@FunctionalInterface
public interface DisconnectedFromHostListener {
void notifYDisconnection (String message);
}
private class ByteReadException extends Exception {
private ByteReadException(String message) {
super(message);
}
}
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
private List<ClientSocketListener> listeners = new ArrayList<>();
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
private Thread thread;
private Socket socket;
private InputStream is;
private Logger logger = LoggerFactory.getLogger(ClientToServerThread.class);
//Output stream
private OutputStream os;
private Timer upWindPacketTimer = new Timer();
private Timer downWindPacketTimer = new Timer();
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
static public final int PACKET_SENDING_INTERVAL_MS = 100;
private int clientId = -1;
private ByteArrayOutputStream crcBuffer;
private boolean socketOpen = true;
/**
* 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 IOException SocketConnection if fail to connect to ip address and port number
* combination
*/
public ClientToServerThread(String ipAddress, Integer portNumber) throws IOException {
socket = new Socket(ipAddress, portNumber);
is = socket.getInputStream();
os = socket.getOutputStream();
sendRegistrationRequest();
thread = new Thread(this);
thread.start();
}
/**
* 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(!socket.isClosed() && socket.isConnected() && socketOpen) {
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) {
if (streamPackets.size() > 0) {
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
if (PacketType.RACE_REGISTRATION_RESPONSE == PacketType.assignPacketType(type, payload)){
processRegistrationResponse(new StreamPacket(type, payloadLength, timeStamp, payload));
}
else {
if (clientId == -1) continue; // Do not continue if not registered
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
for (ClientSocketListener csl : listeners)
csl.newPacket();
}
}
} else {
logger.warn("Packet has been dropped", 1);
}
}
} catch (ByteReadException e) {
logger.warn("Byte read exception on ClientToServerThread", 1);
notifyDisconnectListeners("Connection to server was interrupted");
closeSocket();
}
}
logger.warn("Closed connection to server", 1);
notifyDisconnectListeners("Connection to server was terminated");
closeSocket();
}
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
CustomizeRequestMessage requestMessage = new CustomizeRequestMessage(reqType, this.clientId, payload);
try {
os.write(requestMessage.getBuffer());
} catch (IOException e) {
logger.error("Could not send customization request");
notifyDisconnectListeners("Could not communicate with server");
closeSocket();
}
}
private void notifyDisconnectListeners (String message) {
if (socketOpen) {
for (DisconnectedFromHostListener listener : disconnectionListeners) {
listener.notifYDisconnection(message);
}
}
}
/**
* Sends a request to the server asking for a source ID
*/
private void sendRegistrationRequest() {
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
try {
os.write(requestMessage.getBuffer());
} catch (IOException e) {
logger.error("Could not send registration request. Exiting");
notifyDisconnectListeners("Failed to register with server");
closeSocket();
}
}
/**
* Accepts a response to the registration request message, and updates the client OR quits
* @param packet The registration requests packet
*/
private void processRegistrationResponse(StreamPacket packet){
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
clientId = sourceId;
return;
}
logger.error("Server Denied Connection, Exiting");
final String alertErrorText;
if (status.equals(RegistrationResponseStatus.FAILURE_FULL)){
alertErrorText = "Server is full";
}
else{
alertErrorText = "Could not connect to server";
}
notifyDisconnectListeners(alertErrorText);
closeSocket();
}
/**
* Sends packets for the given boat action. Special cases are: \n
* - DOWNWIND = Packets are sent every ClientToServerThread.PACKET_SENDING_INTERVAL_MS
* - UPWIND = Packets are sent every ClientToServerThread.PACKET_SENDING_INTERVAL_MS
* - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent.
* @param actionType The boat action that will dictate packets sent.
*/
public void sendBoatAction(BoatAction actionType) {
switch (actionType) {
case MAINTAIN_HEADING:
if (upwindTimerFlag) {
cancelTimer(upWindPacketTimer);
upwindTimerFlag = false;
upWindPacketTimer = new Timer();
}
if (downwindTimerFlag) {
cancelTimer(downWindPacketTimer);
downwindTimerFlag = false;
downWindPacketTimer = new Timer();
}
break;
case DOWNWIND:
if (!downwindTimerFlag) {
downwindTimerFlag = true;
downWindPacketTimer.scheduleAtFixedRate(
new TimerTask() {
@Override
public void run() {
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
}
break;
case UPWIND:
if (!upwindTimerFlag) {
upwindTimerFlag = true;
upWindPacketTimer.scheduleAtFixedRate(
new TimerTask() {
@Override
public void run() {
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
}
break;
default:
sendBoatActionMessage(new BoatActionMessage(actionType));
break;
}
}
/**
* Cancels a packet sending timer.
* @param timer The timer to cancel.
*/
private void cancelTimer (Timer timer) {
timer.cancel();
timer.purge();
}
/**
* Sends a boat action of the given message type.
* @param message The given message type.
*/
private void sendBoatActionMessage(BoatActionMessage message) {
if (clientId != -1) {
try {
os.write(message.getBuffer());
} catch (IOException e) {
logger.warn("IOException on attempting to sendBoatAction from Client");
notifyDisconnectListeners("Cannot communicate with server");
closeSocket();
}
}
}
private void closeSocket() {
try {
socket.close();
socketOpen = false;
} catch (IOException e) {
logger.warn("IOException on attempting to close ClientToServerSocket");
}
}
public void setSocketToClose () {
socketOpen = false;
}
public Queue<StreamPacket> getPacketQueue () {
return streamPackets;
}
public void addStreamObserver (ClientSocketListener streamListener) {
listeners.add(streamListener);
}
public void removeStreamObserver (ClientSocketListener streamListener) {
listeners.remove(streamListener);
}
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.add(listener);
}
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.remove(listener);
}
private int readByte() throws ByteReadException {
int currentByte = -1;
try {
currentByte = is.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
logger.warn("IOException on readByte Client side", 1);
notifyDisconnectListeners("Cannot read from server.");
closeSocket();
}
if (currentByte == -1) {
notifyDisconnectListeners("Cannot read from server.");
closeSocket();
logger.warn("InputStream reach end of stream", 1);
}
return currentByte;
}
private byte[] getBytes(int n) throws ByteReadException {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws ByteReadException {
for (int i = 0; i < n; i++) {
readByte();
}
}
int getClientId () {
return clientId;
}
}
@@ -0,0 +1,431 @@
package seng302.visualiser;
import java.io.IOException;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.TimeZone;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import seng302.gameServer.GameState;
import seng302.gameServer.MainServerThread;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.ClientYacht;
import seng302.model.RaceState;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.MarkRoundingData;
import seng302.model.stream.parser.PositionUpdateData;
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.parser.YachtEventData;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.FinishScreenViewController;
import seng302.visualiser.controllers.LobbyController;
import seng302.visualiser.controllers.LobbyController.CloseStatus;
import seng302.visualiser.controllers.RaceViewController;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
* with a JavaFX Pane to insert itself into.
*/
public class GameClient {
private Pane holderPane;
private ClientToServerThread socketThread;
private MainServerThread server;
private RaceViewController raceView;
private Map<Integer, ClientYacht> allBoatsMap;
private RegattaXMLData regattaData;
private RaceXMLData courseData;
private RaceState raceState = new RaceState();
private LobbyController lobbyController;
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
/**
* Create an instance of the game client. Does not do anything until run with runAsClient()
* runAsHost().
* @param holder The JavaFX Pane that the visual elements for the race will be inserted into.
*/
public GameClient(Pane holder) {
this.holderPane = holder;
}
/**
* Connect to a game at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public void runAsClient(String ipAddress, Integer portNumber) {
try {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
showConnectionError(cause);
tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
socketThread.addStreamObserver(this::parsePackets);
LobbyController lobbyController = loadLobby();
lobbyController.setSocketThread(socketThread);
lobbyController.setPlayerID(socketThread.getClientId());
lobbyController.setPlayerListSource(clientLobbyList);
lobbyController.disableReadyButton();
if (regattaData != null){
lobbyController.setTitle(regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName());
}
else{
lobbyController.setTitle(ipAddress);
lobbyController.setCourseName("");
}
lobbyController.addCloseListener((exitCause) -> {
this.tearDownConnection();
this.loadStartScreen();
});
this.lobbyController = lobbyController;
} catch (IOException ioe) {
showConnectionError("Unable to find server");
Platform.runLater(this::loadStartScreen);
}
}
/**
* Connect to a game as the host at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public void runAsHost(String ipAddress, Integer portNumber) {
server = new MainServerThread();
try {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
this.tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
LobbyController lobbyController = loadLobby();
lobbyController.setSocketThread(socketThread);
lobbyController.setPlayerID(socketThread.getClientId());
lobbyController.setPlayerListSource(clientLobbyList);
if (regattaData != null) {
lobbyController.setTitle("Hosting: " + regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName());
} else {
lobbyController.setTitle("Hosting: " + ipAddress);
lobbyController.setCourseName("");
}
lobbyController.addCloseListener(exitCause -> {
if (exitCause == CloseStatus.READY) {
GameState.resetStartTime();
lobbyController.disableReadyButton();
server.startGame();
} else if (exitCause == CloseStatus.LEAVE) {
tearDownConnection();
loadStartScreen();
}
});
this.lobbyController = lobbyController;
} catch (IOException ioe) {
showConnectionError("Cannot connect to server as host");
Platform.runLater(this::loadStartScreen);
}
}
private void tearDownConnection() {
socketThread.setSocketToClose();
if (server != null) {
server.terminate();
server = null;
}
}
private void loadStartScreen() {
// socketThread.setSocketToClose();
// if (server != null) {
// server.terminate();
// server = null;
// }
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("/views/StartScreenView.fxml"));
try {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load());
} catch (IOException e) {
showConnectionError("JavaFX crashed. Please restart the app");
}
}
private void showConnectionError (String message) {
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR);
alert.setHeaderText("Connection Error");
alert.setContentText(message);
alert.showAndWait();
});
}
private void startClientToServerThread (String ipAddress, int portNumber) throws IOException {
socketThread = new ClientToServerThread(ipAddress, portNumber);
socketThread.addStreamObserver(this::parsePackets);
}
/**
* Loads a view of the lobby into the clients pane
*
* @return the lobby controller.
*/
private LobbyController loadLobby() {
FXMLLoader fxmlLoader = new FXMLLoader(
GameClient.class.getResource("/views/LobbyView.fxml"));
try {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
return fxmlLoader.getController();
}
private void loadRaceView() {
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/RaceView.fxml");
holderPane.getScene().setOnKeyPressed(this::keyPressed);
holderPane.getScene().setOnKeyReleased(this::keyReleased);
raceView = fxmlLoader.getController();
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
raceView.loadRace(allBoatsMap, courseData, raceState, player);
}
private void loadFinishScreenView() {
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml");
FinishScreenViewController controller = fxmlLoader.getController();
controller.setFinishers(raceState.getPlayerPositions());
}
private FXMLLoader loadFXMLToHolder(String fxmlLocation) {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource(fxmlLocation)
);
try {
final Node fxmlLoaderFX = fxmlLoader.load();
Platform.runLater(() -> {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoaderFX);
});
} catch (IOException e) {
e.printStackTrace();
}
return fxmlLoader;
}
private void parsePackets() {
while (socketThread.getPacketQueue().peek() != null) {
StreamPacket packet = socketThread.getPacketQueue().poll();
switch (packet.getType()) {
case RACE_STATUS:
processRaceStatusUpdate(StreamParser.extractRaceStatus(packet));
if (raceState.getTimeTillStart() <= 5000) {
startRaceIfAllDataReceived();
}
break;
case REGATTA_XML:
regattaData = XMLParser.parseRegatta(
StreamParser.extractXmlMessage(packet)
);
raceState.setTimeZone(
TimeZone.getTimeZone(
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset()))
)
);
break;
case RACE_XML:
courseData = XMLParser.parseRace(
StreamParser.extractXmlMessage(packet)
);
if (raceView != null) {
raceView.updateRaceData(courseData);
}
break;
case BOAT_XML:
allBoatsMap = XMLParser.parseBoats(
StreamParser.extractXmlMessage(packet)
);
clientLobbyList.clear();
allBoatsMap.forEach((id, boat) ->
clientLobbyList.add(boat.getBoatName())
);
raceState.setBoats(allBoatsMap.values());
break;
case RACE_START_STATUS:
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
if (lobbyController != null) lobbyController.updateRaceState(raceState);
break;
case BOAT_LOCATION:
updatePosition(StreamParser.extractBoatLocation(packet));
break;
case MARK_ROUNDING:
updateMarkRounding(StreamParser.extractMarkRounding(packet));
break;
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
break;
}
}
}
private void startRaceIfAllDataReceived() {
if (allXMLReceived() && raceView == null) {
loadRaceView();
}
}
private boolean allXMLReceived() {
return courseData != null && allBoatsMap != null && regattaData != null;
}
/**
* Updates the position of a boat. Boat and position are given in the provided data.
*/
private void updatePosition(PositionUpdateData positionData) {
if (positionData.getType() == DeviceType.YACHT_TYPE) {
if (allXMLReceived() && allBoatsMap.containsKey(positionData.getDeviceId())) {
ClientYacht yacht = allBoatsMap.get(positionData.getDeviceId());
yacht.updateLocation(positionData.getLat(),
positionData.getLon(), positionData.getHeading(),
positionData.getGroundSpeed());
}
} else if (positionData.getType() == DeviceType.MARK_TYPE) {
//CompoundMark mark = courseData.getCompoundMarks().get(positionData.getDeviceId());
}
}
/**
* Updates the boat as having passed the mark. Boat and mark are given by the ids in the
* provided data.
*
* @param roundingData Contains data for the rounding of a mark.
*/
private void updateMarkRounding(MarkRoundingData roundingData) {
if (allXMLReceived()) {
ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
clientYacht.roundMark(
courseData.getCompoundMarks().get(roundingData.getMarkId()),
roundingData.getTimeStamp(),
raceState.getRaceTime() - roundingData.getTimeStamp()
);
}
updatePlayerPositions();
}
private void processRaceStatusUpdate(RaceStatusData data) {
if (allXMLReceived()) {
raceState.updateState(data);
boolean raceFinished = true;
for (ClientYacht yacht : allBoatsMap.values()) {
if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) {
raceFinished = false;
}
}
for (long[] boatData : data.getBoatData()) {
ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]);
clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
clientYacht.setEstimateTimeAtFinish(boatData[2]);
// int legNumber = (int) boatData[3];
clientYacht.setBoatStatus((int) boatData[4]);
// if (legNumber != clientYacht.getLegNumber()) {
// clientYacht.setLegNumber(legNumber);
// }
}
if (raceFinished) {
close();
loadFinishScreenView();
}
}
}
private void updatePlayerPositions() {
raceState.sortPlayers();
for (ClientYacht yacht : raceState.getPlayerPositions()) {
yacht.setPosition(raceState.getPlayerPositions().indexOf(yacht) + 1);
}
}
private void close() {
socketThread.setSocketToClose();
}
/**
* Handle the key-pressed event from the text field.
* @param e The key event triggering this call
*/
private void keyPressed(KeyEvent e) {
switch (e.getCode()) {
case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break;
case PAGE_UP: // upwind
socketThread.sendBoatAction(BoatAction.UPWIND); break;
case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
}
}
private void keyReleased(KeyEvent e) {
switch (e.getCode()) {
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
case SHIFT: // sails in/sails out
socketThread.sendBoatAction(BoatAction.SAILS_IN);
allBoatsMap.get(socketThread.getClientId()).toggleSail();
break;
case PAGE_UP:
case PAGE_DOWN:
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
}
}
public RaceXMLData getCourseData() {
return courseData;
}
/**
* Tells race view to show a collision animation.
*/
private void showCollisionAlert(YachtEventData yachtEventData) {
// 33 is the agreed code to show collision
if (yachtEventData.getEventId() == 33) {
raceState.storeCollision(
allBoatsMap.get(
yachtEventData.getSubjectId().intValue()
)
);
}
}
}
@@ -0,0 +1,847 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.util.Duration;
import seng302.model.ClientYacht;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ClientYacht;
import seng302.model.Colors;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.AnnotationBox;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.CourseBoundary;
import seng302.visualiser.fxObjects.Gate;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.Marker;
import seng302.visualiser.map.Boundary;
import seng302.visualiser.map.CanvasMap;
/**
* Created by cir27 on 20/07/17.
*/
public class GameView extends Pane {
private double bufferSize = 50;
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private double panelHeight = 960;
private double canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private double metersPerPixelX, metersPerPixelY;
final double SCALE_DELTA = 1.1;
private Text fpsDisplay = new Text();
private Polygon raceBorder = new CourseBoundary();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker> markerObjects;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
private ObservableList<Node> gameObjects;
private BoatObject selectedBoat = null;
private Group annotationsGroup = new Group();
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private Group trails = new Group();
private Group markers = new Group();
private List<CompoundMark> course = new ArrayList<>();
private ImageView mapImage = new ImageView();
//FRAME RATE
private AnimationTimer timer;
private int NUM_SAMPLES = 10;
private final long[] frameTimes = new long[NUM_SAMPLES];
private Double frameRate = 60.0;
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
private ClientYacht playerYacht;
private double windDir = 0.0;
double scaleFactor = 1;
private void zoomOut() {
scaleFactor = 0.1;
if (this.getScaleX() > 0.5) {
this.setScaleX(this.getScaleX() - scaleFactor);
this.setScaleY(this.getScaleY() - scaleFactor);
}
}
private void zoomIn() {
scaleFactor = 0.10;
if (this.getScaleX() < 2.5) {
this.setScaleX(this.getScaleX() + scaleFactor);
this.setScaleY(this.getScaleY() + scaleFactor);
}
}
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private void trackBoat() {
if (selectedBoat != null) {
double x = selectedBoat.getBoatLayoutX();
double y = selectedBoat.getBoatLayoutY();
double displacementX = this.getWidth();
double displacementY = this.getHeight();
this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX());
this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY());
} else {
this.setLayoutX(0);
this.setLayoutY(0);
}
}
public GameView () {
gameObjects = this.getChildren();
// create image view for map, bind panel size to image
gameObjects.add(mapImage);
gameObjects.add(raceBorder);
gameObjects.add(markers);
initializeTimer();
}
private void initializeTimer() {
Arrays.fill(frameTimes, 1_000_000_000 / 60);
timer = new AnimationTimer() {
private long lastTime = 0;
private int FPSCount = 30;
private Double frameRate = 60.0;
private int index = 0;
private boolean arrayFilled = false;
private long sum = 1_000_000_000 / 3;
@Override
public void handle(long now) {
trackBoat();
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);
}
}
lastTime = now;
}
}
boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation());
}
};
}
/**
* 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.getLat(), minLonPoint.getLng());
// 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.getLat(), minLonPoint.getLng());
GeoPoint originPos = GeoUtility
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
// distance from origin corner to bottom right corner of the panel
double distanceFromOriginToBottomRight = Math.sqrt(
Math.pow(panelHeight * metersPerPixelY, 2) + Math
.pow(panelWidth * metersPerPixelX, 2));
double bearingFromOriginToBottomRight = Math
.toDegrees(Math.atan2(panelWidth, -panelHeight));
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());
mapImage.fitWidthProperty().bind(((AnchorPane) this.getParent()).heightProperty());
mapImage.fitHeightProperty().bind(((AnchorPane) this.getParent()).heightProperty());
}
// TODO: 16/08/17 Break up this function
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
marker.setLayoutX(p2d.getX());
marker.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(i-1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = 0;
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker marker = new Marker(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker m1, Marker m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
* border already exists. Assumes the border is larger than the course.
*
* @param border the race border to be drawn.
*/
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
}
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
// TODO: 16/08/17 initialize zooming internal to GameView only
/**
* Enables zoom. Has to be called after this is added to a scene.
*/
public void enableZoom () {
if (this.getScene() != null) {
this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
if (event.getCode() == KeyCode.Z) {
zoomIn();
} else if (event.getCode() == KeyCode.X) {
zoomOut();
}
});
}
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
private void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
// drawGoogleMap();
}
private void setSelectedBoat(BoatObject bo, Boolean isSelected) {
if (this.selectedBoat == bo && !isSelected) {
this.selectedBoat = null;
boatObjects.forEach((boat, group) ->
group.setIsSelected(false)
);
} else if (isSelected) {
this.selectedBoat = bo;
for (BoatObject group : boatObjects.values()) {
if (group != bo) {
group.setIsSelected(false);
}
}
}
}
/**
* Draws all the boats.
* @param yachts The yachts to set in the race
*/
public void setBoats(List<ClientYacht> yachts) {
BoatObject newBoat;
final List<Group> wakes = new ArrayList<>();
for (ClientYacht clientYacht : yachts) {
Paint colour = clientYacht.getColour();
newBoat = new BoatObject();
newBoat.addSelectedBoatListener(this::setSelectedBoat);
newBoat.setFill(colour);
boatObjects.put(clientYacht, newBoat);
createAndBindAnnotationBox(clientYacht, colour);
// wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake());
boatObjectGroup.getChildren().add(newBoat);
trails.getChildren().add(newBoat.getTrail());
// TODO: 1/08/17 Make this less vile to look at.
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
annotations.get(boat).setLocation(p2d.getX(), p2d.getY());
bo.setTrajectory(
heading,
velocity,
metersPerPixelX,
metersPerPixelY);
});
}
annotationsGroup.getChildren().addAll(annotations.values());
Platform.runLater(() -> {
gameObjects.addAll(trails);
gameObjects.addAll(wakes);
gameObjects.addAll(annotationsGroup);
gameObjects.addAll(boatObjectGroup);
});
}
private void createAndBindAnnotationBox(ClientYacht clientYacht, Paint colour) {
AnnotationBox newAnnotation = new AnnotationBox();
newAnnotation.setFill(colour);
newAnnotation.addAnnotation(
"name", "Player: " + clientYacht.getShortName()
);
// newAnnotation.addAnnotation(
// "velocity",
// yacht.getVelocityProperty(),
// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
// );
// newAnnotation.addAnnotation(
// "nextMark",
// yacht.timeTillNextProperty(),
// (time) -> {
// DateFormat format = new SimpleDateFormat("mm:ss");
// return format.format(time);
// }
// );
// newAnnotation.addAnnotation(
// "lastMark",
// yacht.timeTillNextProperty(),
// (time) -> {
// DateFormat format = new SimpleDateFormat("mm:ss");
// return format.format(time);
// }
// );
annotations.put(clientYacht, newAnnotation);
}
private void drawFps(Double fps) {
Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps))));
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 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) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* 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(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
/**
* Find the number of meters per pixel.
*/
private void findMetersPerPixel() {
Point2D p1, p2;
GeoPoint g1, g2;
double theta, distance, dx, dy, dHorizontal, dVertical;
g1 = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
g2 = new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng());
p1 = findScaledXY(new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng()));
p2 = findScaledXY(new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng()));
theta = GeoUtility.getBearingRad(g1, g2);
distance = GeoUtility.getDistance(g1, g2);
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;
}
public void setAnnotationVisibilities(boolean teamName, boolean velocity, boolean estTime,
boolean legTime, boolean trail, boolean wake) {
for (BoatObject boatObject : boatObjects.values()) {
boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
}
for (AnnotationBox ag : annotations.values()) {
ag.setAnnotationVisibility("name", teamName);
ag.setAnnotationVisibility("velocity", velocity);
ag.setAnnotationVisibility("nextMark", estTime);
ag.setAnnotationVisibility("lastMark", legTime);
}
}
public void setFPSVisibility(boolean visibility) {
fpsDisplay.setVisible(visibility);
}
public void selectBoat(ClientYacht selectedClientYacht) {
boatObjects.forEach((boat, group) ->
group.setIsSelected(boat == selectedClientYacht)
);
}
public void pauseRace() {
timer.stop();
}
public void setWindDir(double windDir) {
this.windDir = windDir;
}
public void startRace() {
timer.start();
}
public ClientYacht getPlayerYacht() {
return playerYacht;
}
public void setBoatAsPlayer (ClientYacht playerYacht) {
this.playerYacht = playerYacht;
playerYacht.toggleSail();
boatObjects.get(playerYacht).setAsPlayer();
CompoundMark currentMark = course.get(playerYacht.getLegNumber());
for (Mark mark : currentMark.getMarks()) {
markerObjects.get(mark).showNextExitArrow();
}
annotations.get(playerYacht).addAnnotation(
"velocity",
playerYacht.getVelocityProperty(),
(velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
);
Platform.runLater(() -> {
boatObjectGroup.getChildren().remove(boatObjects.get(playerYacht));
gameObjects.add(boatObjects.get(playerYacht));
annotationsGroup.getChildren().remove(annotations.get(playerYacht));
gameObjects.add(annotations.get(playerYacht));
});
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
}
private void updateMarkArrows (ClientYacht yacht, CompoundMark compoundMark, int legNumber) {
//Only show arrows for this and next leg.
if (compoundMark != null) {
for (Mark mark : compoundMark.getMarks()) {
markerObjects.get(mark).showNextExitArrow();
}
}
CompoundMark nextMark = null;
if (legNumber < course.size() - 1) {
nextMark = course.get(legNumber);
for (Mark mark : nextMark.getMarks()) {
markerObjects.get(mark).showNextEnterArrow();
}
}
if (legNumber - 2 >= 0) {
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
if (lastMark != nextMark) {
for (Mark mark : lastMark.getMarks()) {
markerObjects.get(mark).hideAllArrows();
}
}
}
}
/**
* Given yacht geopoint by race view controller, drawCollision will calculate canvas X and Y and
* display a flashing red circle on collision point.
*
* @param collisionPoint yacht collision point
*/
public void drawCollision(GeoPoint collisionPoint) {
Point2D point = findScaledXY(collisionPoint);
double circleRadius = 0.0;
Circle circle = new Circle(point.getX(), point.getY(), circleRadius, Color.RED);
circle.setFill(Color.TRANSPARENT);
circle.setStroke(Color.RED);
circle.setStrokeWidth(3);
Timeline timeline = new Timeline();
timeline.setCycleCount(1);
KeyFrame keyframe1 = new KeyFrame(Duration.ZERO,
new KeyValue(circle.radiusProperty(), 0),
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
KeyFrame keyFrame2 = new KeyFrame(new Duration(1000),
new KeyValue(circle.radiusProperty(), 50),
new KeyValue(circle.strokeProperty(), Color.RED));
KeyFrame keyFrame3 = new KeyFrame(new Duration(1500),
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
timeline.getKeyFrames().addAll(keyframe1, keyFrame2, keyFrame3);
Platform.runLater(() -> gameObjects.add(circle));
timeline.setOnFinished(event -> Platform.runLater(() -> gameObjects.remove(circle)));
timeline.play();
}
public void setFrameRateFXText(Text fpsDisplay) {
this.fpsDisplay = null;
this.fpsDisplay = fpsDisplay;
}
}
@@ -0,0 +1,74 @@
package seng302.visualiser.controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.visualiser.ClientToServerThread;
public class CustomizationController {
@FXML
private TextField nameField;
@FXML
private ColorPicker boatColorPicker;
@FXML
private Button customizeSubmit;
private LobbyController lc;
private ClientToServerThread socketThread;
private Stage windowStage;
public void initialize() {
}
public void setServerThread(ClientToServerThread ctsThread) {
this.socketThread = ctsThread;
}
@FXML
public void submitCustomization() {
System.out.println("Attempting to send");
socketThread.sendCustomizationRequest(CustomizeRequestType.NAME, nameField.getText().getBytes());
// TODO: 16/08/17 ajm412: Turn colors into byte array.
Color color = boatColorPicker.getValue();
short red = (short) (color.getRed() * 255);
short green = (short) (color.getGreen() * 255);
short blue = (short) (color.getBlue() * 255);
byte[] colorArray = new byte[3];
colorArray[0] = (byte) red;
colorArray[1] = (byte) green;
colorArray[2] = (byte) blue;
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
lc.setPlayersColor(color);
windowStage.close();
}
public void setLobbyController(LobbyController lc) {
this.lc = lc;
}
public void setStage(Stage stage) {
this.windowStage = stage;
}
public void setPlayerName(String name) {
this.nameField.setText(name);
}
public void setPlayerColor(Color playerColor) {
this.boatColorPicker.setValue(playerColor);
}
}
@@ -0,0 +1,90 @@
package seng302.visualiser.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
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.model.ClientYacht;
public class FinishScreenViewController implements Initializable {
@FXML
private GridPane finishScreenGridPane;
@FXML
private TableView<ClientYacht> finishOrderTable;
@FXML
private TableColumn<ClientYacht, String> posCol;
@FXML
private TableColumn<ClientYacht, String> boatNameCol;
@FXML
private TableColumn<ClientYacht, String> shortNameCol;
@FXML
private TableColumn<ClientYacht, String> countryCol;
ObservableList<ClientYacht> data = FXCollections.observableArrayList();
@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
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")
);
finishOrderTable.refresh();
}
public void setFinishers(Collection<ClientYacht> participants) {
List<ClientYacht> sorted = new ArrayList<>(participants);
sorted.sort(Comparator.comparingInt(ClientYacht::getPlacing));
finishOrderTable.getItems().setAll(sorted);
}
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,248 @@
package seng302.visualiser.controllers;
import com.sun.media.jfxmedia.logging.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.Colors;
import seng302.model.RaceState;
import seng302.visualiser.ClientToServerThread;
/**
* A class describing the actions of the lobby screen
* Created by wmu16 on 10/07/17.
*/
public class LobbyController {
public enum CloseStatus {
LEAVE,
READY
}
@FunctionalInterface
public interface LobbyCloseListener {
void notify(CloseStatus exitCause);
}
@FXML
private Text lobbyIpText;
@FXML
private Button readyButton;
@FXML
private Button customizeButton;
@FXML
private TextArea playerOneTxt;
@FXML
private TextArea playerTwoTxt;
@FXML
private TextArea playerThreeTxt;
@FXML
private TextArea playerFourTxt;
@FXML
private TextArea playerFiveTxt;
@FXML
private TextArea playerSixTxt;
@FXML
private TextArea playerSevenTxt;
@FXML
private TextArea playerEightTxt;
@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;
@FXML
private Text timeUntilStart;
@FXML
private Text courseNameText;
private List<ImageView> imageViews = new ArrayList<>();
private List<TextArea> listViews = new ArrayList<>();
private RaceState raceState;
private ClientToServerThread socketThread;
private Stage customizeStage;
private Color playersColor;
private int MAX_NUM_PLAYERS = 8;
private Integer playerID;
private List<LobbyCloseListener> lobbyListeners = new ArrayList<>();
private ObservableList<String> players;
/**
* Add all FXObjects to lists and initialize images.
*/
public void initialize() {
Collections.addAll(listViews,
playerOneTxt, playerTwoTxt, playerThreeTxt, playerFourTxt, playerFiveTxt, playerSixTxt,
playerSevenTxt, playerEightTxt
);
Collections.addAll(imageViews,
firstImageView, secondImageView, thirdImageView, fourthImageView,
fifthImageView, sixthImageView, seventhImageView, eighthImageView
);
initialiseImageView();
timeUntilStart.setText("Waiting For Host...");
}
/**
* Updates player names.
*/
private void updatePlayers() {
//Update players if one added.
for (int i = 0; i < players.size(); i++) {
listViews.get(i).setText(players.get(i));
if (playerID == (i + 1)) {
listViews.get(i).setText(listViews.get(i).getText() + " (YOU)");
}
imageViews.get(i).setVisible(true);
}
//Update empty text fields if player left.
for (int i = MAX_NUM_PLAYERS-1; i >= players.size(); i--) {
listViews.get(i).setText("");
imageViews.get(i).setVisible(false);
}
}
/**
* Sets all images and hides them till players join.
*/
private void initialiseImageView() {
for (ImageView viewer : imageViews) {
viewer.setImage(
new Image(
RaceViewController.class.getResourceAsStream(
"/pics/sail.png")
)
);
viewer.setVisible(false);
}
}
@FXML
public void customize() {
Parent root;
try {
FXMLLoader fxmlLoader = new FXMLLoader(LobbyController.class.getResource("/views/customizeView.fxml"));
root = fxmlLoader.load();
root.getStylesheets().add("/css/master.css");
customizeStage = new Stage();
customizeStage.setTitle("Customize Boat");
customizeStage.setScene(new Scene(root, 700, 450));
CustomizationController cc = fxmlLoader.getController();
cc.setServerThread(this.socketThread);
cc.setPlayerName(this.players.get(playerID - 1));
if (this.playersColor == null) {
this.playersColor = Colors.getColor(playerID - 1);
}
cc.setPlayerColor(this.playersColor);
cc.setStage(customizeStage); // pass the stage through so it can be closed later.
cc.setLobbyController(this);
customizeStage.show();
} catch (IOException e) {
Logger.logMsg(4, "Failed to load Customization View from resources.");
}
}
public void setSocketThread(ClientToServerThread thread) {
this.socketThread = thread;
}
@FXML
public void leaveLobbyButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function!
GameState.setCurrentStage(GameStages.CANCELLED);
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.LEAVE);
}
@FXML
public void readyButtonPressed() {
GameState.setCurrentStage(GameStages.PRE_RACE);
// Do countdown logic here
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.READY);
customizeButton.setDisable(true);
}
public void setTitle (String title) {
lobbyIpText.setText(title);
}
public void setCourseName(String courseName){
courseNameText.setText(courseName);
}
public void addCloseListener(LobbyCloseListener listener) {
lobbyListeners.add(listener);
}
public void setPlayerListSource (ObservableList<String> players) {
this.players = players;
players.addListener((ListChangeListener<? super String>) (lcl) ->
Platform.runLater(this::updatePlayers)
);
Platform.runLater(this::updatePlayers);
}
public void setPlayerID(Integer id) {
playerID = id;
}
public void updateRaceState(RaceState raceState){
this.raceState = raceState;
/*if (this.customizeStage != null) {
this.customizeStage.close();
}*/ // TODO: 17/08/17 ajm412: close the customization window if the host starts the game while customizing
if (!customizeButton.isDisabled()) {
customizeButton.setDisable(true);
}
timeUntilStart.setText("Starting in: " + raceState.getRaceTimeStr());
}
public void disableReadyButton () {
readyButton.setDisable(true);
readyButton.setVisible(false);
}
public void setPlayersColor(Color playerColor) {
this.playersColor = playerColor;
}
}
@@ -0,0 +1,625 @@
package seng302.visualiser.controllers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
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.Data;
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.StringConverter;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.ClientYacht;
import seng302.model.RaceState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.annotations.Annotation;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
import seng302.visualiser.fxObjects.BoatObject;
/**
* Controller class that manages the display of a race
*/
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML
private LineChart<String, Double> 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<ClientYacht> yachtSelectionComboBox;
@FXML
private Text fpsDisplay;
@FXML
private Text windSpeedText;
//Race Data
private Map<Integer, ClientYacht> participants;
private Map<Integer, CompoundMark> markers;
private RaceXMLData courseData;
private GameView gameView;
private RaceState raceState;
private Timeline timerTimeline;
private Timer timer = new Timer();
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
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.visibleProperty().setValue(false);
raceSparkLine.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false);
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
}
public void loadRace (
Map<Integer, ClientYacht> participants, RaceXMLData raceData, RaceState raceState,
ClientYacht player) {
this.participants = participants;
this.courseData = raceData;
this.markers = raceData.getCompoundMarks();
this.raceState = raceState;
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
initialiseSparkLine();
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
while (c.next()) {
if (c.wasPermutated()) {
updateOrder(raceState.getPlayerPositions());
updateSparkLine();
}
}
});
updateOrder(raceState.getPlayerPositions());
gameView = new GameView();
gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
gameView.enableZoom();
gameView.setBoatAsPlayer(player);
gameView.startRace();
raceState.addCollisionListener(gameView::drawCollision);
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
gameView.setWindDir(newDirection.doubleValue());
updateWindDirection(newDirection.doubleValue());
});
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
updateWindSpeed(newSpeed.doubleValue());
});
updateWindDirection(raceState.windDirectionProperty().doubleValue());
updateWindSpeed(raceState.getWindSpeed());
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
}
/**
* The important annotations have been changed, update this view
*
* @param importantAnnotationsState The current state of the selected annotations
*/
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
this.importantAnnotations = importantAnnotationsState;
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
}
/**
* Loads the "select annotations" view in a new window
*/
private void loadSelectAnnotationView() {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
Stage stage = new Stage();
// Set controller
ImportantAnnotationController controller = new ImportantAnnotationController(
this, stage
);
fxmlLoader.setController(controller);
// Load FXML and set CSS
fxmlLoader.setLocation(
getClass().getResource("/views/importantAnnotationSelectView.fxml")
);
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene);
stage.show();
controller.loadState(importantAnnotations);
} catch (IOException e) {
e.printStackTrace();
}
}
private void initialiseFPSCheckBox() {
toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
gameView.setFPSVisibility(toggleFps.isSelected())
);
}
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.setValue(2);
annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
setAnnotations((int) annotationSlider.getValue())
);
}
/**
* Used to add any new yachts into the race that may have started late or not have had data received yet
*/
private void updateSparkLine(){
// TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// Collect the racing yachts that aren't already in the chart
sparkLineData.clear();
List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// Create a new data series for new yachts
sparkLineCandidates
.stream()
.filter(yacht -> yacht.getPosition() != null)
.forEach(yacht -> {
Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getSourceId().toString());
yachtData.getData().add(
new Data<>(
Integer.toString(yacht.getLegNumber()),
1.0 + participants.size() - yacht.getPosition()
)
);
sparkLineData.add(yachtData);
});
// Lambda function to sort the series in order of leg (later legs shown more to the right)
sparkLineData.sort((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)
Platform.runLater(() -> {
sparkLineData
.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()));
});
});
}
private void initialiseSparkLine() {
sparklineYAxis.setUpperBound(participants.size() + 1);
raceSparkLine.setCreateSymbols(false);
}
/**
* Updates the yachts sparkline of the desired yacht 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
*/
void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){
for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add(
new Data<>(
Integer.toString(legNumber),
1.0 + participants.size() - yacht.getPlacing()
)
);
}
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
// positionData.getData().add(
// new XYChart.Data<>(
// Integer.toString(legNumber),
// 1.0 + participants.size() - yacht.getPlacing()
// )
// );
}
/**
* gets the rgb string of the yachts colour to use for the chart via css
* @param yachtId id of yacht passed in to get the yachts colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String yachtId){
Color color = participants.get(Integer.valueOf(yachtId)).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 )
);
}
/**
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht
* 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() {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
updateRaceTime();
}
}, 0, 1000);
}
/**
* Iterates over all corners until ones SeqID matches with the yachts 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(BoatObject bg) {
// TODO: 1/08/17 Move to GameView
//
// Integer legNumber = bg.getClientYacht().getLegNumber();
// List<Corner> markSequence = courseData.getMarkSequence();
//
// if (legNumber == 0) {
// return null;
// } else if (legNumber == markSequence.size() - 1) {
// return null;
// }
//
// for (Corner corner : markSequence) {
// if (legNumber + 2 == corner.getSeqID()) {
// return courseData.getCompoundMarks().get(corner.getCompoundMarkID());
// }
// }
// return null;
return null;
}
/**
* Updates the wind direction arrow and text as from info from the StreamParser
* @param direction the from north angle of the wind.
*/
private void updateWindDirection(double direction) {
windDirectionText.setText(String.format("%.1f°", direction));
windArrowText.setRotate(direction);
}
/**
* Updates the speed of the wind as displayed by the info pane.
* @param windSpeed Windspeed in knots.
*/
private void updateWindSpeed(double windSpeed) {
windSpeedText.setText("Speed: " + String.format("%.1f", windSpeed) + " Knots");
}
/**
* Updates the clock for the race
*/
private void updateRaceTime() {
// if (!raceState.isRaceStarted()) {
// timerLabel.setFill(Color.RED);
// timerLabel.setText("Race Finished!");
// } else {
timerLabel.setText(raceState.getRaceTimeStr());
// }
}
/**
* Updates the order of the yachts as from the StreamParser and sets them in the yacht order
* section
*/
private void updateOrder(ObservableList<ClientYacht> yachts) {
List<Text> vboxEntries = new ArrayList<>();
for (int i = 0; i < yachts.size(); i++) {
// System.out.println("yacht == null " + String.valueOf(yacht == null));
if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
.getCode()) { // 3 is finish status
Text textToAdd = new Text(i + 1 + ". " +
yachts.get(i).getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
vboxEntries.add(textToAdd);
} else {
Text textToAdd = new Text(i + 1 + ". " +
yachts.get(i).getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
vboxEntries.add(textToAdd);
}
}
Platform.runLater(() ->
positionVbox.getChildren().setAll(vboxEntries)
);
}
private void updateLaylines(BoatObject bg) {
// TODO: 1/08/17 move to GameView
//
// 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(gameViewController, nextMark)) {
// isUpwind = true;
// } else {
// isUpwind = false;
// }
//
// for(MarkObject mg : gameViewController.getMarkGroups()) {
//
// mg.removeLaylines();
//
// if (mg.getMainMark().getId() == nextMark.getId()) {
//
// SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
// SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
// Point2D markPoint1 = gameViewController
// .findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
// Point2D markPoint2 = gameViewController
// .findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
// HashMap<Double, Double> angleAndSpeed;
// if (isUpwind) {
// angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
// } else {
// angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
// }
//
// Double resultingAngle = angleAndSpeed.keySet().iterator().next();
//
//
// Point2D yachtCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
// Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
// Integer lineFuncResult = GeoUtility.lineFunction(yachtCurrentPos, gateMidPoint, markPoint2);
// Line rightLayline = new Line();
// Line leftLayline = new Line();
// if (lineFuncResult == 1) {
// rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
// leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
// } else if (lineFuncResult == -1) {
// rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
// leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
// }
//
// leftLayline.setStrokeWidth(0.5);
// leftLayline.setStroke(bg.getBoat().getColour());
//
// rightLayline.setStrokeWidth(0.5);
// rightLayline.setStroke(bg.getBoat().getColour());
//
// bg.setLaylines(leftLayline, rightLayline);
// mg.addLaylines(leftLayline, rightLayline);
//
// }
// }
// }
// }
}
private Point2D getPointRotation(Point2D ref, Double distance, Double angle) {
Double newX = ref.getX() + (ref.getX() + distance - ref.getX()) * Math.cos(angle)
- (ref.getY() + distance - ref.getY()) * Math.sin(angle);
Double newY = ref.getY() + (ref.getX() + distance - ref.getX()) * Math.sin(angle)
+ (ref.getY() + distance - ref.getY()) * Math.cos(angle);
return new Point2D(newX, newY);
}
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
return line;
}
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
return line;
}
/**
* Initialised the combo box with any yachts currently in the race and adds the required listener
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
yachtSelectionComboBox.setItems(
FXCollections.observableArrayList(participants.values())
);
//Null check is if the listener is fired but nothing selected
yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
if (selectedBoat != null) {
gameView.selectBoat(selectedBoat);
}
});
}
/**
* Display the list of yachts 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().toString());
} catch (IOException e) {
System.err.println(e.toString());
}
}
private String getMillisToFormattedTime(long milliseconds) {
return String.format("%02d:%02d:%02d",
TimeUnit.MILLISECONDS.toHours(milliseconds),
TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute
);
}
private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) {
// No Annotations
case 0:
gameView.setAnnotationVisibilities(
false, false, false, false, false, false
);
break;
// Important Annotations
case 1:
gameView.setAnnotationVisibilities(
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:
gameView.setAnnotationVisibilities(
true, true, true, true, true, true
);
break;
}
}
/**
* Sets all the annotations of the selected yacht to be visible and all others to be hidden
*
* @param yacht The yacht for which we want to view all annotations
*/
private void setSelectedBoat(ClientYacht yacht) {
// for (BoatObject bg : gameViewController.getBoatGroups()) {
// //We need to iterate over all race groups to get the matching yacht group belonging to this yacht if we
// //are to toggle its annotations, there is no other backwards knowledge of a yacht to its yachtgroup.
// if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
//// updateLaylines(bg);
// bg.setIsSelected(true);
//// selectedBoat = yacht;
// } else {
// bg.setIsSelected(false);
// }
// }
}
public void updateRaceData (RaceXMLData raceData) {
this.courseData = raceData;
gameView.updateBorder(raceData.getCourseLimit());
}
}
@@ -0,0 +1,168 @@
package seng302.visualiser.controllers;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import seng302.gameServer.GameState;
import seng302.visualiser.GameClient;
/**
* A Class describing the actions of the start screen controller
* Created by wmu16 on 10/07/17.
*/
public class StartScreenController implements Initializable {
@FXML
private TextField ipTextField;
@FXML
private TextField portTextField;
@FXML
private GridPane startScreen2;
@FXML
private AnchorPane holder;
GameClient gameClient;
public void initialize(URL url, ResourceBundle resourceBundle) {
// gameClient = new GameClient(holder);
}
//
// /**
// * 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 (IOException e) {
// e.printStackTrace();
// }
// 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() {
// new GameState(getLocalHostIp());
gameClient = new GameClient(holder);
gameClient.runAsHost(getLocalHostIp(), 4942);
// try {
//// String ipAddress = InetAddress.getLocalHost().getHostAddress();
//// new GameState(ipAddress);
//// new MainServerThread();
//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
//// controller.setClientToServerThread(clientToServerThread);
// // 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);
// } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot host");
// alert.setContentText("Oops, failed to host, try to restart.");
// alert.showAndWait();
// e.printStackTrace();
// }
}
/**
* 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
gameClient = new GameClient(holder);
gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942);
// 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);
//
//// controller.setClientToServerThread(clientToServerThread);
//// setContentPane("/views/LobbyView.fxml");
// } 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) {
e.printStackTrace();
}
if (ipAddress == null) {
System.out.println("[HOST] Cannot obtain local host ip address.");
}
// ClientState.setHostIp(ipAddress);
return ipAddress;
}
}
@@ -0,0 +1,13 @@
package seng302.visualiser.controllers.annotations;
/**
* Annotations the user can select as important
*/
public enum Annotation {
SPEED,
WAKE,
TRACK,
NAME,
ESTTIMETONEXTMARK,
LEGTIME
}
@@ -0,0 +1,146 @@
package seng302.visualiser.controllers.annotations;
import java.net.URL;
import java.util.ResourceBundle;
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;
;
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.visualiser.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.visualiser.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,230 @@
package seng302.visualiser.fxObjects;
import java.util.HashMap;
import java.util.Map;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
/**
* Grouping of string objects over a semi transparent background.
*/
public class AnnotationBox extends Group {
@FunctionalInterface
public interface AnnotationFormatter<T> {
String transformString (T input);
}
/**
* Class stores a text object and relationship for updating the text object if needed
*
* @param <T> The type of observable value passed to the annotation, if there is one.
*/
public class Annotation<T> {
private Text text;
private ObservableValue<T> source;
private AnnotationFormatter<T> format;
/**
* Constructor for observing annotation
* @param textObject the javaFX text object the annotation is displayed in
* @param source observable value that the annotation is taken from
* @param formatter interface describing how to format the source data if needed
*/
public Annotation (Text textObject, ObservableValue<T> source, AnnotationFormatter<T> formatter) {
this.text = textObject;
this.source = source;
this.format = formatter;
source.addListener((obs, oldVal, newVal) ->
Platform.runLater(() -> text.setText(format.transformString(newVal)))
);
}
/**
* Constructor for a static annotation
* @param textObject the javaFX text object the annotation is displayed in
* @param annotationText the static value of the test object
*/
public Annotation (Text textObject, String annotationText) {
textObject.setText(annotationText);
text = textObject;
}
private Text getText () {
return text;
}
}
//Text offset constants
private static final double X_OFFSET_TEXT = 20d;
private static final double Y_OFFSET_TEXT_INIT = -35d;
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_ARC_SIZE = 10;
private int visibleAnnotations = 0;
private double backgroundWidth = 145d;
private Rectangle background = new Rectangle();
private Paint theme = Color.BLACK;
private Map<String, Annotation> annotationsByName = new HashMap<>();
/**
* Creates an empty annotation box. The box is offset from (0,0) by (17, -38).
*/
public AnnotationBox() {
this.setCache(true);
background.setX(BACKGROUND_X);
background.setY(BACKGROUND_Y);
background.setWidth(backgroundWidth);
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.SCALE);
this.getChildren().add(background);
}
/**
* Adds an annotation to the box. Use the name to reference the annotation for removal or\
* changing visibility.
* @param annotationName the name of the annotation.
* @param annotation the annotation.
*/
public void addAnnotation (String annotationName, Annotation annotation) {
annotationsByName.put(annotationName, annotation);
Platform.runLater(() -> {
this.getChildren().add(annotation.getText());
visibleAnnotations++;
update();
});
}
/**
* Adds an annotation with a constant text.
* @param annotationName The name of the annotation. Will be used to reference it later.
* @param annotationText The desired text.
*/
public void addAnnotation (String annotationName, String annotationText) {
Text text = getTextObject();
addAnnotation(annotationName, new Annotation(text, annotationText));
}
/**
* Adds an annotation with the given name. The annotation will contain the value of the given
* ObservableValue. The formatter should return a String and takes an object of the same type as
* the ObservableValue as a parameter. The String is how you want the annotation to look.
* @param annotationName The annotation name.
* @param observable The observable value the annotation will display.
* @param formatter A formatting function for the observable value.
* @param <E> The type of ObservableValue.
*/
public <E> void addAnnotation (String annotationName, ObservableValue<E> observable,
AnnotationFormatter<E> formatter) {
Text newText = getTextObject();
addAnnotation(annotationName, new Annotation<>(newText, observable, formatter));
}
/**
* Sets the visibility of the annotation with the given name if it exists.
* @param annotationName The name of the annotation
* @param visibility the desired visibility
*/
public void setAnnotationVisibility (String annotationName, boolean visibility) {
if (annotationsByName.containsKey(annotationName)) {
Text textField = annotationsByName.get(annotationName).text;
boolean currentState = textField.visibleProperty().get();
if (visibility != currentState) {
if (visibility)
visibleAnnotations++;
else
visibleAnnotations--;
}
textField.setVisible(visibility);
update();
}
}
/**
* Removes the annotation with the given name if it exits.
* @param annotationName The name given when the annotation was created.
*/
public void removeAnnotation (String annotationName) {
if (annotationName.contains(annotationName)) {
Platform.runLater(() -> {
this.getChildren().remove(annotationsByName.remove(annotationName).getText());
visibleAnnotations--;
update();
});
annotationsByName.remove(annotationName);
}
}
/**
* Moves the annotation.
* @param x x location
* @param y y location
*/
public void setLocation (double x, double y) {
Platform.runLater(()-> this.relocate(x + BACKGROUND_X, y + BACKGROUND_Y));
}
/**
* Changes the width of the annotation box. Default is 145.
* @param width new width.
*/
public void setWidth (double width) {
backgroundWidth = width;
Platform.runLater(() -> background.setWidth(backgroundWidth));
}
private void update () {
background.setVisible(visibleAnnotations != 0);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * visibleAnnotations);
for (int i = 1; i <= visibleAnnotations; i++) {
Text text = (Text) this.getChildren().get(i);
if (text.visibleProperty().get()) {
text.setX(X_OFFSET_TEXT);
text.setY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * i);
// });
}
}
}
/**
* Returns a text object for an annotation.
* @return The text object
*/
private Text getTextObject() {
Text text = new Text();
text.setFill(theme);
text.setStrokeWidth(2);
// text.setCacheHint(CacheHint.QUALITY);
text.setCache(true);
return text;
}
/**
* Set the colour of the annotation box's border and text colour.
* @param value desired colour.
*/
public void setFill (Paint value) {
theme = value;
background.setStroke(theme);
annotationsByName.forEach((name, annotation) -> annotation.getText().setFill(theme));
}
}
@@ -0,0 +1,400 @@
package seng302.visualiser.fxObjects;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
/**
* 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 BoatObject extends Group {
@FunctionalInterface
public interface SelectedBoatListener {
void notifySelected(BoatObject boatObject, Boolean isSelected);
}
//Constants for drawing
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
private double xVelocity;
private double yVelocity;
private double lastHeading;
private double sailState;
//Graphical objects
private Polyline trail = new Polyline();
private Polygon boatPoly;
private Polygon sail;
private Wake wake;
private Line leftLayLine;
private Line rightLayline;
private double distanceTravelled, lastRotation;
private Point2D lastPoint;
private Paint colour = Color.BLACK;
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
private boolean isPlayer = false;
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
/**
* Creates a BoatGroup with the default triangular boat polygon.
*/
public BoatObject() {
this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
}
/**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* at point (0,0).
*
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
public BoatObject(double... points) {
initChildren(points);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
private void initChildren(double... points) {
boatPoly = new Polygon(points);
boatPoly.setFill(colour);
boatPoly.setFill(this.colour);
boatPoly.setOnMouseEntered(event -> {
boatPoly.setFill(Color.FLORALWHITE);
boatPoly.setStroke(Color.RED);
});
boatPoly.setOnMouseExited(event -> {
boatPoly.setFill(colour);
boatPoly.setFill(this.colour);
boatPoly.setStroke(Color.BLACK);
});
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
// boatPoly.setCacheHint(CacheHint.SPEED);
// annotationBox = new AnnotationBox();
// annotationBox.setFill(colour);
leftLayLine = new Line();
rightLayline = new Line();
trail.getStrokeDashArray().setAll(5d, 10d);
trail.setCache(true);
wake = new Wake(0, -BOAT_HEIGHT);
wake.setVisible(true);
sail = new Polygon(0.0,BOAT_HEIGHT / 4,
0.0, BOAT_HEIGHT);
sailState = 0;
sail.setStrokeWidth(2.0);
sail.setStroke(Color.BLACK);
sail.setFill(Color.TRANSPARENT);
sail.setCache(true);
super.getChildren().clear();
super.getChildren().addAll(boatPoly, sail);
}
public void setFill (Paint value) {
this.colour = value;
boatPoly.setFill(colour);
trail.setStroke(colour);
}
/**
* 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
* @param rotation The rotation by which the boat moves
* @param velocity The velocity the boat is moving
* @param sailIn Boolean to toggle sail state.
*/
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
Double dx = Math.abs(boatPoly.getLayoutX() - x);
Double dy = Math.abs(boatPoly.getLayoutY() - y);
Platform.runLater(() -> {
rotateTo(rotation, sailIn, windDir);
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
if (sailIn) {
// sail.getPoints().clear();
// sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
// sail.getPoints().addAll(0.0, 0.0, 24.0, 0.0);
sail.setLayoutX(x);
sail.setLayoutY(y);
} else {
animateSail();
sail.setLayoutX(x);
sail.setLayoutY(y);
}
wake.setLayoutX(x);
wake.setLayoutY(y);
});
wake.setRotation(rotation, velocity);
// rotateTo(rotation);
// boatPoly.setLayoutX(x);
// boatPoly.setLayoutY(y);
// wake.setLayoutX(x);
// wake.setLayoutY(y);
// wake.rotate(rotation);
// wake.setRotation(rotation, groundSpeed);
// isStopped = false;
// destinationSet = true;
lastRotation = rotation;
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
if (distanceTravelled > 15 && isPlayer) {
distanceTravelled = 0d;
Platform.runLater(() -> trail.getPoints().addAll(x, y));
}
}
private Double normalizeHeading(double heading, double windDirection) {
Double normalizedHeading = heading - windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
private void rotateTo(double heading, boolean sailsIn, double windDir) {
boatPoly.getTransforms().setAll(new Rotate(heading));
if (sailsIn) {
Double sailWindOffset = 30.0;
Double upwindAngleLimit = 15.0;
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
Double normalizedHeading = normalizeHeading(heading, windDir);
if (normalizedHeading < 180) {
sail.getTransforms().setAll(new Rotate(windDir + 90 + sailWindOffset));
sail.getPoints().clear();
sail.getPoints().addAll(0.0, 0.0, 4.0, -1.5, 8.0, -3.0, 12.0, -3.5, 16.0, -3.0, 20.0, -1.5, 24.0, 0.0);
if (normalizedHeading > 90 + sailWindOffset){
sail.getTransforms().setAll(new Rotate(heading + downwindAngleLimit));
}
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
sail.getTransforms().setAll(new Rotate(heading + 90 - upwindAngleLimit));
}
} else {
sail.getTransforms().setAll(new Rotate(windDir + 90 - sailWindOffset));
sail.getPoints().clear();
sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
if (normalizedHeading < 270 - sailWindOffset){
sail.getTransforms().setAll(new Rotate(heading + 180 - downwindAngleLimit));
}
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
sail.getTransforms().setAll(new Rotate(heading + 90 + upwindAngleLimit));
}
}
} else {
sail.getTransforms().setAll(new Rotate(windDir));
}
}
private void animateSail(){
Double[] points = new Double[200];
double amplitude = 2.0;
double period = 10;
for (int i = 0; i < 50; i++) {
points[i * 2] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
points[i * 2 + 1] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
points[199 - (i * 2)] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
points[199 - (i * 2 + 1)] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
}
if (sailState == - 2 * Math.PI) {
sailState = 0;
} else {
sailState = sailState - Math.PI / 5;
}
sail.getPoints().clear();
sail.getPoints().addAll(points);
}
public void updateLocation() {
// double dx = xVelocity / 60;
// double dy = yVelocity / 60;
//
// distanceTravelled += Math.abs(dx) + Math.abs(dy);
// moveGroupBy(dx, dy);
//
// if (distanceTravelled > 70) {
// distanceTravelled = 0d;
//
// if (lastPoint != null) {
// Line l = new Line(
// lastPoint.getX(),
// lastPoint.getY(),
// boatPoly.getLayoutX(),
// boatPoly.getLayoutY()
// );
// l.getStrokeDashArray().setAll(3d, 7d);
// l.setStroke(colour);
// l.setCache(true);
// l.setCacheHint(CacheHint.SPEED);
// lineGroup.getChildren().add(l);
// }
// lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
// }
// wake.updatePosition();
}
// /**
// * 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(GameViewController canvasController, Mark nextMark) {
//
// Double windAngle = StreamParser.getWindDirection();
// GateMark thisGateMark = (GateMark) nextMark;
// SingleMark nextMark1 = thisGateMark.getSingleMark1();
// SingleMark nextMark2 = thisGateMark.getSingleMark2();
// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
//
// Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
// Point2D windTestPoint = 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.
// */
// return boatLineFuncResult.equals(windLineFuncResult);
// return true;
// }
public void setIsSelected(Boolean isSelected) {
updateListener(isSelected);
this.isSelected = isSelected;
setLineGroupVisible(isSelected);
setWakeVisible(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.trail.setVisible(trail);
}
public void setLineGroupVisible(Boolean visible) {
trail.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 Group getWake () {
return wake;
}
public Node getTrail() {
return trail;
}
public Double getBoatLayoutX() {
return boatPoly.getLayoutX();
}
public Double getBoatLayoutY() {
return boatPoly.getLayoutY();
}
/**
* 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(2);
boatPoly.setStrokeLineCap(StrokeLineCap.ROUND);
isPlayer = true;
animateSail();
}
public void setTrajectory(double heading, double velocity, double windDir) {
wake.setRotation(lastHeading - heading, velocity);
rotateTo(heading, false, windDir);
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
lastHeading = heading;
}
public Boolean getSelected() {
return isSelected;
}
public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) {
// wake.setRotation(lastHeading - heading, velocity);
// rotateTo(heading);
// xVelocity = Math.cos(Math.toRadians(heading)) * velocity * scaleFactorX;
// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY;
lastHeading = heading;
}
private void updateListener(Boolean isSelected) {
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
sbl.notifySelected(this, isSelected);
}
}
public void addSelectedBoatListener(SelectedBoatListener sbl) {
selectedBoatListenerListeners.add(sbl);
}
}
@@ -0,0 +1,15 @@
package seng302.visualiser.fxObjects;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
/**
* Polygon with default course border settings.
*/
public class CourseBoundary extends Polygon {
public CourseBoundary() {
this.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
this.setStrokeWidth(3);
this.setFill(new Color(0,0,0,0));
}
}
@@ -0,0 +1,20 @@
package seng302.visualiser.fxObjects;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
/**
* Visual object representing a gate, intended to connect two mark objects.
*/
public class Gate extends Line {
public Gate () {
super.setStrokeWidth(2);
super.getStrokeDashArray().setAll(2d, 5d);
}
public Gate (Paint colour) {
this();
super.setStroke(colour);
}
}
@@ -0,0 +1,147 @@
package seng302.visualiser.fxObjects;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;
// TODO: 16/08/17 this class used to be well written... FeelsBadMan. Maybe lose the ternary operators.
/**
* Factory class for making rounding arrows for mark objects out of JavaFX objects.
*/
public class MarkArrowFactory {
/**
* The side of the boat that will be closest to the mark.
*/
public enum RoundingSide {
PORT,
STARBOARD,
}
public static final double MARK_ARROW_SEPARATION = 15;
public static final double ARROW_LENGTH = 75;
public static final double ARROW_HEAD_DEPTH = 10;
public static final double ARROW_HEAD_WIDTH = 6;
public static final double STROKE_WIDTH = 3;
/**
* Creates an entry arrow group showing an arrow into and out of the rounding area. It is centered on (0, 0).
* @param roundingSide The side of the boat that will be closest to the mark.
* @param angleOfEntry The angle between this mark and the last one as a heading from north in degrees.
* @param angleOfExit The angle between this mark and the next one as a heading from north in degrees.
* @param colour The desired colour of the arrows.
* @return The group containing all JavaFX objects.
*/
public static Group constructEntryArrow (RoundingSide roundingSide, double angleOfEntry,
double angleOfExit, Paint colour) {
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit && Math.abs(angleOfExit - angleOfEntry) < 180) {
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit && -Math.abs(angleOfEntry - angleOfExit) > -180) {
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
}
angleOfEntry = 180 - angleOfEntry;
Group arrow = new Group();
Group exitSection = constructExitArrow(roundingSide, angleOfExit, colour);
angleOfExit = 180 - angleOfExit;
Arc roundSection = new Arc(
0, 0, MARK_ARROW_SEPARATION, MARK_ARROW_SEPARATION,
(roundingSide == RoundingSide.PORT ? -180 : 0) + angleOfEntry,
roundingSide == RoundingSide.PORT ? Math.abs(angleOfExit - angleOfEntry) : -Math.abs(angleOfEntry - angleOfExit)
);
roundSection.setStrokeWidth(STROKE_WIDTH);
roundSection.setType(ArcType.OPEN);
roundSection.setStroke(colour);
roundSection.setFill(new Color(0,0,0,0));
Polygon entrySection = constructLineSegment(
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT, 180 + angleOfEntry, colour
);
arrow.getChildren().addAll(exitSection, roundSection, entrySection);
return arrow;
}
private static Group makeInteriorAngle (RoundingSide roundingSide, double angleOfExit, double angleOfEntry, Paint colour) {
Group arrow = new Group();
Polygon lineSegment;
angleOfEntry = Math.toRadians(360 - angleOfEntry);
angleOfExit = Math.toRadians(180 - angleOfExit);
int multiplier = roundingSide == RoundingSide.STARBOARD ? -1 : 1;
double xStart = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfEntry + Math.PI / 2);
double yStart = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfEntry + Math.PI / 2);
xStart = xStart + (ARROW_LENGTH * Math.sin(angleOfEntry));
yStart = yStart + (ARROW_LENGTH * Math.cos(angleOfEntry));
multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double xEnd = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfExit + Math.PI / 2);
double yEnd = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfExit + Math.PI / 2);
xEnd = xEnd + (ARROW_LENGTH * Math.sin(angleOfExit));
yEnd = yEnd + (ARROW_LENGTH * Math.cos(angleOfExit));
lineSegment = new Polygon(
xStart, yStart,
xEnd, yEnd
);
lineSegment.setStroke(colour);
lineSegment.setFill(Color.BLUE);
lineSegment.setStrokeWidth(STROKE_WIDTH);
lineSegment.setStrokeLineCap(StrokeLineCap.ROUND);
Polyline arrowHead = constructArrowHead(
90 + Math.toDegrees(Math.atan2(yStart - yEnd, xEnd - xStart)),
colour
);
arrowHead.setLayoutX(xEnd);
arrowHead.setLayoutY(yEnd);
arrow.getChildren().addAll(lineSegment, arrowHead);
return arrow;
}
/**
* Creates an exit arrow group pointing towards the next mark.
* @param roundingSide The side of the boat that will be closest to the mark.
* @param angle The angle to the next mark as a heading from north in degrees.
* @param colour The colour of the arrow.
* @return The group containing all the JavaFX objects.
*/
public static Group constructExitArrow (RoundingSide roundingSide, double angle, Paint colour) {
angle = 180 - angle;
Group arrow = new Group();
Polygon arrowBody = constructLineSegment(roundingSide, angle, colour);
Polyline arrowHead = constructArrowHead(angle, colour);
arrowHead.setLayoutX(arrowBody.getPoints().get(2));
arrowHead.setLayoutY(arrowBody.getPoints().get(3));
arrow.getChildren().addAll(arrowBody, arrowHead);
return arrow;
}
private static Polygon constructLineSegment (RoundingSide roundingSide, double angle, Paint colour) {
Polygon lineSegment;
angle = Math.toRadians(angle);
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double xStart = multiplier * MARK_ARROW_SEPARATION * Math.sin(angle + Math.PI / 2);
double yStart = multiplier * MARK_ARROW_SEPARATION * Math.cos(angle + Math.PI / 2);
double xEnd = xStart + (ARROW_LENGTH * Math.sin(angle));
double yEnd = yStart + (ARROW_LENGTH * Math.cos(angle));
lineSegment = new Polygon(
xStart, yStart,
xEnd, yEnd
);
lineSegment.setStroke(colour);
lineSegment.setFill(Color.BLUE);
lineSegment.setStrokeWidth(STROKE_WIDTH);
lineSegment.setStrokeLineCap(StrokeLineCap.ROUND);
return lineSegment;
}
private static Polyline constructArrowHead (double rotation, Paint colour) {
Polyline arrow = new Polyline(
-ARROW_HEAD_WIDTH, -ARROW_HEAD_DEPTH,
0, 0,
ARROW_HEAD_WIDTH, -ARROW_HEAD_DEPTH
);
arrow.getTransforms().add(new Rotate(-rotation));
arrow.setStrokeLineCap(StrokeLineCap.ROUND);
arrow.setStroke(colour);
arrow.setStrokeWidth(STROKE_WIDTH);
return arrow;
}
}
@@ -0,0 +1,94 @@
package seng302.visualiser.fxObjects;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
/**
* Visual object for a mark. Contains a coloured circle and any specified arrows.
*/
public class Marker extends Group {
private Circle mark = new Circle();
private Paint colour = Color.BLACK;
private List<Group> enterArrows = new ArrayList<>();
private List<Group> exitArrows = new ArrayList<>();
private int enterArrowIndex = 0;
private int exitArrowIndex = 0;
/**
* Creates a new Marker containing only a circle. The default colour is black.
*/
public Marker() {
mark.setRadius(5);
mark.setCenterX(0);
mark.setCenterY(0);
Platform.runLater(() -> this.getChildren().addAll(mark, new Group())); //Empty group placeholder or arrows.
}
/**
* Creates a new Marker containing only a circle of the given colour.
* @param colour the desired colour for the marker.
*/
public Marker(Paint colour) {
this();
this.colour = colour;
mark.setFill(colour);
}
/**
* Adds an exit and entry arrow pair to the mark. Arrows are hidden and shown in the order they
* are created by calling showNextEnterArrow() or showNextExitArrow()
* @param roundingSide the side the marker will be from the perspective of the arrow.
* @param entryAngle The angle the arrow will point towards a marker
* @param exitAngle The angle the arrow wil point from the marker.
*/
public void addArrows(MarkArrowFactory.RoundingSide roundingSide, double entryAngle,
double exitAngle) {
//Change Color.GRAY to this.colour to revert all gray arrows.
enterArrows.add(
MarkArrowFactory.constructEntryArrow(roundingSide, entryAngle, exitAngle, Color.GRAY)
);
exitArrows.add(
MarkArrowFactory.constructExitArrow(roundingSide, exitAngle, Color.GRAY)
);
}
/**
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
*/
public void showNextEnterArrow() {
showArrow(enterArrows, enterArrowIndex);
enterArrowIndex++;
}
/**
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
*/
public void showNextExitArrow() {
showArrow(exitArrows, exitArrowIndex);
exitArrowIndex++;
}
private void showArrow(List<Group> arrowList, int arrowListIndex) {
if (arrowListIndex < arrowList.size()) {
if (arrowListIndex == 1) {;
}
Platform.runLater(() -> {
this.getChildren().remove(1);
this.getChildren().add(arrowList.get(arrowListIndex));
});
}
}
/**
* Hides all arrows.
*/
public void hideAllArrows() {
Platform.runLater(() -> this.getChildren().setAll(mark, new Group()));
}
}
@@ -0,0 +1,124 @@
package seng302.visualiser.fxObjects;
import javafx.application.Platform;
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;
/**
* 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) {
Platform.runLater(() -> {
rotate(rotation);
double rad = (14 / numWakes) + velocity;
for (Arc arc : arcs) {
arc.setRadiusX(rad);
arc.setRadiusY(rad);
rad += (14 / numWakes) + (velocity / 2.5);
}
});
// } 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));
}
}
}

Some files were not shown because too many files have changed in this diff Show More