@ -1,6 +1,5 @@
package visualiser.Controllers ;
import javafx.animation.AnimationTimer ;
import javafx.application.Platform ;
import javafx.collections.FXCollections ;
@ -14,15 +13,17 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent ;
import javafx.scene.layout.AnchorPane ;
import javafx.scene.layout.GridPane ;
import javafx.scene.layout.Pane ;
import javafx.scene.layout.StackPane ;
import javafx.util.Callback ;
import network.Messages.Enums.RaceStatusEnum ;
import shared.model.Leg ;
import visualiser.Controllers2.ArrowController ;
import visualiser.Controllers2.Controller2 ;
import visualiser.Controllers2.FinishController ;
import visualiser.app.App ;
import visualiser.gameController.ControllerClient ;
import visualiser.gameController.Keys.ControlKey ;
import visualiser.gameController.Keys.KeyFactory ;
import visualiser.model.* ;
import java.io.IOException ;
@ -31,87 +32,64 @@ import java.util.Optional;
import java.util.logging.Level ;
import java.util.logging.Logger ;
import static visualiser.app.App.keyFactory ;
/ * *
* Controller used to display a running race .
* /
public class RaceController extends Controller2 {
/ * *
* The race object which describes the currently occurring race .
* /
private VisualiserRaceEvent visualiserRace ;
/ * *
* Service for sending keystrokes to server
* /
private VisualiserRaceState raceState ;
private ControllerClient controllerClient ;
private boolean isHost ;
/ * *
* The canvas that draws the race .
* /
private ResizableRaceCanvas raceCanvas ;
private KeyFactory keyFactory ;
/ * *
* The sparkline graph .
* /
private Sparkline sparkline ;
private boolean infoTableShow = true ; // shown or hidden
private boolean isHost ;
/ * *
* state of the info table
* /
private boolean infoTableShow ;
// note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController ;
private @FXML GridPane canvasBase ;
private @FXML SplitPane race ;
private @FXML StackPane arrowPane ;
private @FXML Label timer ;
private @FXML Label FPS ;
private @FXML Label timeZone ;
private @FXML CheckBox showFPS ;
private @FXML TableView < VisualiserBoat > boatInfoTable ;
private @FXML TableColumn < VisualiserBoat , String > boatPlacingColumn ;
private @FXML TableColumn < VisualiserBoat , String > boatTeamColumn ;
private @FXML TableColumn < VisualiserBoat , Leg > boatMarkColumn ;
private @FXML TableColumn < VisualiserBoat , Number > boatSpeedColumn ;
private @FXML LineChart < Number , Number > sparklineChart ;
private @FXML AnchorPane annotationPane ;
/ * *
* The arrow controller .
* Displays a specified race .
* Intended to be called on loading the scene .
* @param visualiserRace Object modelling the race .
* @param controllerClient Socket Client that manipulates the controller .
* @param isHost is user a host
* /
@FXML private ArrowController arrowController ;
@FXML private GridPane canvasBase ;
@FXML private SplitPane race ;
public void startRace ( VisualiserRaceEvent visualiserRace , ControllerClient controllerClient , Boolean isHost ) {
this . visualiserRace = visualiserRace ;
this . raceState = visualiserRace . getVisualiserRaceState ( ) ;
this . controllerClient = controllerClient ;
this . isHost = isHost ;
keyFactory . load ( ) ;
/ * *
* This is the root node of the arrow control .
* /
@FXML private Pane arrow ;
initKeypressHandler ( ) ;
initialiseRaceVisuals ( ) ;
}
/ * *
* This is the pane we place the actual arrow control inside of .
* Sets up the listener and actions that occur when a key is pressed .
* /
@FXML private StackPane arrowPane ;
@FXML private Label timer ;
@FXML private Label FPS ;
@FXML private Label timeZone ;
@FXML private CheckBox showFPS ;
@FXML private TableView < VisualiserBoat > boatInfoTable ;
@FXML private TableColumn < VisualiserBoat , String > boatPlacingColumn ;
@FXML private TableColumn < VisualiserBoat , String > boatTeamColumn ;
@FXML private TableColumn < VisualiserBoat , Leg > boatMarkColumn ;
@FXML private TableColumn < VisualiserBoat , Number > boatSpeedColumn ;
@FXML private LineChart < Number , Number > sparklineChart ;
@FXML private AnchorPane annotationPane ;
public void initialize ( ) {
// KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true ;
// Initialise keyboard handler
public void initKeypressHandler ( ) {
race . addEventFilter ( KeyEvent . KEY_PRESSED , event - > {
String codeString = event . getCode ( ) . toString ( ) ;
if ( codeString . equals ( "TAB" ) ) { toggleTable ( ) ; }
// valid key pressed
ControlKey controlKey = keyFactory . getKey ( codeString ) ;
if ( controlKey ! = null ) {
try {
@ -123,6 +101,8 @@ public class RaceController extends Controller2 {
Logger . getGlobal ( ) . log ( Level . WARNING , "RaceController was interrupted on thread: " + Thread . currentThread ( ) + "while sending: " + controlKey , e ) ;
}
}
// escape key
if ( event . getCode ( ) = = KeyCode . ESCAPE ) {
try {
@ -132,12 +112,10 @@ public class RaceController extends Controller2 {
alert . setContentText ( "Do you wish to quit the race? You are the host" ) ;
Optional < ButtonType > result = alert . showAndWait ( ) ;
if ( result . get ( ) = = ButtonType . OK ) {
// parent.endEvent();
HostController hc = ( HostController ) loadScene (
" /visualiser/scenes/ hostgame.fxml") ;
" hostgame.fxml") ;
hc . endEvent ( ) ;
race . setVisible ( false ) ;
// App.app.showMainStage(App.getStage());
App . app . loadTitleScreen ( ) ;
}
} else {
Alert alert = new Alert ( Alert . AlertType . CONFIRMATION ) ;
@ -145,8 +123,7 @@ public class RaceController extends Controller2 {
alert . setContentText ( "Do you wish to quit the race?" ) ;
Optional < ButtonType > result = alert . showAndWait ( ) ;
if ( result . get ( ) = = ButtonType . OK ) {
race . setVisible ( false ) ;
// App.app.showMainStage(App.getStage());
App . app . loadTitleScreen ( ) ;
}
}
} catch ( IOException e ) {
@ -158,41 +135,25 @@ public class RaceController extends Controller2 {
} ) ;
}
/ * *
* Initialises the various UI components to listen to the { @link # visualiserRace } .
* /
private void initialiseRace ( ) {
//Fps display.
initialiseFps ( this . visualiserRace ) ;
//Information table.
initialiseInfoTable ( this . visualiserRace ) ;
//Sparkline.
initialiseSparkline ( this . visualiserRace ) ;
//Arrow.
initialiseArrow ( this . visualiserRace ) ;
//Race canvas.
initialiseRaceCanvas ( this . visualiserRace ) ;
initialiseView3D ( this . visualiserRace ) ;
//Race timezone label.
initialiseRaceTimezoneLabel ( this . visualiserRace ) ;
//Race clock.
initialiseRaceClock ( this . visualiserRace ) ;
//Start the race animation timer.
raceTimer ( ) ;
}
private void initialiseView3D ( VisualiserRaceEvent race ) {
List < VisualiserBoat > boats = race . getVisualiserRaceState ( ) . getBoats ( ) ;
private void initialiseRaceVisuals ( ) {
// initialise displays
initialiseFps ( ) ;
initialiseInfoTable ( ) ;
initialiseRaceCanvas ( ) ;
initialiseView3D ( ) ;
initialiseRaceClock ( ) ;
raceTimer ( ) ; // start the timer
new Annotations ( this . annotationPane , this . raceCanvas ) ;
new Sparkline ( this . raceState , this . sparklineChart ) ;
timeZone . setText ( this . raceState . getRaceClock ( ) . getTimeZone ( ) ) ;
arrowController . setWindProperty ( this . raceState . windProperty ( ) ) ;
}
private void initialiseView3D ( ) {
List < VisualiserBoat > boats = raceState . getBoats ( ) ;
for ( VisualiserBoat boat : boats ) {
boat . positionProperty ( ) . addListener ( ( o , prev , curr ) - > {
System . out . println ( boat . getCountry ( ) + " is at " + curr . toString ( ) ) ;
@ -200,25 +161,12 @@ public class RaceController extends Controller2 {
}
}
/ * *
* Initialises the frame rate functionality . This allows for toggling the frame rate , and connect the fps label to the race ' s fps property .
* @param visualiserRace The race to connect the fps label to .
* /
private void initialiseFps ( VisualiserRaceEvent visualiserRace ) {
//On/off toggle.
initialiseFpsToggle ( ) ;
//Label value.
initialiseFpsLabel ( visualiserRace ) ;
}
/ * *
* Initialises a listener for the fps toggle .
* Initialises the frame rate functionality . This allows for toggling the
* frame rate , and connect the fps label to the race ' s fps property .
* /
private void initialiseFps Toggle ( ) {
private void initialiseFps ( ) {
// fps toggle listener
showFPS . selectedProperty ( ) . addListener ( ( ov , old_val , new_val ) - > {
if ( showFPS . isSelected ( ) ) {
FPS . setVisible ( true ) ;
@ -229,59 +177,44 @@ public class RaceController extends Controller2 {
}
} ) ;
}
/ * *
* Initialises the fps label to update when the race fps changes .
* @param visualiserRace The race to monitor the frame rate of .
* /
private void initialiseFpsLabel ( VisualiserRaceEvent visualiserRace ) {
visualiserRace . getFrameRateProperty ( ) . addListener ( ( observable , oldValue , newValue ) - > {
Platform . runLater ( ( ) - > this . FPS . setText ( "FPS: " + newValue . toString ( ) ) ) ;
// fps label display
this . visualiserRace . getFrameRateProperty ( ) . addListener ( ( observable ,
oldValue , newValue ) - > {
Platform . runLater ( ( ) - >
this . FPS . setText ( "FPS: " + newValue . toString ( ) ) ) ;
} ) ;
}
/ * *
* Initialises the information table view to listen to a given race .
* @param race Race to listen to .
* /
public void initialiseInfoTable ( VisualiserRaceEvent race ) {
//Copy list of boats.
ObservableList < VisualiserBoat > boats = FXCollections . observableArrayList ( r ace. getVisualiserRaceState ( ) . getBoats ( ) ) ;
public void initialiseInfoTable ( ) {
// list of boats to display data for
ObservableList < VisualiserBoat > boats = FXCollections
. observableArrayList ( this . visualiserR ace. getVisualiserRaceState ( ) . getBoats ( ) ) ;
SortedList < VisualiserBoat > sortedBoats = new SortedList < > ( boats ) ;
sortedBoats . comparatorProperty ( ) . bind ( boatInfoTable . comparatorProperty ( ) ) ;
//Update copy when original changes.
race . getVisualiserRaceState ( ) . getBoats ( ) . addListener ( ( ListChangeListener . Change < ? extends VisualiserBoat > c ) - > Platform . runLater ( ( ) - > {
boats . setAll ( race . getVisualiserRaceState ( ) . getBoats ( ) ) ;
// update list when boat information changes
this . visualiserRace . getVisualiserRaceState ( ) . getBoats ( ) . addListener (
( ListChangeListener . Change < ? extends VisualiserBoat > c ) - > Platform . runLater ( ( ) - > {
boats . setAll ( this . visualiserRace . getVisualiserRaceState ( ) . getBoats ( ) ) ;
} ) ) ;
//Set up table.
// set table data
boatInfoTable . setItems ( sortedBoats ) ;
//Set up each column.
//Name.
boatTeamColumn . setCellValueFactory (
cellData - > cellData . getValue ( ) . nameProperty ( ) ) ;
//Speed.
boatSpeedColumn . setCellValueFactory (
cellData - > cellData . getValue ( ) . currentSpeedProperty ( ) ) ;
boatMarkColumn . setCellValueFactory (
cellData - > cellData . getValue ( ) . legProperty ( ) ) ;
boatPlacingColumn . setCellValueFactory (
cellData - > cellData . getValue ( ) . placingProperty ( ) ) ;
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn . setCellFactory (
//Callback object.
new Callback < TableColumn < VisualiserBoat , Number > , TableCell < VisualiserBoat , Number > > ( ) {
//Callback function.
@Override
public TableCell < VisualiserBoat , Number > call ( TableColumn < VisualiserBoat , Number > param ) {
//We return a table cell that populates itself with a Number, and formats it.
@ -290,30 +223,18 @@ public class RaceController extends Controller2 {
//Function to update the cell text.
@Override
protected void updateItem ( Number item , boolean empty ) {
if ( item ! = null ) {
super . updateItem ( item , empty ) ;
setText ( String . format ( "%.2fkn" , item . doubleValue ( ) ) ) ;
}
}
} ;
}
} ) ;
//Last mark.
boatMarkColumn . setCellValueFactory (
cellData - > cellData . getValue ( ) . legProperty ( ) ) ;
//Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn . setCellFactory (
//Callback object.
new Callback < TableColumn < VisualiserBoat , Leg > , TableCell < VisualiserBoat , Leg > > ( ) {
//Callback function.
@Override
public TableCell < VisualiserBoat , Leg > call ( TableColumn < VisualiserBoat , Leg > param ) {
//We return a table cell that populates itself with a Leg's name.
@ -322,133 +243,56 @@ public class RaceController extends Controller2 {
//Function to update the cell text.
@Override
protected void updateItem ( Leg item , boolean empty ) {
if ( item ! = null ) {
super . updateItem ( item , empty ) ;
setText ( item . getName ( ) ) ;
}
}
} ;
}
} ) ;
//Current place within race.
boatPlacingColumn . setCellValueFactory (
cellData - > cellData . getValue ( ) . placingProperty ( ) ) ;
}
/ * *
* Initialises the { @link Sparkline} , and listens to a specified { @link VisualiserRaceEvent } .
* @param race The race to listen to .
* Initialises the { @link ResizableRaceCanvas } , provides the race to
* read data from .
* /
private void initialiseSparkline ( VisualiserRaceEvent race ) {
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
this . sparkline = new Sparkline ( this . visualiserRace . getVisualiserRaceState ( ) , this . sparklineChart ) ;
}
/ * *
* Initialises the { @link ResizableRaceCanvas } , provides the race to read data from .
* @param race Race to read data from .
* /
private void initialiseRaceCanvas ( VisualiserRaceEvent race ) {
private void initialiseRaceCanvas ( ) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas ( race . getVisualiserRace State( ) ) ;
raceCanvas = new ResizableRaceCanvas ( raceState ) ;
//Set properties.
raceCanvas . setMouseTransparent ( true ) ;
raceCanvas . widthProperty ( ) . bind ( canvasBase . widthProperty ( ) ) ;
raceCanvas . heightProperty ( ) . bind ( canvasBase . heightProperty ( ) ) ;
// Draw it and show it.
// draw and display
raceCanvas . draw ( ) ;
raceCanvas . setVisible ( true ) ;
//Add to scene.
canvasBase . getChildren ( ) . add ( 0 , raceCanvas ) ;
}
/ * *
* Intialises the race time zone label with the race ' s time zone .
* @param race The race to get time zone from .
* /
private void initialiseRaceTimezoneLabel ( VisualiserRaceEvent race ) {
timeZone . setText ( race . getVisualiserRaceState ( ) . getRaceClock ( ) . getTimeZone ( ) ) ;
}
/ * *
* Initialises the race clock to listen to the specified race .
* @param race The race to listen to .
* /
private void initialiseRaceClock ( VisualiserRaceEvent race ) {
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
race . getVisualiserRaceState ( ) . getRaceClock ( ) . durationProperty ( ) . addListener ( ( observable , oldValue , newValue ) - > {
private void initialiseRaceClock ( ) {
raceState . getRaceClock ( ) . durationProperty ( ) . addListener ( ( observable ,
oldValue , newValue ) - > {
Platform . runLater ( ( ) - > {
timer . setText ( newValue ) ;
} ) ;
} ) ;
}
/ * *
* Displays a specified race .
* @param visualiserRace Object modelling the race .
* @param controllerClient Socket Client that manipulates the controller .
* @param isHost is user a host
* /
public void startRace ( VisualiserRaceEvent visualiserRace , ControllerClient controllerClient , Boolean isHost ) {
this . visualiserRace = visualiserRace ;
this . controllerClient = controllerClient ;
this . isHost = isHost ;
initialiseRace ( ) ;
//Display this controller.
race . setVisible ( true ) ;
// set up annotation displays
new Annotations ( annotationPane , raceCanvas ) ;
}
/ * *
* Transition from the race view to the finish view .
* @param boats boats there are in the race .
* /
public void finishRace ( ObservableList < VisualiserBoat > boats ) {
race . setVisible ( false ) ;
// parent.enterFinish(boats);
try {
FinishController fc = ( FinishController ) loadScene
( "/visualiser/scenes/finish.fxml" ) ;
fc . enterFinish ( boats ) ;
} catch ( IOException e ) {
e . printStackTrace ( ) ;
}
public void finishRace ( ) throws IOException {
FinishController fc =
( FinishController ) loadScene ( "/visualiser/scenes/finish.fxml" ) ;
fc . loadFinish ( raceState . getBoats ( ) ) ;
}
/ * *
* Initialises the arrow controller with data from the race to observe .
* @param race The race to observe .
* /
private void initialiseArrow ( VisualiserRaceEvent race ) {
arrowController . setWindProperty ( race . getVisualiserRaceState ( ) . windProperty ( ) ) ;
}
/ * *
* Timer which monitors the race .
* /
@ -456,45 +300,30 @@ public class RaceController extends Controller2 {
new AnimationTimer ( ) {
@Override
public void handle ( long arg0 ) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace . getVisualiserRaceState ( ) . getRaceStatusEnum ( ) ;
//If the race has finished, go to finish view.
if ( raceStat us = = RaceStatusEnum . FINISHED ) {
//Stop this timer.
stop ( ) ;
//Hide this, and display the finish controller.
finishRace ( visualiserRace . getVisualiserRaceState ( ) . getBoats ( ) ) ;
if ( raceState . getRaceStatusEnum ( ) = = RaceStatusEnum . FINISHED ) {
stop ( ) ; // stop the timer
try {
finishRace ( ) ;
} catch ( IOException e ) {
e . printStackTrace ( ) ;
}
} else {
// Otherwise, render the canvas.
// refresh visual race display
raceCanvas . drawRace ( ) ;
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable . sort ( ) ;
}
//Return to main screen if we lose connection.
if ( ! visualiserRace . getServerConnection ( ) . isAlive ( ) ) {
race . setVisible ( false ) ;
//parent.enterTitle();
try {
// App.app.showMainStage(App.getStage());
// } catch (IOException e) {
// e.printStackTrace();
App . app . loadTitleScreen ( ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
}
//TODO currently this doesn't work correctly - the title screen remains visible after clicking join game
//TODO we should display an error to the user
//TODO also need to "reset" any state (race, connections, etc...).
}
}
} . start ( ) ;
}
@ -507,20 +336,16 @@ public class RaceController extends Controller2 {
if ( infoTableShow ) {
race . setDividerPositions ( tablePercent ) ;
arrowPane . setScaleX ( 0.5 ) ;
arrowPane . setScaleY ( 0.5 ) ;
arrowPane . setTranslateX ( 0 + ( arrowPane . getScene ( ) . getWidth ( ) / 4 ) * tablePercent ) ;
arrowPane . setTranslateY ( 0 - arrowPane . getScene ( ) . getHeight ( ) / 4 ) ;
} else {
race . setDividerPositions ( 1 ) ;
arrowPane . setScaleX ( 1 ) ;
arrowPane . setScaleY ( 1 ) ;
arrowPane . setTranslateX ( 0 ) ;
arrowPane . setTranslateY ( 0 ) ;
}
boatInfoTable . refresh ( ) ;
infoTableShow = ! infoTableShow ;