diff --git a/.gitignore b/.gitignore index 48326a70..b7e0c073 100644 --- a/.gitignore +++ b/.gitignore @@ -183,3 +183,4 @@ local.properties # IntelliJDEA ignore *.iml dedicatedServer/.idea/ +settings/keyBindings.xml diff --git a/.mailmap b/.mailmap index c5401576..7498647c 100644 --- a/.mailmap +++ b/.mailmap @@ -18,3 +18,4 @@ Erika Savell Connor Taylor-Brown Fraser Cope +Jessica Syder Jessica Syder \ No newline at end of file diff --git a/dedicatedServer/src/main/java/app/App.java b/dedicatedServer/src/main/java/app/App.java index 34ee2c5b..3b36c43e 100644 --- a/dedicatedServer/src/main/java/app/App.java +++ b/dedicatedServer/src/main/java/app/App.java @@ -21,7 +21,7 @@ public class App extends Application { public void start(Stage primaryStage) { try { //TODO should read a configuration file to configure server? - Event raceEvent = new Event(false); + Event raceEvent = new Event(false, 0); } catch (Exception e) { diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index bfdf174f..488eb0c0 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -2,14 +2,15 @@ package mock.app; import mock.dataInput.PolarParser; import mock.exceptions.EventConstructionException; -import mock.model.*; +import mock.model.MockRace; +import mock.model.Polars; +import mock.model.RaceLogic; +import mock.model.SourceIdAllocator; import mock.model.commandFactory.CompositeCommand; -import mock.model.wind.RandomWindGenerator; import mock.model.wind.ShiftingWindGenerator; import mock.model.wind.WindGenerator; import mock.xml.RaceXMLCreator; import network.Messages.LatestMessages; -import org.xml.sax.SAXException; import shared.dataInput.*; import shared.enums.XMLFileType; import shared.exceptions.InvalidBoatDataException; @@ -19,17 +20,10 @@ import shared.exceptions.XMLReaderException; import shared.model.Bearing; import shared.model.Constants; -import javax.xml.bind.JAXBException; -import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; /** @@ -70,6 +64,8 @@ public class Event { private Thread connectionThread; + private int mapIndex; + @@ -79,11 +75,32 @@ public class Event { * @param singlePlayer Whether or not to create a single player event. * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ - public Event(boolean singlePlayer) throws EventConstructionException { + public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException { - String raceXMLFile = "mock/mockXML/raceTest.xml"; +// System.out.println(XMLUtilities.validateXML(this.getClass().getClassLoader().getResource("mock/mockXML/iMapLayout.xml").toString() +// , this.getClass().getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"))); + this.mapIndex = mapIndex; + String raceXMLFile; String boatsXMLFile = "mock/mockXML/boatTest.xml"; String regattaXMLFile = "mock/mockXML/regattaTest.xml"; + switch (mapIndex){ + case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + break; + case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml"; + break; + case 2: raceXMLFile = "mock/mockXML/iMapLayout.xml"; + break; + case 3: raceXMLFile = "mock/mockXML/mMapLayout.xml"; + break; + case 4: + raceXMLFile = "mock/mockXML/raceTutorial.xml"; + boatsXMLFile = "mock/mockXML/boatTutorial.xml"; + regattaXMLFile = "mock/mockXML/regattaTutorial.xml"; + break; + default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + + } + if (singlePlayer) { raceXMLFile = "mock/mockXML/raceSinglePlayer.xml"; @@ -95,9 +112,10 @@ public class Event { //this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8); this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); - - this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, 300); - + this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, 300, false); + if(mapIndex==4){ + this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000); + } this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8); @@ -180,9 +198,15 @@ public class Event { * @return String containing edited xml */ public static String setRaceXMLAtCurrentTimeToNow(String raceXML) { + return setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime); + } + public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){ //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. - long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000); + + long millisecondsToAdd = racePreStartTime + racePreparatoryTime; + + long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; @@ -192,7 +216,6 @@ public class Event { raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime)); raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd))); - return raceXML; } diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index b7ccef12..c9575ce3 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -23,6 +23,7 @@ import java.util.logging.Logger; public class RaceServer { private MockRace race; private LatestMessages latestMessages; + private static RaceServer server; /** @@ -47,10 +48,17 @@ public class RaceServer { public RaceServer(MockRace race, LatestMessages latestMessages) { + server = this; this.race = race; this.latestMessages = latestMessages; } + public static void staticUpdateXML() { + if (server != null) { + server.updateXMLFiles(); + } + } + /** * Parses the race to create a snapshot, and places it in latestMessages. */ diff --git a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java index 17a7e85f..f590d9b7 100644 --- a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java +++ b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java @@ -42,6 +42,7 @@ public class SourceIdAllocator { } List allocatedIDs = mockRace.getRaceDataSource().getParticipants(); + List allIDs = new ArrayList<>(mockRace.getBoatDataSource().getBoats().keySet()); //Get list of unallocated ids. diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index 16bb23d2..1e5407dc 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -1,35 +1,23 @@ package mock.xml; -import org.w3c.dom.Document; import org.xml.sax.SAXException; import shared.dataInput.RaceXMLReader; import shared.enums.XMLFileType; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.XMLReaderException; -import shared.model.*; -import shared.xml.Race.*; +import shared.model.CompoundMark; +import shared.model.Constants; +import shared.model.GPSCoordinate; +import shared.xml.Race.XMLCompoundMark; +import shared.xml.Race.XMLLimit; +import shared.xml.Race.XMLMark; +import shared.xml.Race.XMLRace; import shared.xml.XMLUtilities; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.bind.util.JAXBSource; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Source; -import javax.xml.transform.dom.DOMSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.StringWriter; -import java.math.BigInteger; -import java.net.URL; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -72,7 +60,7 @@ public class RaceXMLCreator { * @throws XMLReaderException if the xml is not readable * @throws InvalidRaceDataException if the race is invalid */ - public static String alterRaceToWind(String s, XMLFileType fileType, double degrees) throws XMLReaderException, InvalidRaceDataException { + public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException { RaceXMLReader reader = new RaceXMLReader(s, fileType); @@ -83,9 +71,23 @@ public class RaceXMLCreator { RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), XMLRace.class); - setRaceXMLAtCurrentTimeToNow(race); + if(tutorial){ + setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l); + } else { + setRaceXMLAtCurrentTimeToNow(race); + } + + + CompoundMark leewardGate = getLeewardGate(reader); + CompoundMark windwardGate = getWindwardGate(reader); - double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position()); + double raceOriginalBearing = 0; + + /*if (leewardGate != null && windwardGate != null) { + raceOriginalBearing = getLineAngle( + leewardGate.getMark1Position(), + windwardGate.getMark1Position() ); + }*/ double degreesToRotate = degrees - raceOriginalBearing; @@ -98,6 +100,7 @@ public class RaceXMLCreator { } } + /** * Rotate the features in a race such as the boundary, and the marks. * @param race the race to alter @@ -185,14 +188,11 @@ public class RaceXMLCreator { } - /** - * Sets the xml description of the race to show the race was created now, and starts in 4 minutes - * @param raceXML The race.xml contents. - */ - public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) { + + public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){ //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. - long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000); + long millisecondsToAdd = racePrestartTime + racePreparatoryTime; long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; @@ -203,4 +203,13 @@ public class RaceXMLCreator { raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd))); } + /** + * Sets the xml description of the race to show the race was created now, and starts in 4 minutes + * @param raceXML The race.xml contents. + */ + public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) { + setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime); + + } + } diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java index c16d722f..86f02d91 100644 --- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -1,5 +1,6 @@ package network.Messages; +import mock.model.RaceServer; import network.Messages.Enums.XMLMessageType; import java.util.*; @@ -33,9 +34,6 @@ public class LatestMessages extends Observable { private XMLMessage regattaXMLMessage; - - - /** * Ctor. */ @@ -45,6 +43,7 @@ public class LatestMessages extends Observable { /** * Returns a copy of the race snapshot. + * * @return Copy of the race snapshot. */ public List getSnapshot() { @@ -54,6 +53,7 @@ public class LatestMessages extends Observable { /** * Sets the snapshot of the race. + * * @param snapshot New snapshot of race. */ public void setSnapshot(List snapshot) { @@ -61,12 +61,9 @@ public class LatestMessages extends Observable { } - - - - /** * Returns the latest race xml message. + * * @return The latest race xml message. */ public XMLMessage getRaceXMLMessage() { @@ -75,6 +72,7 @@ public class LatestMessages extends Observable { /** * Sets the latest race xml message to a specified race XML message. + * * @param raceXMLMessage The new race XML message to use. */ public void setRaceXMLMessage(XMLMessage raceXMLMessage) { @@ -87,6 +85,7 @@ public class LatestMessages extends Observable { /** * Returns the latest boat xml message. + * * @return The latest boat xml message. */ public XMLMessage getBoatXMLMessage() { @@ -95,6 +94,7 @@ public class LatestMessages extends Observable { /** * Sets the latest boat xml message to a specified boat XML message. + * * @param boatXMLMessage The new boat XML message to use. */ public void setBoatXMLMessage(XMLMessage boatXMLMessage) { @@ -107,6 +107,7 @@ public class LatestMessages extends Observable { /** * Returns the latest regatta xml message. + * * @return The latest regatta xml message. */ public XMLMessage getRegattaXMLMessage() { @@ -115,6 +116,7 @@ public class LatestMessages extends Observable { /** * Sets the latest regatta xml message to a specified regatta XML message. + * * @param regattaXMLMessage The new regatta XML message to use. */ public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) { @@ -126,6 +128,7 @@ public class LatestMessages extends Observable { /** * Checks the type of xml message, and places it in this LatestMessages object. + * * @param xmlMessage The new xml message to use. */ public void setXMLMessage(XMLMessage xmlMessage) { @@ -145,17 +148,20 @@ public class LatestMessages extends Observable { /** * Returns whether or not there is an xml message for each message type. + * * @return True if race, boat, and regatta have an xml message, false otherwise. */ public boolean hasAllXMLMessages() { - if (this.regattaXMLMessage == null || this.boatXMLMessage == null || this.raceXMLMessage == null) { + if (this.regattaXMLMessage == null || this.boatXMLMessage == null || + this.raceXMLMessage == null) { return false; } else { + RaceServer.staticUpdateXML(); return true; } } -} +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/shared/model/RaceClock.java b/racevisionGame/src/main/java/shared/model/RaceClock.java index 4c6532a9..3ce61245 100644 --- a/racevisionGame/src/main/java/shared/model/RaceClock.java +++ b/racevisionGame/src/main/java/shared/model/RaceClock.java @@ -3,6 +3,7 @@ package shared.model; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import org.jetbrains.annotations.Nullable; +import visualiser.Controllers.RaceStartController; import visualiser.model.ResizableRaceCanvas; import java.time.Duration; @@ -15,8 +16,8 @@ import java.util.Date; * This class is used to implement a clock which keeps track of and * displays times relevant to a race. This is displayed on the * {@link ResizableRaceCanvas} via the - * {@link visualiser.Controllers.RaceController} and the - * {@link visualiser.Controllers.StartController}. + * {@link visualiser.Controllers.RaceViewController} and the + * {@link RaceStartController}. */ public class RaceClock { diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java index eb5d001d..739b2f6e 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java @@ -1,65 +1,31 @@ package visualiser.Controllers; - import javafx.application.Platform; import javafx.beans.property.Property; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.image.ImageView; -import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; -import javafx.scene.shape.Circle; import shared.model.Bearing; import shared.model.Wind; /** - * Controller for the arrow.fxml view. + * Controller for the wind direction arrow on the race screen. */ public class ArrowController { - - - @FXML - private Pane compass; - - @FXML - private StackPane arrowStackPane; - - @FXML - private ImageView arrowImage; - - @FXML - private Circle circle; - - @FXML - private Label northLabel; - - @FXML - private Label windLabel; - - @FXML - private Label speedLabel; - - - /** - * This is the property our arrow control binds to. - */ - private Property wind; - - - /** - * Constructor. - */ - public ArrowController() { - } - + private @FXML StackPane arrowStackPane; + private @FXML ImageView arrowImage; + private @FXML Label speedLabel; + private final static Integer MIN_KNOTS = 2; // knots for min_height + private final static Integer MAX_KNOTS = 30; // knots for max_height + private final static Integer MIN_HEIGHT = 25; // min arrow height + private final static Integer MAX_HEIGHT = 75; // max arrow height /** * Sets which wind property the arrow control should bind to. * @param wind The wind property to bind to. */ public void setWindProperty(Property wind) { - this.wind = wind; - wind.addListener((observable, oldValue, newValue) -> { if (newValue != null) { Platform.runLater(() -> updateWind(newValue)); @@ -67,7 +33,6 @@ public class ArrowController { }); } - /** * Updates the control to use the new wind value. * This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed). @@ -78,7 +43,6 @@ public class ArrowController { updateWindSpeed(wind.getWindSpeed()); } - /** * Updates the control to account for the new wind speed. * This changes the length (height) of the wind arrow, and updates the speed label. @@ -94,29 +58,22 @@ public class ArrowController { * @param speedKnots Wind speed, in knots. */ private void updateWindArrowLength(double speedKnots) { - - //At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height. - double minKnots = 2; - double maxKnots = 30; - double deltaKnots = maxKnots - minKnots; - - double minHeight = 25; - double maxHeight = 75; - double deltaHeight = maxHeight - minHeight; + double deltaKnots = MAX_KNOTS - MIN_KNOTS; + double deltaHeight = MAX_HEIGHT - MIN_HEIGHT; //Clamp speed. - if (speedKnots > maxKnots) { - speedKnots = maxKnots; - } else if (speedKnots < minKnots) { - speedKnots = minKnots; + if (speedKnots > MAX_KNOTS) { + speedKnots = MAX_KNOTS; + } else if (speedKnots < MIN_KNOTS) { + speedKnots = MIN_KNOTS; } //How far between the knots bounds is the current speed? - double currentDeltaKnots = speedKnots - minKnots; + double currentDeltaKnots = speedKnots - MIN_KNOTS; double currentKnotsScalar = currentDeltaKnots / deltaKnots; //Thus, how far between the pixel height bounds should the arrow height be? - double newHeight = minHeight + (currentKnotsScalar * deltaHeight); + double newHeight = MIN_HEIGHT + (currentKnotsScalar * deltaHeight); arrowImage.setFitHeight(newHeight); } @@ -129,7 +86,6 @@ public class ArrowController { speedLabel.setText(String.format("%.1fkn", speedKnots)); } - /** * Updates the control to account for a new wind bearing. * This rotates the arrow according to the bearing. @@ -140,7 +96,4 @@ public class ArrowController { arrowStackPane.setRotate(bearing.degrees()); } - - - -} +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java deleted file mode 100644 index 28692488..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java +++ /dev/null @@ -1,147 +0,0 @@ -package visualiser.Controllers; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.*; -import javafx.scene.layout.AnchorPane; -import mock.app.Event; -import org.xml.sax.SAXException; -import shared.exceptions.InvalidBoatDataException; -import shared.exceptions.InvalidRaceDataException; -import shared.exceptions.InvalidRegattaDataException; -import shared.exceptions.XMLReaderException; -import visualiser.model.RaceConnection; - -import javax.xml.bind.JAXBException; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.soap.Text; -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ResourceBundle; - -//TODO it appears this view/controller was replaced by Lobby.fxml. Remove? -/** - * Controls the connection that the VIsualiser can connect to. - */ -public class ConnectionController extends Controller { - @FXML - private AnchorPane connectionWrapper; - @FXML - private TableView connectionTable; - @FXML - private TableColumn hostnameColumn; - @FXML - private TableColumn statusColumn; - @FXML - private Button connectButton; - - @FXML - private TextField urlField; - @FXML - private TextField portField; - - - /*Title Screen fxml items*/ - @FXML - private Button hostGameTitleBtn; - @FXML - private Button connectGameBtn; - @FXML - private RadioButton nightRadioBtn; - @FXML - private RadioButton dayRadioButton; - - /*Lobby fxml items*/ - @FXML - private TableView lobbyTable; - @FXML - private TableColumn gameNameColumn; - @FXML - private TableColumn hostNameColumn; - @FXML - private TableColumn playerCountColumn; - @FXML - private TextField playerNameField; - @FXML - private Button joinGameBtn; - - /*Host game fxml items*/ - @FXML - private TextField gameNameField; - @FXML - private TextField hostNameField; - @FXML - private TextField hostGameBtn; - - - - - - - - - private ObservableList connections; - - - - @Override - public void initialize(URL location, ResourceBundle resources) { - // TODO - replace with config file - connections = FXCollections.observableArrayList(); - - connectionTable.setItems(connections); - hostnameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty()); - statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty()); - - connectionTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> { - if (curr != null && curr.check()) connectButton.setDisable(false); - else connectButton.setDisable(true); - }); - connectButton.setDisable(true); - } - - /** - * Sets current status of all connections. - */ - public void checkConnections() { - for(RaceConnection connection: connections) { - connection.check(); - } - } - - public AnchorPane startWrapper(){ - return connectionWrapper; - } - - /** - * Connects to host currently selected in table. Button enabled only if host is ready. - */ - public void connectSocket() { - try{ - RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem(); - Socket socket = new Socket(connection.getHostname(), connection.getPort()); - socket.setKeepAlive(true); - connectionWrapper.setVisible(false); - //parent.enterLobby(socket); - } catch (IOException e) { /* Never reached */ } - } - - /** - * adds a new connection - */ - public void addConnection(){ - String hostName = urlField.getText(); - String portString = portField.getText(); - try{ - int port = Integer.parseInt(portString); - connections.add(new RaceConnection(hostName, port, null)); - }catch(NumberFormatException e){ - System.err.println("Port number entered is not a number"); - } - } - - -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/Controller.java b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java index 220b7816..6d14b32d 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/Controller.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java @@ -1,32 +1,108 @@ package visualiser.Controllers; -import javafx.fxml.Initializable; - -import java.net.URL; -import java.util.ResourceBundle; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Modality; +import javafx.stage.Stage; +import visualiser.app.App; +import java.io.IOException; /** - * Controller parent for app controllers. - * Created by fwy13 on 15/03/2017. + * Abstract controller class to give each subclass the functionality to load + * a new scene into the existing stage, or create a new popup window. */ -public abstract class Controller implements Initializable { - protected MainController parent; +public abstract class Controller { + private Stage stage = App.getStage(); + + /** + * Loads the title screen again when app is already running. + * @throws IOException if a problem with the title.fxml + */ + protected void loadTitleScreen() throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/title.fxml")); + Parent root = loader.load(); + stage.setResizable(false); + Scene scene = new Scene(root); + addCssStyle(scene); + stage.setScene(scene); + stage.show(); + } + + /** + * Used to load a new scene in the currently open stage. + * @param fxmlUrl the URL of the FXML file to be loaded + * @return the controller of the new scene + * @throws IOException if there is an issue with the fxmlUrl + */ + protected Controller loadScene(String fxmlUrl) throws IOException { + // load the correct fxml file + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(getClass().getResource + ("/visualiser/scenes/"+fxmlUrl)); + Parent root = loader.load(); + + // reuse previous stage and it's window size + Stage stage = App.getStage(); + Double stageHeight = stage.getHeight(); + Double stageWidth = stage.getWidth(); + + // set new scene into existing window + Scene scene = new Scene(root, stageWidth, stageHeight); + addCssStyle(scene); + stage.setScene(scene); + stage.setResizable(true); + stage.show(); + stage.setHeight(stageHeight); + stage.setWidth(stageWidth); + stage.sizeToScene(); + + // return controller for the loaded fxml scene + return loader.getController(); + } /** - * Sets the parent of the application - * - * @param parent controller + * Used to load a scene in a new separate popup stage. + * @param fxmlUrl the URL of the FXML file to be loaded + * @param title title for the new window + * @param modality modality settings for popup window + * @return the controller of the new scene + * @throws IOException if there is an issue with the fxmlUrl */ - public void setParent(MainController parent) { - this.parent = parent; + protected Controller loadPopupScene(String fxmlUrl, String title, Modality + modality) throws IOException { + // load the correct fxml scene + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(getClass().getResource( + "/visualiser/scenes/" + fxmlUrl)); + Parent root = loader.load(); + + // create a new 'pop-up' window + Stage stage = new Stage(); + stage.initModality(modality); + stage.setTitle(title); + stage.centerOnScreen(); + stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png"))); + Scene scene = new Scene(root); + addCssStyle(scene); + stage.setScene(scene); + stage.show(); + + // return controller for the loaded fxml scene + return loader.getController(); } /** - * Initialisation class that is run on start up. - * - * @param location resources location - * @param resources resources bundle + * Adds the relevant CSS styling to the scene being loaded. + * @param scene new scene to be loaded and displayed */ - @Override - public abstract void initialize(URL location, ResourceBundle resources); + private void addCssStyle(Scene scene){ + if (App.dayMode) { + scene.getStylesheets().add("/css/dayMode.css"); + } else { + scene.getStylesheets().add("/css/nightMode.css"); + } + } + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java deleted file mode 100644 index 6de6dcdf..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java +++ /dev/null @@ -1,92 +0,0 @@ -package visualiser.Controllers; - - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.layout.AnchorPane; -import visualiser.model.VisualiserBoat; - -import java.net.URL; -import java.util.ResourceBundle; - - -/** - * Finish Screen for when the race finishes. - */ -public class FinishController extends Controller { - - @FXML - AnchorPane finishWrapper; - - @FXML - TableView boatInfoTable; - - @FXML - TableColumn boatRankColumn; - - @FXML - TableColumn boatNameColumn; - - @FXML - Label raceWinnerLabel; - - - /** - * The boats to display on the table. - */ - private ObservableList boats; - - - /** - * Ctor. - */ - public FinishController() { - } - - - @Override - public void initialize(URL location, ResourceBundle resources){ - } - - - - /** - * Sets up the finish table - * @param boats Boats to display - */ - private void setFinishTable(ObservableList boats) { - this.boats = boats; - - //Set contents. - boatInfoTable.setItems(boats); - - //Name. - boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); - - //Rank/position. - boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().placingProperty()); - - - //Winner label. - if (boats.size() > 0) { - raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue()); - raceWinnerLabel.setWrapText(true); - } - - } - - - - /** - * Display the table - * @param boats boats to display on the table. - */ - public void enterFinish(ObservableList boats){ - finishWrapper.setVisible(true); - setFinishTable(boats); - } - -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java deleted file mode 100644 index 008276ef..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java +++ /dev/null @@ -1,165 +0,0 @@ -package visualiser.Controllers; - -import com.interactivemesh.jfx.importer.stl.StlMeshImporter; -import javafx.animation.AnimationTimer; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.control.SplitPane; -import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.scene.shape.Box; -import javafx.scene.shape.Mesh; -import javafx.scene.shape.MeshView; -import javafx.scene.shape.Shape3D; -import javafx.scene.transform.Rotate; -import mock.app.Event; -import mock.exceptions.EventConstructionException; -import visualiser.model.View3D; - -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Controller for Hosting a game. - */ -public class HostController extends Controller { - - -// @FXML -// TextField gameNameField; -// -// @FXML -// TextField hostNameField; - - @FXML - private ImageView imageView; - - @FXML - AnchorPane hostWrapper; - - @FXML - AnchorPane imagePane; - - @FXML - SplitPane splitPane; - - @FXML - AnchorPane specPane; - - @FXML - GridPane playerContainer; - - private Event game; - - private View3D view3D; - - @Override - public void initialize(URL location, ResourceBundle resources) { - ObservableList shapes = FXCollections.observableArrayList(); - - view3D = new View3D(); - view3D.setItems(shapes); - playerContainer.add(view3D, 0,0); - - URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); - - StlMeshImporter importer = new StlMeshImporter(); - importer.read(asset); - MeshView mesh = new MeshView(importer.getImport()); - shapes.add(mesh); - - view3D.setPivot(mesh); - view3D.setDistance(50); - view3D.setYaw(45); - view3D.setPitch(20); - - Rotate rotation = new Rotate(0, Rotate.Y_AXIS); - mesh.getTransforms().addAll(rotation, new Rotate(-90, Rotate.X_AXIS)); - - AnimationTimer rotate = new AnimationTimer() { - @Override - public void handle(long now) { - rotation.setAngle(rotation.getAngle() + 0.1); - } - }; - rotate.start(); - } - - /** - * Hosts a game - */ - public void hostGamePressed() { - try { - this.game = new Event(false); - connectSocket("localhost", 4942); - } catch (EventConstructionException e) { - Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); - throw new RuntimeException(e); - } - } - - public void endEvent() throws IOException { - game.endEvent(); - } - - /** - * Connect to a socket - * @param address address of the server - * @param port port that the server is run off - */ - public void connectSocket(String address, int port) { - try{ - Socket socket = new Socket(address, port); - hostWrapper.setVisible(false); - parent.enterLobby(socket, true); - } catch (IOException e) { /* Never reached */ } - } - - public AnchorPane startWrapper(){ - return hostWrapper; - } - - /** - * Hosts a game. - */ - public void hostGame(){ - splitPane.setResizableWithParent(specPane, false); - splitPane.lookupAll(".split-pane-divider").stream().forEach(div -> div.setMouseTransparent(true)); - imageView.fitWidthProperty().bind(imagePane.widthProperty()); - imageView.fitHeightProperty().bind(imagePane.heightProperty()); - hostWrapper.setVisible(true); - } - - /** - * Menu button pressed. Prompt alert then return to menu - */ - public void menuBtnPressed(){ - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("Quitting race"); - alert.setContentText("Do you wish to quit the race?"); - alert.setHeaderText("You are about to quit the race"); - Optional result = alert.showAndWait(); - if(result.get() == ButtonType.OK){ - hostWrapper.setVisible(false); - parent.enterTitle(); - } - } - - /** - * Start button pressed. Currently only prints out start - */ - public void startBtnPressed(){ - //System.out.println("Should start the race. This button is only visible for the host"); - hostGamePressed(); - } - -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java new file mode 100644 index 00000000..6f82f87d --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java @@ -0,0 +1,118 @@ +package visualiser.Controllers; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import mock.app.Event; +import mock.exceptions.EventConstructionException; +import visualiser.app.App; +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Controller for Hosting a game. + */ +public class HostGameController extends Controller { + private @FXML ImageView mapImage; + private ArrayList listOfMaps; + private int currentMapIndex = 0; + + public void initialize() { + loadMaps(); + } + + /** + * Loads in the list of playable maps to be selected from. + */ + private void loadMaps(){ + // image preview of maps + Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png")); + Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png")); + Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png")); + Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png")); + + listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap)); + mapImage.setImage(listOfMaps.get(currentMapIndex)); + Platform.runLater(() -> { + mapImage.fitWidthProperty() + .bind(mapImage.getScene().getWindow().widthProperty().multiply(0.6)); + }); + } + + /** + * Hosts a game + */ + public void hostGamePressed() { + try { + App.game = new Event(false, currentMapIndex); + App.gameType = currentMapIndex; + connectSocket("localhost", 4942); + } catch (EventConstructionException e) { + Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); + throw new RuntimeException(e); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Connect to a socket + * @param address address of the server + * @param port port that the server is run off + */ + public void connectSocket(String address, int port) throws IOException { + Socket socket = new Socket(address, port); + RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml"); + rsc.enterLobby(socket, true); + } + + /** + * Menu button pressed. Prompt alert then return to menu + */ + public void menuBtnPressed() throws Exception { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Quitting race"); + alert.setContentText("Do you wish to quit the race?"); + alert.setHeaderText("You are about to quit the race"); + Optional result = alert.showAndWait(); + if(result.get() == ButtonType.OK){ + loadTitleScreen(); + } + } + + /** + * Method called when the 'next' arrow button is pressed. It is used to + * change the currently displayed map preview to the next in the list. + */ + public void nextImage(){ + // increase index + currentMapIndex = (currentMapIndex + 1) % listOfMaps.size(); + // update map preview + mapImage.setImage(listOfMaps.get(currentMapIndex)); + } + + /** + * Method called when the 'previous' arrow button is pressed. It is used to + * change the currently displayed map preview to the previous in the list. + */ + public void previousImage(){ + // decrease index + currentMapIndex = ((((currentMapIndex - 1) % listOfMaps.size()) + + listOfMaps.size()) % listOfMaps.size()); + // update map preview + mapImage.setImage(listOfMaps.get(currentMapIndex)); + } + + public void setCurrentMapIndex(Integer index){ + this.currentMapIndex = index; + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java index 84d82e91..617af614 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java @@ -2,9 +2,6 @@ package visualiser.Controllers; import javafx.application.Platform; 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.ListView; import javafx.scene.input.KeyCode; @@ -20,12 +17,10 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static visualiser.app.App.keyFactory; - /** * Controller for the scene used to display and update current key bindings. */ -public class KeyBindingsController { +public class KeyBindingsController extends Controller { private @FXML Button btnSave; private @FXML Button btnCancel; private @FXML Button btnReset; @@ -33,23 +28,27 @@ public class KeyBindingsController { private @FXML ListView lstKey; private @FXML ListView lstDescription; private @FXML AnchorPane anchor; + private KeyFactory existingKeyFactory; private KeyFactory newKeyFactory; private Boolean changed = false; // keyBindings have been modified private Button currentButton = null; // last button clicked public void initialize(){ // create new key factory to modify, keeping the existing one safe + existingKeyFactory = new KeyFactory(); + existingKeyFactory.load(); newKeyFactory = copyExistingFactory(); initializeTable(); populateTable(); setKeyListener(); + setClosedListener(); } /** * Sets up table before populating it. * Set up includes headings, CSS styling and modifying default properties. */ - public void initializeTable(){ + private void initializeTable(){ // set the headings for each column lstKey.getItems().add("Key"); lstControl.getItems().add("Command"); @@ -67,15 +66,15 @@ public class KeyBindingsController { // stop the columns from being selectable, so only the buttons are lstKey.getSelectionModel().selectedItemProperty() - .addListener((observable, oldvalue, newValue) -> + .addListener((observable, oldValue, newValue) -> Platform.runLater(() -> lstKey.getSelectionModel().select(0))); lstDescription.getSelectionModel().selectedItemProperty() - .addListener((observable, oldvalue, newValue) -> + .addListener((observable, oldValue, newValue) -> Platform.runLater(() -> lstDescription.getSelectionModel().select(0))); lstControl.getSelectionModel().selectedItemProperty() - .addListener((observable, oldvalue, newValue) -> + .addListener((observable, oldValue, newValue) -> Platform.runLater(() -> lstControl.getSelectionModel().select(0))); } @@ -83,7 +82,7 @@ public class KeyBindingsController { /** * Populates the table with commands and their key binding details. */ - public void populateTable(){ + private void populateTable(){ // add each command to the table for (Map.Entry entry : newKeyFactory.getKeyState().entrySet()) { // create button for command @@ -100,11 +99,11 @@ public class KeyBindingsController { /** * Makes a copy of the {@link KeyFactory} that does not modify the original. - * @return new keyfactory to be modified + * @return new keyFactory to be modified */ - public KeyFactory copyExistingFactory(){ + private KeyFactory copyExistingFactory(){ newKeyFactory = new KeyFactory(); - Map oldKeyState = keyFactory.getKeyState(); + Map oldKeyState = existingKeyFactory.getKeyState(); Map newKeyState = new HashMap<>(); // copy over commands and their keys @@ -116,11 +115,32 @@ public class KeyBindingsController { } /** - * Creates a listener for the base anchorpane for key presses. + * Creates a listener for when a user tries to close the current window. + */ + private void setClosedListener(){ + anchor.sceneProperty().addListener((obsS, oldS, newS) -> { + if (newS != null) { + newS.windowProperty().addListener((obsW, oldW, newW) -> { + if (newW != null) { + Stage stage = (Stage)newW; + // WE is processed by onExit method + stage.setOnCloseRequest(we -> { + if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) { + onExit(we); + } + }); + } + }); + } + }); + } + + /** + * Creates a listener for the base anchorPane for key presses. * It updates the current key bindings of the {@link KeyFactory} if * required. */ - public void setKeyListener(){ + private void setKeyListener(){ anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> { // if esc, cancel current button click if (event.getCode() == KeyCode.ESCAPE){ @@ -179,10 +199,10 @@ public class KeyBindingsController { */ public void save(){ if (isFactoryValid()) { - keyFactory = newKeyFactory; + existingKeyFactory = newKeyFactory; newKeyFactory = new KeyFactory(); changed = false; - keyFactory.save(); // save persistently + existingKeyFactory.save(); // save persistently loadNotification("Key bindings were successfully saved.", false); } else { loadNotification("One or more key bindings are missing. " + @@ -195,10 +215,10 @@ public class KeyBindingsController { * commands are missing a key binding. * @return True if valid, false if invalid */ - public Boolean isFactoryValid(){ + private Boolean isFactoryValid(){ for (Map.Entry entry : newKeyFactory.getKeyState().entrySet ()) { - if (entry.getKey().toString()==entry.getValue().toString()){ + if (entry.getKey().equals(entry.getValue().toString())){ return false; } } @@ -211,7 +231,7 @@ public class KeyBindingsController { * @param we {@link WindowEvent} close request to be consumed if settings * have not been successfully saved. */ - public void onExit(WindowEvent we){ + private void onExit(WindowEvent we){ // if modified KeyFactory hasn't been saved if (changed){ loadNotification("Please cancel or save your changes before exiting" + @@ -225,23 +245,15 @@ public class KeyBindingsController { * @param message the message to be displayed to the user * @param warning true if the message to be displayed is due to user error */ - public void loadNotification(String message, Boolean warning){ - Parent root = null; - FXMLLoader loader = new FXMLLoader(getClass().getResource - ("/visualiser/scenes/notification.fxml")); + private void loadNotification(String message, Boolean warning){ try { - root = loader.load(); + NotificationController nc = (NotificationController) + loadPopupScene("notification.fxml", + "", Modality.APPLICATION_MODAL); + nc.setMessage(message, warning); } catch (IOException e) { e.printStackTrace(); } - NotificationController controller = loader.getController(); - Stage stage = new Stage(); - stage.setScene(new Scene(root)); - stage.centerOnScreen(); - stage.initModality(Modality.APPLICATION_MODAL); - stage.show(); - // displays given message in the window - controller.setMessage(message, warning); } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java index d541c531..8df0e4ed 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java @@ -7,46 +7,29 @@ import javafx.scene.control.Button; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; -import javafx.scene.layout.AnchorPane; import visualiser.model.RaceConnection; - import java.io.IOException; import java.net.Socket; -import java.net.URL; -import java.util.ResourceBundle; /** * Controller for the Lobby for entering games */ public class LobbyController extends Controller { - - @FXML - private AnchorPane lobbyWrapper; - @FXML - private TableView lobbyTable; - @FXML - private TableColumn gameNameColumn; - @FXML - private TableColumn hostNameColumn; - @FXML - private TableColumn statusColumn; - @FXML - private Button joinGameBtn; - @FXML - private TextField addressFld; - @FXML - private TextField portFld; - + private @FXML TableView lobbyTable; + private @FXML TableColumn gameNameColumn; + private @FXML TableColumn hostNameColumn; + private @FXML TableColumn statusColumn; + private @FXML Button joinGameBtn; + private @FXML TextField addressFld; + private @FXML TextField portFld; private ObservableList connections; - - @Override - public void initialize(URL location, ResourceBundle resources) { + public void initialize() { + // set up the connection table connections = FXCollections.observableArrayList(); //connections.add(new RaceConnection("localhost", 4942, "Local Game")); lobbyTable.setItems(connections); - gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty()); hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty()); statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty()); @@ -54,8 +37,7 @@ public class LobbyController extends Controller { lobbyTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> { if (curr != null && curr.statusProperty().getValue().equals("Ready")) { joinGameBtn.setDisable(false); - } - else { + } else { joinGameBtn.setDisable(true); } }); @@ -75,26 +57,21 @@ public class LobbyController extends Controller { } else { joinGameBtn.setDisable(true); } - } catch (Exception e){} + } catch (Exception ignored){} } /** * Connect to a connection. */ - public void connectSocket() { - try{ - RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem(); - Socket socket = new Socket(connection.getHostname(), connection.getPort()); - lobbyWrapper.setVisible(false); - parent.enterLobby(socket, false); - } catch (IOException e) { /* Never reached */ - e.printStackTrace(); - } + public void connectSocket() throws IOException { + RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem(); + Socket socket = new Socket(connection.getHostname(), connection.getPort()); + RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml"); + rsc.enterLobby(socket, false); } - public void menuBtnPressed(){ - lobbyWrapper.setVisible(false); - parent.enterTitle(); + public void menuBtnPressed() throws IOException { + loadScene("title.fxml"); } /** @@ -103,24 +80,14 @@ public class LobbyController extends Controller { public void addConnectionPressed(){ String hostName = addressFld.getText(); String portString = portFld.getText(); - try{ + try { int port = Integer.parseInt(portString); connections.add(new RaceConnection(hostName, port, "Boat Game")); addressFld.clear(); portFld.clear(); - }catch(NumberFormatException e){ + } catch (NumberFormatException e) { System.err.println("Port number entered is not a number"); } } - public AnchorPane startWrapper(){ - return lobbyWrapper; - } - - /** - * Enter the lobby page. - */ - public void enterLobby(){ - lobbyWrapper.setVisible(true); - } -} +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java deleted file mode 100644 index e1aa1ede..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java +++ /dev/null @@ -1,132 +0,0 @@ -package visualiser.Controllers; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.layout.AnchorPane; -import visualiser.gameController.ControllerClient; -import visualiser.model.VisualiserBoat; -import visualiser.model.VisualiserRaceEvent; - -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.util.ResourceBundle; - - -/** - * Controller that everything is overlayed onto. This makes it so that changing scenes does not resize your stage. - */ -public class MainController extends Controller { - - @FXML private StartController startController; - @FXML private RaceController raceController; - @FXML private ConnectionController connectionController; - @FXML private FinishController finishController; - @FXML private TitleController titleController; - @FXML private HostController hostController; - @FXML private LobbyController lobbyController; - - - /** - * Ctor. - */ - public MainController() { - } - - - - /** - * Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race). - * @param visualiserRace The object modelling the race. - * @param controllerClient Socket Client that manipulates the controller. - * @param isHost if the client is the host of a race or not. - */ - public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) { - raceController.startRace(visualiserRace, controllerClient, isHost); - } - - public void endEvent() throws IOException { hostController.endEvent(); } - - /** - * Transitions from the server selection screen to the pre-race lobby for a given server. - * @param socket The server to read data from. - * @param isHost is connection a host - */ - public void enterLobby(Socket socket, Boolean isHost) { - startController.enterLobby(socket, isHost); - } - - /** - * Transitions from the {@link RaceController} screen to the {@link FinishController} screen. - * @param boats The boats to display on the finish screen. - */ - public void enterFinish(ObservableList boats) { - finishController.enterFinish(boats); - } - - /** - * Transitions into the title screen - */ - public void enterTitle() { - titleController.enterTitle(); - } - - /** - * Transitions into lobby screen - */ - public void enterLobby(){ lobbyController.enterLobby(); } - - /** - * Transitions into host game screen - */ - public void hostGame(){ hostController.hostGame(); } - - /** - * Sets up the css for the start of the program - */ - public void startCss(){titleController.setDayMode();} - - /** - * Main Controller for the applications will house the menu and the displayed pane. - * - * @param location of resources - * @param resources bundle - */ - @Override - public void initialize(URL location, ResourceBundle resources) { - - startController.setParent(this); - raceController.setParent(this); - connectionController.setParent(this); - finishController.setParent(this); - titleController.setParent(this); - hostController.setParent(this); - lobbyController.setParent(this); - - - AnchorPane.setTopAnchor(startController.startWrapper(), 0.0); - AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0); - AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0); - AnchorPane.setRightAnchor(startController.startWrapper(), 0.0); - - AnchorPane.setTopAnchor(lobbyController.startWrapper(), 0.0); - AnchorPane.setBottomAnchor(lobbyController.startWrapper(), 0.0); - AnchorPane.setLeftAnchor(lobbyController.startWrapper(), 0.0); - AnchorPane.setRightAnchor(lobbyController.startWrapper(), 0.0); - - AnchorPane.setTopAnchor(hostController.startWrapper(), 0.0); - AnchorPane.setBottomAnchor(hostController.startWrapper(), 0.0); - AnchorPane.setLeftAnchor(hostController.startWrapper(), 0.0); - AnchorPane.setRightAnchor(hostController.startWrapper(), 0.0); - - AnchorPane.setTopAnchor(finishController.finishWrapper, 0.0); - AnchorPane.setBottomAnchor(finishController.finishWrapper, 0.0); - AnchorPane.setLeftAnchor(finishController.finishWrapper, 0.0); - AnchorPane.setRightAnchor(finishController.finishWrapper, 0.0); - - AnchorPane.setTopAnchor(titleController.titleWrapper, 0.0); - AnchorPane.setBottomAnchor(titleController.titleWrapper, 0.0); - AnchorPane.setLeftAnchor(titleController.titleWrapper, 0.0); - AnchorPane.setRightAnchor(titleController.titleWrapper, 0.0); - } -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java b/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java index d122ec80..ce3c25ee 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java @@ -9,7 +9,7 @@ import javafx.stage.Stage; /** * Controller for a popup notification regarding user activity. */ -public class NotificationController { +public class NotificationController extends Controller{ private @FXML Label lblDescription; private @FXML Text txtMessage; diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java deleted file mode 100644 index 043726f7..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ /dev/null @@ -1,516 +0,0 @@ -package visualiser.Controllers; - - -import javafx.animation.AnimationTimer; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.collections.transformation.SortedList; -import javafx.fxml.FXML; -import javafx.scene.chart.LineChart; -import javafx.scene.control.*; -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.app.App; -import visualiser.gameController.ControllerClient; -import visualiser.gameController.Keys.ControlKey; -import visualiser.model.*; - -import java.io.IOException; -import java.net.URL; -import java.util.Optional; -import java.util.ResourceBundle; -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 Controller { - - - /** - * The race object which describes the currently occurring race. - */ - private VisualiserRaceEvent visualiserRace; - - - /** - * Service for sending keystrokes to server - */ - private ControllerClient controllerClient; - - - private boolean isHost; - - /** - * The canvas that draws the race. - */ - private ResizableRaceCanvas raceCanvas; - - /** - * The sparkline graph. - */ - private Sparkline sparkline; - - /** - * state of the info table - */ - private boolean infoTableShow; - - /** - * The arrow controller. - */ - @FXML private ArrowController arrowController; - - - - @FXML private GridPane canvasBase; - - - @FXML private SplitPane race; - - /** - * This is the root node of the arrow control. - */ - @FXML private Pane arrow; - - /** - * This is the pane we place the actual arrow control inside of. - */ - @FXML private StackPane arrowPane; - @FXML private Label timer; - @FXML private Label FPS; - @FXML private Label timeZone; - @FXML private CheckBox showFPS; - @FXML private TableView boatInfoTable; - @FXML private TableColumn boatPlacingColumn; - @FXML private TableColumn boatTeamColumn; - @FXML private TableColumn boatMarkColumn; - @FXML private TableColumn boatSpeedColumn; - @FXML private LineChart sparklineChart; - @FXML private AnchorPane annotationPane; - - - - /** - * Ctor. - */ - public RaceController() { - } - - @Override - public void initialize(URL location, ResourceBundle resources) { -// KeyFactory keyFactory = KeyFactory.getFactory(); - infoTableShow = true; - - // Initialise keyboard handler - race.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - String codeString = event.getCode().toString(); - - if (codeString.equals("TAB")){toggleTable();} - - ControlKey controlKey = keyFactory.getKey(codeString); - if(controlKey != null) { - try { - controlKey.onAction(); // Change key state if applicable - controllerClient.sendKey(controlKey); - event.consume(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); - } - } - if(event.getCode() == KeyCode.ESCAPE) { - - try { - if (isHost) { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("Exit Race"); - alert.setContentText("Do you wish to quit the race? You are the host"); - Optional result = alert.showAndWait(); - if (result.get() == ButtonType.OK) { - parent.endEvent(); - race.setVisible(false); - App.app.showMainStage(App.getStage()); - } - } else { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("Exit Race"); - alert.setContentText("Do you wish to quit the race?"); - Optional result = alert.showAndWait(); - if (result.get() == ButtonType.OK) { - race.setVisible(false); - App.app.showMainStage(App.getStage()); - } - } - } catch (IOException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - - /** - * 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); - - //Race timezone label. - initialiseRaceTimezoneLabel(this.visualiserRace); - - //Race clock. - initialiseRaceClock(this.visualiserRace); - - - //Start the race animation timer. - raceTimer(); - } - - - - /** - * 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. - */ - private void initialiseFpsToggle() { - - showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { - if (showFPS.isSelected()) { - FPS.setVisible(true); - - } else { - FPS.setVisible(false); - - } - }); - - } - - /** - * 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())); - }); - - } - - - - /** - * 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 boats = FXCollections.observableArrayList(race.getVisualiserRaceState().getBoats()); - SortedList sortedBoats = new SortedList<>(boats); - sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty()); - - //Update copy when original changes. - race.getVisualiserRaceState().getBoats().addListener((ListChangeListener.Change c) -> Platform.runLater(() -> { - boats.setAll(race.getVisualiserRaceState().getBoats()); - })); - - - //Set up table. - boatInfoTable.setItems(sortedBoats); - - - //Set up each column. - - //Name. - boatTeamColumn.setCellValueFactory( - cellData -> cellData.getValue().nameProperty() ); - - //Speed. - boatSpeedColumn.setCellValueFactory( - cellData -> cellData.getValue().currentSpeedProperty() ); - - //Kind of ugly, but allows for formatting an observed speed. - boatSpeedColumn.setCellFactory( - //Callback object. - new Callback, TableCell>() { - - //Callback function. - @Override - public TableCell call(TableColumn param) { - //We return a table cell that populates itself with a Number, and formats it. - return new TableCell(){ - - //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, TableCell>() { - - //Callback function. - @Override - public TableCell call(TableColumn param) { - //We return a table cell that populates itself with a Leg's name. - return new TableCell(){ - - //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. - */ - 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) { - - //Create canvas. - raceCanvas = new ResizableRaceCanvas(race.getVisualiserRaceState()); - - //Set properties. - raceCanvas.setMouseTransparent(true); - raceCanvas.widthProperty().bind(canvasBase.widthProperty()); - raceCanvas.heightProperty().bind(canvasBase.heightProperty()); - - //Draw it and show it. - 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) -> { - 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 boats) { - race.setVisible(false); - parent.enterFinish(boats); - } - - - /** - * 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. - */ - private void raceTimer() { - 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 (raceStatus == RaceStatusEnum.FINISHED) { - //Stop this timer. - stop(); - - //Hide this, and display the finish controller. - finishRace(visualiserRace.getVisualiserRaceState().getBoats()); - - } else { - //Otherwise, render the canvas. - 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(); - } 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(); - } - - /** - * toggles if the info table is shown - */ - private void toggleTable() { - double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth(); - - 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; - } - -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceFinishController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceFinishController.java new file mode 100644 index 00000000..29d2b290 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceFinishController.java @@ -0,0 +1,39 @@ +package visualiser.Controllers; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import visualiser.model.VisualiserBoat; + +/** + * Finish Screen for when the race finishes. + */ +public class RaceFinishController extends Controller { + private @FXML TableView boatInfoTable; + private @FXML TableColumn boatRankColumn; + private @FXML TableColumn boatNameColumn; + private @FXML Label raceWinnerLabel; + + /** + * Display the table + * @param boats boats to display on the table. + */ + public void loadFinish(ObservableList boats) { + // set table contents + boatInfoTable.setItems(boats); + //Name. + boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + //Rank/position. + boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().placingProperty()); + + //Winner label. + if (boats.size() > 0) { + raceWinnerLabel.setText("Winner: " + + boatNameColumn.getCellObservableValue(0).getValue()); + raceWinnerLabel.setWrapText(true); + } + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceStartController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceStartController.java new file mode 100644 index 00000000..34057d41 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceStartController.java @@ -0,0 +1,141 @@ +package visualiser.Controllers; + +import javafx.animation.AnimationTimer; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RequestToJoinEnum; +import visualiser.gameController.ControllerClient; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + +import java.io.IOException; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Controller to for waiting for the race to start. + */ +public class RaceStartController extends Controller { + private @FXML Label raceTitleLabel; + private @FXML Label raceStartLabel; + private @FXML Label timeZoneTime; + private @FXML Label timer; + private @FXML TableView boatNameTable; + private @FXML TableColumn boatNameColumn; + private @FXML TableColumn boatCodeColumn; + private @FXML Label raceStatusLabel; + + private VisualiserRaceEvent visualiserRaceEvent; + private VisualiserRaceState raceState; + private ControllerClient controllerClient; + private boolean isHost; + + /** + * Show starting information for a race given a socket. + * Intended to be called on loading the scene. + * @param socket network source of information + * @param isHost is user a host + */ + public void enterLobby(Socket socket, Boolean isHost) { + try { + this.isHost = isHost; + this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT); + this.controllerClient = visualiserRaceEvent.getControllerClient(); + this.raceState = visualiserRaceEvent.getVisualiserRaceState(); + showRaceDetails(); + } catch (IOException e) { + //TODO should probably let this propagate, so that we only enter this scene if everything works + Logger.getGlobal() + .log(Level.WARNING, "Could not connect to server.", e); + } + } + + /** + * Displays details and starts the timer for the race being started + */ + private void showRaceDetails() { + raceTitleLabel.setText(this.raceState.getRegattaName()); + initialiseBoatTable(); + initialiseRaceClock(); + countdownTimer(); + } + + /** + * Initialises the boat table that is to be shown on the pane. + */ + private void initialiseBoatTable() { + //Get the boats. + ObservableList boats = + this.raceState.getBoats(); + + //Populate table. + boatNameTable.setItems(boats); + boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty()); + } + + /** + * Initialises the race clock/timer labels for the start time, current time, and remaining time. + */ + private void initialiseRaceClock() { + raceStartLabel.setText( + this.raceState.getRaceClock().getStartingTimeString()); + + // init clock start time + this.raceState.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + raceStartLabel.setText(newValue); + }); + }); + + // init clock current time + this.raceState.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timeZoneTime.setText(newValue); + }); + }); + + // init clock remaining time + this.raceState.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); + } + + /** + * Countdown timer until race starts. + */ + private void countdownTimer() { + new AnimationTimer() { + @Override + public void handle(long arg0) { + // display current race status + RaceStatusEnum raceStatus = raceState.getRaceStatusEnum(); + raceStatusLabel.setText("Race Status: " + raceStatus.name()); + + // if race is in PREPARATORY or STARTED status + if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) { + stop(); // stop this timer + // load up the race scene + try { + RaceViewController rvc = (RaceViewController) + loadScene("raceView.fxml"); + rvc.startRace(visualiserRaceEvent, controllerClient, + isHost); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }.start(); + } + +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java new file mode 100644 index 00000000..46283c63 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java @@ -0,0 +1,567 @@ +package visualiser.Controllers; + +import com.interactivemesh.jfx.importer.stl.StlMeshImporter; +import javafx.animation.AnimationTimer; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.fxml.FXML; +import javafx.scene.chart.LineChart; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.MeshView; +import javafx.scene.shape.Sphere; +import javafx.scene.transform.Translate; +import javafx.util.Callback; +import network.Messages.Enums.RaceStatusEnum; +import shared.dataInput.RaceDataSource; +import shared.model.Leg; +import shared.model.Mark; +import visualiser.app.App; +import visualiser.enums.TutorialState; +import visualiser.gameController.ControllerClient; +import visualiser.gameController.Keys.ControlKey; +import visualiser.gameController.Keys.KeyFactory; +import visualiser.layout.Subject3D; +import visualiser.layout.View3D; +import visualiser.model.Sparkline; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; +import visualiser.utils.GPSConverter; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Controller used to display a running race. + */ +public class RaceViewController extends Controller { + private VisualiserRaceEvent visualiserRace; + private VisualiserRaceState raceState; + private ControllerClient controllerClient; + private KeyFactory keyFactory = new KeyFactory(); + private boolean infoTableShow = true; // shown or hidden + private boolean isHost; + private TutorialState currentState; + private ArrayList tutorialStates; + private boolean isTutorial = false; + private String keyToPress; + private View3D view3D; + private ObservableList viewSubjects; + + // note: it says it's not used but it is! do not remove :) + private @FXML ArrowController arrowController; + private @FXML GridPane canvasBase; + private @FXML SplitPane racePane; + private @FXML StackPane arrowPane; + private @FXML Label timer; + private @FXML Label FPS; + private @FXML Label timeZone; + private @FXML CheckBox showFPS; + private @FXML TableView boatInfoTable; + private @FXML TableColumn boatPlacingColumn; + private @FXML TableColumn boatTeamColumn; + private @FXML TableColumn boatMarkColumn; + private @FXML TableColumn boatSpeedColumn; + private @FXML LineChart sparklineChart; + private @FXML Label tutorialText; + + /** + * 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 + */ + public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) { + this.visualiserRace = visualiserRace; + this.raceState = visualiserRace.getVisualiserRaceState(); + this.controllerClient = controllerClient; + this.isHost = isHost; + keyFactory.load(); + + tutorialCheck(); + initKeypressHandler(); + initialiseRaceVisuals(); + } + + /** + * Checks if the current game is a tutorial race and sets up initial + * tutorial displays if it is. + */ + private void tutorialCheck(){ + if (App.gameType == 4) { + isTutorial = true; + tutorialText.setVisible(true); + tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values())); + + currentState = tutorialStates.get(0); + tutorialStates.remove(0); + searchMapForKey("Upwind"); + + tutorialText.setText( + "Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " + + keyToPress + " to turn upwind."); + + } else { + isTutorial = false; + tutorialText.setVisible(false); + } + } + + /** + * Sets up the listener and actions that occur when a key is pressed. + */ + private void initKeypressHandler() { + racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + String codeString = event.getCode().toString(); + + // tab key + if (codeString.equals("TAB")){toggleTable();} + + // any key pressed + ControlKey controlKey = keyFactory.getKey(codeString); + if(controlKey != null) { + try { + controlKey.onAction(); // Change key state if applicable + + //Check if current race is a tutorial + if (isTutorial){ + //Check if current tutorial state has the same boat protocol code as key press + if (controlKey.getProtocolCode().equals(currentState.getAction())){ + //Update tutorial + checkTutorialState(); + } + } + + controllerClient.sendKey(controlKey); + event.consume(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Logger.getGlobal().log(Level.WARNING, "RaceViewController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); + Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // escape key + if(event.getCode() == KeyCode.ESCAPE) { + try { + if (isHost) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Exit Race"); + alert.setContentText("Do you wish to quit the race? You are the host"); + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + App.game.endEvent(); + loadTitleScreen(); + } + } else { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Exit Race"); + alert.setContentText("Do you wish to quit the race?"); + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + loadTitleScreen(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + /** + * Initialises the various UI components to listen to the {@link #visualiserRace}. + */ + private void initialiseRaceVisuals() { + // initialise displays + initialiseFps(); + initialiseInfoTable(); + initialiseView3D(); + initialiseRaceClock(); + raceTimer(); // start the timer + new Sparkline(this.raceState, this.sparklineChart); + timeZone.setText(this.raceState.getRaceClock().getTimeZone()); + arrowController.setWindProperty(this.raceState.windProperty()); + } + + private void initialiseView3D() { + viewSubjects = FXCollections.observableArrayList(); + + // Import boat mesh + URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 " + + "Complete Boat.stl"); + StlMeshImporter importer = new StlMeshImporter(); + importer.read(asset); + + // Configure camera angles and control + view3D = new View3D(); + view3D.setDistance(1050); + view3D.setYaw(0); + view3D.setPitch(60); + view3D.enableTracking(); + canvasBase.add(view3D, 0, 0); + + // Set up projection from GPS to view + RaceDataSource raceData = raceState.getRaceDataSource(); + final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450); + + view3D.setItems(viewSubjects); + // Position and add each mark to view + for (Mark mark : raceState.getMarks()) { + Subject3D subject = new Subject3D(new Sphere(2)); + subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX()); + subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY()); + + viewSubjects.add(subject); + } + // Position and add each boat to view + for (VisualiserBoat boat : raceState.getBoats()) { + MeshView mesh = new MeshView(importer.getImport()); + Subject3D subject = new Subject3D(mesh); + viewSubjects.add(subject); + + // Track this boat's movement with the new subject + AnimationTimer trackBoat = new AnimationTimer() { + @Override public void handle(long now) { + subject.setHeading(boat.getBearing().degrees()); + subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX()); + subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY()); + } + }; + trackBoat.start(); + } + // Fix initial bird's-eye position + view3D.updatePivot(new Translate(250, 0, 210)); + + // Bind zooming to scrolling + view3D.setOnScroll(e -> { + view3D.updateDistance(e.getDeltaY()); + }); + + // Bind zooming to keypress (Z/X default) + racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> { + ControlKey key = keyFactory.getKey(e.getCode().toString()); + if (key != null) { + switch (key.toString()) { + case "Zoom In": + //Check if race is a tutorial + if (isTutorial) { + //Check if the current tutorial state is zoom-in + if (currentState.equals(TutorialState.ZOOMIN)) { + try { + //Update tutorial + checkTutorialState(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + } + view3D.updateDistance(-10); + break; + case "Zoom Out": + //Check if race is a tutorial + if(isTutorial) { + //Check if current tutorial state is zoom-out + if (currentState.equals(TutorialState.ZOOMOUT)) { + try { + //Update tutorial + checkTutorialState(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + } + view3D.updateDistance(10); + break; + } + } + }); + } + + /** + * 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() { + // fps toggle listener + showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { + if (showFPS.isSelected()) { + FPS.setVisible(true); + } else { + FPS.setVisible(false); + } + }); + + // 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. + */ + private void initialiseInfoTable() { + // list of boats to display data for + ObservableList boats = FXCollections + .observableArrayList(this.visualiserRace.getVisualiserRaceState().getBoats()); + SortedList sortedBoats = new SortedList<>(boats); + sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty()); + + // update list when boat information changes + this.visualiserRace.getVisualiserRaceState().getBoats().addListener( + (ListChangeListener.Change c) -> Platform.runLater(() -> { + boats.setAll(this.visualiserRace.getVisualiserRaceState().getBoats()); + })); + + // set table data + boatInfoTable.setItems(sortedBoats); + boatTeamColumn.setCellValueFactory( + cellData -> cellData.getValue().nameProperty()); + 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( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn param) { + //We return a table cell that populates itself with a Number, and formats it. + return new TableCell(){ + + //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())); + } + } + }; + } + }); + + //Kind of ugly, but allows for turning an observed Leg into a string. + boatMarkColumn.setCellFactory( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn param) { + //We return a table cell that populates itself with a Leg's name. + return new TableCell(){ + + //Function to update the cell text. + @Override + protected void updateItem(Leg item, boolean empty) { + if (item != null) { + super.updateItem(item, empty); + setText(item.getName()); + } + } + }; + } + }); + } + + /** + * Initialises the race clock to listen to the specified race. + */ + private void initialiseRaceClock() { + raceState.getRaceClock().durationProperty().addListener((observable, + oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); + } + + /** + * Transition from the race view to the finish view. + */ + private void finishRace() throws IOException { + RaceFinishController fc = + (RaceFinishController)loadScene("raceFinish.fxml"); + fc.loadFinish(raceState.getBoats()); + } + + /** + * Timer which monitors the race. + */ + private void raceTimer() { + new AnimationTimer() { + @Override + public void handle(long arg0) { + //If the race has finished, go to finish view. + if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) { + stop(); // stop the timer + try { + finishRace(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + boatInfoTable.sort(); + } + + //Return to main screen if we lose connection. + if (!visualiserRace.getServerConnection().isAlive()) { + try { + loadTitleScreen(); + } catch (Exception e) { + e.printStackTrace(); + } + //TODO we should display an error to the user + //TODO also need to "reset" any state (race, connections, etc...). + } + } + }.start(); + } + + /** + * toggles if the info table is shown + */ + private void toggleTable() { + double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + + boatSpeedColumn.getPrefWidth())/racePane.getWidth(); + + if (infoTableShow) { + racePane.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 { + racePane.setDividerPositions(1); + arrowPane.setScaleX(1); + arrowPane.setScaleY(1); + arrowPane.setTranslateX(0); + arrowPane.setTranslateY(0); + } + boatInfoTable.refresh(); + infoTableShow = !infoTableShow; + } + + /** + * Get the next tutorial state + */ + private void updateTutorialState(){ + //Next tutorial state is popped from list + currentState = tutorialStates.get(0); + tutorialStates.remove(0); + } + + /** + * Search key map for key given string of command + * @param command the command of the key + */ + private void searchMapForKey(String command){ + //For loop through keyFactory + for (Map.Entry entry: keyFactory.getKeyState().entrySet()){ + if(entry.getValue().toString().equals(command)){ + + //Found next key required to press + keyToPress = entry.getKey(); + } + } + } + + /** + * Updates tutorial state and gui display for tutorial text + * @throws Exception Exception thrown + */ + private void checkTutorialState() throws Exception { + //Switch statement to check what the current tutorial state is + switch (currentState){ + case UPWIND: + //Set next key to press as the downwind key + searchMapForKey("Downwind"); + //Update tutorial text + tutorialText.setText("Nice! To turn downwind press " + keyToPress + "."); + updateTutorialState(); + break; + case DOWNWIND: + //Set next key to press as the tack/gybe key + searchMapForKey("Tack/Gybe"); + //Update tutorial text + tutorialText.setText("Nice! To tack or gybe press " + keyToPress + "."); + updateTutorialState(); + break; + case TACKGYBE: + //Set next key to press as the VMG key + searchMapForKey("VMG"); + //Update tutorial text + tutorialText.setText("Nice! To use VMG press " + keyToPress + ". This will turn the boat."); + updateTutorialState(); + break; + case VMG: + //Set next key to press as the sails-in key + searchMapForKey("Toggle Sails"); + //Update tutorial text + tutorialText.setText("Nice! To sails in press " + keyToPress + ". This will stop the boat."); + updateTutorialState(); + break; + case SAILSIN: + //Set next key to press as the sails-out key + searchMapForKey("Toggle Sails"); + //Update tutorial text + tutorialText.setText("Nice! To sails out press " + keyToPress + " again. The will start moving again."); + updateTutorialState(); + break; + case SAILSOUT: + //Set next key to press as the zoom-in key + searchMapForKey("Zoom In"); + //Update tutorial text + tutorialText.setText("Nice! To zoom in press " + keyToPress + "."); + updateTutorialState(); + break; + case ZOOMIN: + //Set next key to press as the zoom-out key + searchMapForKey("Zoom Out"); + //Update tutorial text + tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. To zoom out press " + keyToPress + "."); + updateTutorialState(); + break; + case ZOOMOUT: + //Finished tutorial. Display pop-up for exiting the tutorial + tutorialText.setText("Congratulations! You're done!"); + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Finished Tutorial"); + alert.setHeaderText("You have finished the tutorial."); + alert.setContentText("Now you know the controls you are ready to race!"); + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + App.game.endEvent(); + loadTitleScreen(); + } + break; + default: + //State not found. Exit tutorial to title menu + App.game.endEvent(); + loadTitleScreen(); + break; + } + } + +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java deleted file mode 100644 index 890eb816..00000000 --- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java +++ /dev/null @@ -1,275 +0,0 @@ -package visualiser.Controllers; - -import javafx.animation.AnimationTimer; -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import mock.model.commandFactory.CompositeCommand; -import network.Messages.Enums.RaceStatusEnum; -import network.Messages.Enums.RequestToJoinEnum; -import network.Messages.LatestMessages; -import shared.dataInput.*; -import shared.enums.XMLFileType; -import shared.exceptions.InvalidBoatDataException; -import shared.exceptions.InvalidRaceDataException; -import shared.exceptions.InvalidRegattaDataException; -import shared.exceptions.XMLReaderException; -import visualiser.gameController.ControllerClient; -import visualiser.model.VisualiserRaceState; -import visualiser.network.ServerConnection; -import visualiser.model.VisualiserBoat; -import visualiser.model.VisualiserRaceEvent; - -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - - -/** - * Controller to for waiting for the race to start. - */ -public class StartController extends Controller { - - @FXML private GridPane start; - @FXML private AnchorPane startWrapper; - - /** - * The name of the race/regatta. - */ - @FXML private Label raceTitleLabel; - - /** - * The time the race starts at. - */ - @FXML private Label raceStartLabel; - - /** - * The current time at the race location. - */ - @FXML private Label timeZoneTime; - - /** - * Time until the race starts. - */ - @FXML private Label timer; - - @FXML private TableView boatNameTable; - @FXML private TableColumn boatNameColumn; - @FXML private TableColumn boatCodeColumn; - - /** - * The status of the race. - */ - @FXML private Label raceStatusLabel; - - - /** - * The race + connection to server. - */ - private VisualiserRaceEvent visualiserRaceEvent; - - - /** - * Writes BoatActions to outgoing message queue. - */ - private ControllerClient controllerClient; - - private boolean isHost; - - - - - - - /** - * Ctor. - */ - public StartController() { - } - - @Override - public void initialize(URL location, ResourceBundle resources) { - } - - - /** - * Starts the race. - */ - private void startRace() { - - //Initialise the boat table. - initialiseBoatTable(this.visualiserRaceEvent.getVisualiserRaceState()); - - //Initialise the race name. - initialiseRaceName(this.visualiserRaceEvent.getVisualiserRaceState()); - - //Initialises the race clock. - initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState()); - - //Starts the race countdown timer. - countdownTimer(); - } - - - - - public AnchorPane startWrapper(){ - return startWrapper; - } - - - /** - * Initialises the boat table that is to be shown on the pane. - * @param visualiserRace The race to get data from. - */ - private void initialiseBoatTable(VisualiserRaceState visualiserRace) { - - //Get the boats. - ObservableList boats = visualiserRace.getBoats(); - - //Populate table. - boatNameTable.setItems(boats); - boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); - boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty()); - } - - /** - * Initialises the race name which is shown on the pane. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceName(VisualiserRaceState visualiserRace) { - - raceTitleLabel.setText(visualiserRace.getRegattaName()); - - } - - /** - * Initialises the race clock/timer labels for the start time, current time, and remaining time. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceClock(VisualiserRaceState visualiserRace) { - - //Start time. - initialiseRaceClockStartTime(visualiserRace); - - //Current time. - initialiseRaceClockCurrentTime(visualiserRace); - - //Remaining time. - initialiseRaceClockDuration(visualiserRace); - - } - - - /** - * Initialises the race current time label. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceClockStartTime(VisualiserRaceState visualiserRace) { - - raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString()); - - visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - raceStartLabel.setText(newValue); - }); - }); - - } - - - /** - * Initialises the race current time label. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceClockCurrentTime(VisualiserRaceState visualiserRace) { - - visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - timeZoneTime.setText(newValue); - }); - }); - - } - - /** - * Initialises the race duration label. - * @param visualiserRace The race to get data from. - */ - private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) { - - visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - timer.setText(newValue); - }); - }); - - } - - /** - * Countdown timer until race starts. - */ - private void countdownTimer() { - new AnimationTimer() { - @Override - public void handle(long arg0) { - - //Get the current race status. - RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); - - //Display it. - raceStatusLabel.setText("Race Status: " + raceStatus.name()); - - - //If the race has reached the preparatory phase, or has started... - if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) { - //Stop this timer. - stop(); - - //Hide this, and display the race controller. - startWrapper.setVisible(false); - //start.setVisible(false);//TODO is this needed? - - parent.beginRace(visualiserRaceEvent, controllerClient, isHost); - - } - } - }.start(); - } - - - - /** - * Show starting information for a race given a socket. - * @param socket network source of information - * @param isHost is user a host - */ - public void enterLobby(Socket socket, Boolean isHost) { - try { - - this.isHost = isHost; - - this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT); - - this.controllerClient = visualiserRaceEvent.getControllerClient(); - - - startWrapper.setVisible(true); - - startRace(); - - } catch (IOException e) { - //TODO should probably let this propagate, so that we only enter this scene if everything works - Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e); - } - } - -} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java index aa2edf13..43d22e5e 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java @@ -1,21 +1,16 @@ package visualiser.Controllers; -import javafx.event.EventHandler; 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.Label; import javafx.scene.control.RadioButton; -import javafx.scene.layout.AnchorPane; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; import javafx.stage.Modality; -import javafx.stage.Stage; -import javafx.stage.WindowEvent; +import mock.exceptions.EventConstructionException; import visualiser.app.App; import java.io.IOException; -import java.net.URL; -import java.util.ResourceBundle; /** * Controller for the opening title window. @@ -24,15 +19,11 @@ import java.util.ResourceBundle; * the game. */ public class TitleController extends Controller { - //FXML stuff - @FXML - Button btnJoin; - @FXML - AnchorPane titleWrapper; - @FXML - RadioButton dayModeRD; - @FXML - RadioButton nightModeRD; + private @FXML RadioButton dayModeRD; + private @FXML RadioButton nightModeRD; + private @FXML Label tutorialLabel; + private @FXML Pane menuPane; + private @FXML ImageView imgSun; /** * Method called when the 'host a game' button is pressed. @@ -41,26 +32,15 @@ public class TitleController extends Controller { * @throws IOException if main has problems */ public void hostAGame() throws IOException { - titleWrapper.setVisible(false); - parent.hostGame(); - App.getStage().setResizable(true); - } - - /** - * Switch the scene to the title page. - */ - public void enterTitle(){ - titleWrapper.setVisible(true); + loadScene("hostGame.fxml"); } /** * To be implemented at a later date- will open the next scene displaying * games a player can join. Place holder method for now! */ - public void joinAGame() { - titleWrapper.setVisible(false); - parent.enterLobby(); - App.getStage().setResizable(true); + public void joinAGame() throws IOException { + loadScene("lobby.fxml"); } /** @@ -68,8 +48,12 @@ public class TitleController extends Controller { */ public void setDayMode(){ dayModeRD.getScene().getStylesheets().clear(); + menuPane.getStylesheets().clear(); + imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sun.png").toExternalForm())); dayModeRD.getScene().getStylesheets().add("/css/dayMode.css"); + menuPane.setStyle("-fx-background-color: #6be6ff;"); nightModeRD.setSelected(false); + App.dayMode = true; } /** @@ -77,41 +61,32 @@ public class TitleController extends Controller { */ public void setNightMode(){ nightModeRD.getScene().getStylesheets().clear(); + menuPane.getStylesheets().clear(); + imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sunsleep.png").toExternalForm())); nightModeRD.getScene().getStylesheets().add("/css/nightMode.css"); + menuPane.setStyle("-fx-background-color: #1f2c60;"); dayModeRD.setSelected(false); - } - - @Override - public void initialize(URL location, ResourceBundle resources) { + App.dayMode = false; } /** * Called when control button is pressed. New pop up window displaying controls */ - public void controlBtnPressed(){ + public void showControls(){ try { - FXMLLoader loader = new FXMLLoader(); - loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml")); - Parent layout = loader.load(); - Scene scene = new Scene(layout); - Stage popupStage = new Stage(); - popupStage.setResizable(false); - popupStage.setTitle("Game Controls"); - popupStage.initModality(Modality.WINDOW_MODAL); - popupStage.centerOnScreen(); - popupStage.setScene(scene); - popupStage.show(); - KeyBindingsController controller = loader.getController(); - popupStage.setOnCloseRequest(new EventHandler() { - public void handle(WindowEvent we) { - if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) { - controller.onExit(we); - } - } - }); - } catch (Exception e){ + loadPopupScene("keyBindings.fxml", + "Game Controls", Modality.WINDOW_MODAL); + } catch (IOException e) { e.printStackTrace(); } } -} + public void tutorialStartPressed() throws IOException, + EventConstructionException { + App.gameType = 4; + HostGameController hgc = new HostGameController(); + hgc.setCurrentMapIndex(4); + hgc.hostGamePressed(); + } + +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java index e095e04e..2e3287ae 100644 --- a/racevisionGame/src/main/java/visualiser/app/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -7,7 +7,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.concurrent.Worker; -import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; @@ -24,25 +23,22 @@ import javafx.scene.paint.Color; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; -import javafx.stage.WindowEvent; import javafx.util.Duration; -import visualiser.Controllers.MainController; -import visualiser.gameController.Keys.KeyFactory; +import mock.app.Event; public class App extends Application { - private static Stage stage; + public static Event game; + public static Boolean dayMode = true; + public static Integer gameType = 0; private Pane splashLayout; private ProgressBar loadProgress; private Label progressText; private static final int SPLASH_WIDTH = 676; private static final int SPLASH_HEIGHT = 227; - public static KeyFactory keyFactory = new KeyFactory(); - public static App app; /** * Entry point for running the programme - * * @param args for starting the programme */ public static void main(String[] args) { @@ -51,9 +47,6 @@ public class App extends Application { @Override public void init() { - // load the user's personalised key bindings - keyFactory.load(); - ImageView splash = new ImageView(new Image( getClass().getClassLoader().getResourceAsStream("images/splashScreen.png") )); @@ -79,15 +72,15 @@ public class App extends Application { /** * Method that sets up and displays the splash screen - * @param initStage the inital stage + * @param stage the initial stage * @throws Exception if something wrong with title screen. */ - public void start(Stage initStage) throws Exception { + public void start(Stage stage) throws Exception { final Task> boatTask = new Task>() { @Override protected ObservableList call() throws InterruptedException { ObservableList addedFilling = - FXCollections.observableArrayList(); + FXCollections.observableArrayList(); ObservableList burgerFilling = FXCollections.observableArrayList( "Buns", "Patties", "Lettuce", "Onions", "Tomato", @@ -95,7 +88,7 @@ public class App extends Application { ); updateMessage("Preparing ingredients . . ."); - Thread.sleep(200); + Thread.sleep(100); for (int i = 0; i < burgerFilling.size(); i++) { Thread.sleep(100); updateProgress(i + 1, burgerFilling.size()); @@ -105,69 +98,49 @@ public class App extends Application { } Thread.sleep(100); updateMessage("Burger's done!"); - return addedFilling; } }; - showSplash( - initStage, + stage, boatTask, () -> { try { - showMainStage(new Stage()); + loadTitleScreen(); } catch (Exception e) { e.printStackTrace(); } } ); new Thread(boatTask).start(); - } /** - * Get main stage - * @return main stage + * Get the main stage to be shared for all regular game play scenes. + * @return shared stage */ public static Stage getStage() { return App.stage; } /** - * Set main stage - * @param stage stage to set main stage + * Loads the title screen for the first time on app start. + * @throws Exception if there is a problem with a resource loaded */ - public static void setStage(Stage stage) { - App.stage = stage; - } - - /** - * Show the main stage after the splash screen - * @param stage main stage for application - * @throws Exception Throws an exception on error - */ - public void showMainStage(Stage stage) throws Exception { - App.stage = stage; - App.app = this; - FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/main.fxml")); + private void loadTitleScreen() throws Exception { + stage = new Stage(); + FXMLLoader loader = new FXMLLoader(getClass().getResource + ("/visualiser/scenes/title.fxml")); Parent root = loader.load(); stage.setResizable(false); - MainController mc = (MainController) loader.getController(); - mc.enterTitle(); Scene scene = new Scene(root); - - stage.setScene(scene); - stage.setTitle("RaceVision - Team 7"); + stage.setTitle("The Boat Game - Burgers & Boats"); stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png"))); - mc.startCss(); - setStage(stage); + stage.setScene(scene); stage.show(); - stage.setOnCloseRequest(new EventHandler() { - @Override - public void handle(WindowEvent event) { - Platform.exit(); - System.exit(0); - } + stage.setOnCloseRequest(event -> { + Platform.exit(); + System.exit(0); }); } diff --git a/racevisionGame/src/main/java/visualiser/enums/TutorialState.java b/racevisionGame/src/main/java/visualiser/enums/TutorialState.java new file mode 100644 index 00000000..9764f130 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/enums/TutorialState.java @@ -0,0 +1,60 @@ +package visualiser.enums; + +import network.Messages.Enums.BoatActionEnum; + +/** + * State of which stage the tutorial is currently in + */ +public enum TutorialState { + + /** + * State for upwind in tutorial + */ + UPWIND(BoatActionEnum.UPWIND), + + /** + * State for downwind in tutorial + */ + DOWNWIND(BoatActionEnum.DOWNWIND), + + /** + * State for tacking/gybing in tutorial + */ + TACKGYBE(BoatActionEnum.TACK_GYBE), + + /** + * State for vmg in tutorial + */ + VMG(BoatActionEnum.AUTO_PILOT), + + /** + * State for sails-in in tutorial + */ + SAILSIN(BoatActionEnum.SAILS_IN), + + /** + * State for sails-out in tutorial + */ + SAILSOUT(BoatActionEnum.SAILS_OUT), + + /** + * State for zoom-in in tutorial + */ + ZOOMIN(null), + + /** + * State for zoom-out in tutorial + */ + ZOOMOUT(null); + + private BoatActionEnum action; + + TutorialState(BoatActionEnum action){ + this.action = action; + } + + public BoatActionEnum getAction(){ + return action; + } + +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java b/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java index b4b3de9b..88cdcb74 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java +++ b/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java @@ -3,15 +3,14 @@ package visualiser.gameController; import javafx.animation.AnimationTimer; import javafx.scene.Scene; import visualiser.gameController.Keys.ControlKey; - +import visualiser.gameController.Keys.KeyFactory; import java.util.HashMap; -import static visualiser.app.App.keyFactory; - /** * Class for checking what keys are currently being used */ public class InputChecker { + private KeyFactory keyFactory; private HashMap currentlyActiveKeys = new HashMap<>(); /** @@ -19,7 +18,8 @@ public class InputChecker { * @param scene Scene the controller is to run in parallel with. */ public void runWithScene(Scene scene){ -// KeyFactory keyFactory = KeyFactory.getFactory(); + KeyFactory keyFactory = new KeyFactory(); + keyFactory.load(); scene.setOnKeyPressed(event -> { String codeString = event.getCode().toString(); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java index dd489f73..ce4b341e 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java @@ -1,6 +1,5 @@ package visualiser.gameController.Keys; -import javafx.scene.input.KeyCode; import network.Messages.Enums.BoatActionEnum; /** @@ -45,7 +44,7 @@ public abstract class ControlKey { /** * What this key should do when the command is issued for it to do its job. */ - public abstract void onAction();//may want to make it take in a visualiser and stuff in the future. + public abstract void onAction(); /** * What to do when the key is held diff --git a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java new file mode 100644 index 00000000..af76f4f4 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java @@ -0,0 +1,65 @@ +package visualiser.layout; + +import javafx.scene.shape.Shape3D; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; + +/** + * Wrapper for controlling the position and heading of rendered 3D models. + */ +public class Subject3D { + /** + * Rendered mesh + */ + private Shape3D mesh; + + /** + * Position translation updated by state listeners + */ + private Translate position; + + /** + * Heading rotation updated by state listeners + */ + private Rotate heading; + + /** + * Constructor for view subject wrapper + * @param mesh to be rendered + */ + public Subject3D(Shape3D mesh) { + this.mesh = mesh; + this.position = new Translate(); + this.heading = new Rotate(0, Rotate.Y_AXIS); + + this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS)); + } + + public Shape3D getMesh() { + return mesh; + } + + public Translate getPosition() { + return this.position; + } + + public Rotate getHeading() { + return heading; + } + + public void setX(double x) { + position.setX(x); + } + + public void setY(double y) { + position.setY(y); + } + + public void setZ(double z) { + position.setZ(z); + } + + public void setHeading(double angle) { + heading.setAngle(angle); + } +} diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java new file mode 100644 index 00000000..27fe6086 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -0,0 +1,260 @@ +package visualiser.layout; + +import javafx.beans.value.ChangeListener; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.Group; +import javafx.scene.PerspectiveCamera; +import javafx.scene.SubScene; +import javafx.scene.input.PickResult; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Shape3D; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; + +import java.util.HashMap; +import java.util.Map; + +/** + * Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to + * interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and + * sky box, whose textures are set with special methods. + */ +public class View3D extends Pane { + /** + * Container for group and camera + */ + private SubScene scene; + /** + * Observable list of renderable items + */ + private ObservableList items; + /** + * Map for selecting Subject3D from Shape3D + */ + private Map selectionMap; + /** + * Subject tracked by camera + */ + private Subject3D target; + /** + * Rendering container for shapes + */ + private Group world; + /** + * Near limit of view frustum + */ + private double nearClip; + /** + * Far limit of view frustum + */ + private double farClip; + /** + * Camera origin + */ + private Translate pivot; + /** + * Distance of camera from pivot point + */ + private Translate distance; + /** + * Angle along ground between z-axis and camera + */ + private Rotate yaw; + /** + * Angle between ground plane and camera direction + */ + private Rotate pitch; + /** + * Single listener for subject heading changes + */ + private ChangeListener pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr); + /** + * Single listener for subject position (x) changes + */ + private ChangeListener pivotX = (o, prev, curr) -> pivot.setX((double)curr); + /** + * Single listener for subject position (y) changes + */ + private ChangeListener pivotY = (o, prev, curr) -> pivot.setY((double)curr); + /** + * Single listener for subject position (z) changes + */ + private ChangeListener pivotZ = (o, prev, curr) -> pivot.setZ((double)curr); + /** + * Distance to switch from third person to bird's eye + */ + private double THIRD_PERSON_LIMIT = 100; + + /** + * Default constructor for View3D. Sets up Scene and PerspectiveCamera. + */ + public View3D() { + this.world = new Group(); + this.selectionMap = new HashMap<>(); + this.target = null; + this.scene = new SubScene(world, 300, 300); + + scene.widthProperty().bind(this.widthProperty()); + scene.heightProperty().bind(this.heightProperty()); + scene.setFill(new Color(0.2, 0.6, 1, 1)); + + scene.setCamera(buildCamera()); + + this.getChildren().add(scene); + } + + /** + * Sets up camera view frustum and binds transformations + * @return perspective camera + */ + private PerspectiveCamera buildCamera() { + PerspectiveCamera camera = new PerspectiveCamera(true); + + // Set up view frustum + nearClip = 0.1; + farClip = 3000.0; + camera.setNearClip(nearClip); + camera.setFarClip(farClip); + + // Set up transformations + pivot = new Translate(); + distance = new Translate(); + yaw = new Rotate(0, Rotate.Y_AXIS); + pitch = new Rotate(0, Rotate.X_AXIS); + camera.getTransforms().addAll(pivot, yaw, pitch, distance); + + return camera; + } + + /** + * Provide the list of subjects to be automatically added or removed from the view as the list + * changes. + * @param items list managed by client + */ + public void setItems(ObservableList items) { + this.items = items; + this.items.addListener((ListChangeListener) c -> { + while(c.next()) { + if (c.wasRemoved() || c.wasAdded()) { + for (Subject3D shape : c.getRemoved()) { + world.getChildren().remove(shape.getMesh()); + selectionMap.remove(shape.getMesh()); + } + for (Subject3D shape : c.getAddedSubList()) { + world.getChildren().add(shape.getMesh()); + selectionMap.put(shape.getMesh(), shape); + } + } + } + }); + } + + /** + * Intercept mouse clicks on subjects in view. The applied listener cannot be removed. + */ + public void enableTracking() { + scene.setOnMousePressed(e -> { + PickResult result = e.getPickResult(); + if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { + trackSubject(selectionMap.get(result.getIntersectedNode())); + } + }); + } + + /** + * Stop camera from following the last selected subject + */ + private void untrackSubject() { + if(target != null) { + target.getPosition().xProperty().removeListener(pivotX); + target.getPosition().yProperty().removeListener(pivotY); + target.getPosition().zProperty().removeListener(pivotZ); + target.getHeading().angleProperty().removeListener(pivotHeading); + } + } + + /** + * Set camera to follow the selected subject + * @param subject to track + */ + private void trackSubject(Subject3D subject) { + untrackSubject(); + target = subject; + + updatePivot(target.getPosition()); + setYaw(target.getHeading().getAngle()); + + target.getPosition().xProperty().addListener(pivotX); + target.getPosition().yProperty().addListener(pivotY); + target.getPosition().zProperty().addListener(pivotZ); + target.getHeading().angleProperty().addListener(pivotHeading); + + this.setDistance(THIRD_PERSON_LIMIT); + this.setPitch(20); + } + + public void setNearClip(double nearClip) { + this.nearClip = nearClip; + } + + public void setFarClip(double farClip) { + this.farClip = farClip; + } + + /** + * Sets the coordinates of the camera pivot once. + * @param pivot source of coordinates + */ + public void updatePivot(Translate pivot) { + this.pivot.setX(pivot.getX()); + this.pivot.setY(pivot.getY()); + this.pivot.setZ(pivot.getZ()); + } + + /** + * Set distance of camera from pivot + * @param distance in units + */ + public void setDistance(double distance) { + this.distance.setZ(-distance); + } + + /** + * Adds delta to current distance and changes camera mode if applicable. + * Third person limit specifies the distance at which a third person camera + * switches to bird's-eye, remaining focused on the same position. + * @param delta amount to change distance by + */ + public void updateDistance(double delta) { + double distance = -this.distance.getZ() + delta; + + if(distance <= 0) { + this.setDistance(0); + } else if(distance > THIRD_PERSON_LIMIT) { + untrackSubject(); + this.setYaw(0); + this.setPitch(60); + this.setDistance(distance); + } else { + this.setDistance(distance); + } + } + + /** + * Set angle of camera from z-axis along ground + * @param yaw in degrees + */ + public void setYaw(double yaw) { + this.yaw.setAngle(yaw); + } + + /** + * Set elevation of camera + * @param pitch in degrees + */ + public void setPitch(double pitch) { + this.pitch.setAngle(-pitch); + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/Annotations.java b/racevisionGame/src/main/java/visualiser/model/Annotations.java index 54976c35..f5f2c46d 100644 --- a/racevisionGame/src/main/java/visualiser/model/Annotations.java +++ b/racevisionGame/src/main/java/visualiser/model/Annotations.java @@ -15,7 +15,7 @@ import java.util.Map; * Class that processes user selected annotation visibility options to * display the requested information on the * {@link ResizableRaceCanvas}. These are displayed - * via the {@link visualiser.Controllers.RaceController}.
+ * via the {@link visualiser.Controllers.RaceViewController}.
* Annotation options for a {@link VisualiserBoat} include: its name, * abbreviation, speed, the time since it passed the last * {@link shared.model.Mark}, estimated time to the next marker, diff --git a/racevisionGame/src/main/java/visualiser/model/BoatDisplay3D.java b/racevisionGame/src/main/java/visualiser/model/BoatDisplay3D.java deleted file mode 100644 index 9314f5cd..00000000 --- a/racevisionGame/src/main/java/visualiser/model/BoatDisplay3D.java +++ /dev/null @@ -1,18 +0,0 @@ -package visualiser.model; - -import com.interactivemesh.jfx.importer.Importer; -import javafx.scene.layout.Pane; - -/** - * Created by fwy13 on 29/08/17. - */ -public class BoatDisplay3D extends Pane { - - - public BoatDisplay3D(String filePath){ - super(); -// Shape3D -// this.getChildren().add(); - } - -} diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 6c0dee48..cc485445 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -18,7 +18,7 @@ import java.util.List; /** * This JavaFX Canvas is used to update and display details for a * {@link RaceMap} via the - * {@link visualiser.Controllers.RaceController}.
+ * {@link visualiser.Controllers.RaceViewController}.
* It fills it's parent and cannot be downsized.
* Details displayed include: * {@link VisualiserBoat}s (and their diff --git a/racevisionGame/src/main/java/visualiser/model/Sparkline.java b/racevisionGame/src/main/java/visualiser/model/Sparkline.java index 3f7e07b8..e50931be 100644 --- a/racevisionGame/src/main/java/visualiser/model/Sparkline.java +++ b/racevisionGame/src/main/java/visualiser/model/Sparkline.java @@ -21,7 +21,7 @@ import java.util.Map; * placing position as they complete each {@link shared.model.Leg} by * passing a course {@link shared.model.Mark}.
* This sparkline is displayed using the - * {@link visualiser.Controllers.RaceController}. + * {@link visualiser.Controllers.RaceViewController}. */ public class Sparkline { diff --git a/racevisionGame/src/main/java/visualiser/model/TrackPoint.java b/racevisionGame/src/main/java/visualiser/model/TrackPoint.java index f3378a43..60a07d3b 100644 --- a/racevisionGame/src/main/java/visualiser/model/TrackPoint.java +++ b/racevisionGame/src/main/java/visualiser/model/TrackPoint.java @@ -8,8 +8,8 @@ import shared.model.GPSCoordinate; * {@link VisualiserBoat Boat} has travelled in a race.
* TrackPoints are displayed on a * {@link ResizableRaceCanvas}, via the - * {@link visualiser.Controllers.RaceController}.
- * Track points can be made visible or hidden via the RaceController's + * {@link visualiser.Controllers.RaceViewController}.
+ * Track points can be made visible or hidden via the RaceViewController's * {@link Annotations}. */ public class TrackPoint { diff --git a/racevisionGame/src/main/java/visualiser/model/View3D.java b/racevisionGame/src/main/java/visualiser/model/View3D.java deleted file mode 100644 index 6affb906..00000000 --- a/racevisionGame/src/main/java/visualiser/model/View3D.java +++ /dev/null @@ -1,145 +0,0 @@ -package visualiser.model; - -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.scene.Group; -import javafx.scene.PerspectiveCamera; -import javafx.scene.SubScene; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Shape3D; -import javafx.scene.transform.Rotate; -import javafx.scene.transform.Translate; - -/** - * Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to - * interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and - * sky box, whose textures are set with special methods. - */ -public class View3D extends Pane { - /** - * Observable list of renderable items - */ - private ObservableList items; - /** - * Rendering container for shapes - */ - private Group world; - /** - * Near limit of view frustum - */ - private double nearClip; - /** - * Far limit of view frustum - */ - private double farClip; - /** - * Position camera pivots around - */ - private Translate pivot; - /** - * Distance of camera from pivot point - */ - private Translate distance; - /** - * Angle along ground between z-axis and camera - */ - private Rotate yaw; - /** - * Angle between ground plane and camera direction - */ - private Rotate pitch; - - /** - * Default constructor for View3D. Sets up Scene and PerspectiveCamera. - */ - public View3D() { - world = new Group(); - - SubScene scene = new SubScene(world, 300, 300); - scene.widthProperty().bind(this.widthProperty()); - scene.heightProperty().bind(this.heightProperty()); - scene.setFill(Color.BLACK); - - scene.setCamera(buildCamera()); - - this.getChildren().add(scene); - } - - /** - * Sets up camera view frustum and binds transformations - * @return perspective camera - */ - private PerspectiveCamera buildCamera() { - PerspectiveCamera camera = new PerspectiveCamera(true); - - // Set up view frustum - nearClip = 0.1; - farClip = 1000.0; - camera.setNearClip(nearClip); - camera.setFarClip(farClip); - - // Set up transformations - pivot = new Translate(); - distance = new Translate(); - yaw = new Rotate(0, Rotate.Y_AXIS); - pitch = new Rotate(0, Rotate.X_AXIS); - camera.getTransforms().addAll(pivot, yaw, pitch, distance); - - return camera; - } - - public void setItems(ObservableList items) { - this.items = items; - this.items.addListener((ListChangeListener) c -> { - while(c.next()) { - if (c.wasRemoved() || c.wasAdded()) { - for (Shape3D shape : c.getRemoved()) world.getChildren().remove(shape); - for (Shape3D shape : c.getAddedSubList()) world.getChildren().add(shape); - } - } - }); - } - - public void setNearClip(double nearClip) { - this.nearClip = nearClip; - } - - public void setFarClip(double farClip) { - this.farClip = farClip; - } - - /** - * Set object to centre on camera - * @param pivot centred object - */ - public void setPivot(Shape3D pivot) { - this.pivot.setX(pivot.getTranslateX()); - this.pivot.setY(pivot.getTranslateY()); - this.pivot.setZ(pivot.getTranslateZ()); - } - - /** - * Set distance of camera from pivot - * @param distance in units - */ - public void setDistance(double distance) { - this.distance.setZ(-distance); - } - - /** - * Set angle of camera from z-axis along ground - * @param yaw in degrees - */ - public void setYaw(double yaw) { - this.yaw.setAngle(yaw); - } - - /** - * Set elevation of camera - * @param pitch in degrees - */ - public void setPitch(double pitch) { - this.pitch.setAngle(-pitch); - } -} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java index 6cbfdaa3..12e2ee37 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java @@ -1,11 +1,10 @@ package visualiser.model; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.scene.paint.Color; import network.Messages.Enums.BoatStatusEnum; -import shared.model.Azimuth; -import shared.model.Boat; -import shared.model.Constants; -import shared.model.GPSCoordinate; +import shared.model.*; import java.time.Duration; import java.time.ZonedDateTime; @@ -18,7 +17,7 @@ import java.util.List; * This class is used to represent and store information about a boat which may * travel around in a race. It is displayed on the * {@link ResizableRaceCanvas ResizableRaceCanvas} via the - * {@link visualiser.Controllers.RaceController RaceController}. + * {@link visualiser.Controllers.RaceViewController RaceViewController}. */ public class VisualiserBoat extends Boat { @@ -61,7 +60,8 @@ public class VisualiserBoat extends Boat { private boolean isClientBoat = false; - + private ObjectProperty positionProperty; + private ObjectProperty bearingProperty; /** @@ -239,4 +239,38 @@ public class VisualiserBoat extends Boat { public void setClientBoat(boolean clientBoat) { isClientBoat = clientBoat; } + + @Override + public GPSCoordinate getPosition() { + return positionProperty.get(); + } + + @Override + public void setPosition(GPSCoordinate position) { + if(this.positionProperty == null) { + this.positionProperty = new SimpleObjectProperty<>(); + } + this.positionProperty.set(position); + } + + public ObjectProperty positionProperty() { + return positionProperty; + } + + @Override + public Bearing getBearing() { + return bearingProperty.get(); + } + + @Override + public void setBearing(Bearing bearing) { + if(this.bearingProperty == null) { + this.bearingProperty = new SimpleObjectProperty<>(); + } + this.bearingProperty.set(bearing); + } + + public ObjectProperty bearingProperty() { + return bearingProperty; + } } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java index d5ab9b66..467499be 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java @@ -66,7 +66,7 @@ public class VisualiserRaceEvent { this.serverConnection = new ServerConnection(socket, visualiserRaceState, raceCommands, requestType); - this.serverConnectionThread = new Thread(serverConnection, "StartController.enterLobby()->serverConnection thread " + serverConnection); + this.serverConnectionThread = new Thread(serverConnection, "RaceStartController.enterLobby()->serverConnection thread " + serverConnection); this.serverConnectionThread.start(); diff --git a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java index 5d6d8773..70cc7631 100644 --- a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java +++ b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java @@ -7,14 +7,12 @@ import network.MessageRouters.MessageRouter; import network.Messages.AC35Data; import network.Messages.Enums.MessageType; import network.Messages.Enums.RequestToJoinEnum; -import network.Messages.LatestMessages; import network.StreamRelated.MessageDeserialiser; import network.StreamRelated.MessageSerialiser; import shared.model.RunnableWithFramePeriod; -import visualiser.model.VisualiserRaceEvent; -import visualiser.model.VisualiserRaceController; import visualiser.enums.ConnectionToServerState; import visualiser.gameController.ControllerClient; +import visualiser.model.VisualiserRaceController; import visualiser.model.VisualiserRaceState; import java.io.IOException; @@ -310,7 +308,7 @@ public class ServerConnection implements RunnableWithFramePeriod { } - //TODO create input controller here. RaceController should query for it, if it exists. + //TODO create input controller here. RaceViewController should query for it, if it exists. private void createPlayerInputController() { this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend()); diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java new file mode 100644 index 00000000..22dd937f --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java @@ -0,0 +1,104 @@ +package visualiser.utils; + +import shared.dataInput.RaceDataSource; +import shared.model.GPSCoordinate; +import visualiser.model.GraphCoordinate; + +/** + * Converts GPS coordinates to view volume coordinates. Longitudes are equally spaced at all latitudes, + * which leads to inaccurate distance measurements close to the poles. This is acceptable as races are + * not likely to be set there. + */ +public class GPSConverter { + private double longRight; + private double longLeft; + private double latBottom; + private double latTop; + /** + * Conversion factor from longitude to view units + */ + private double longitudeFactor; + /** + * Conversion factor from latitude to view units + */ + private double latitudeFactor; + + /** + * Set up projection with default view boundaries from RaceDataSource + * @param source for view boundaries + * @param longitudeFactor separation of a degree of longitude in view units + * @param latitudeFactor separation of a degree of latitude in view units + */ + public GPSConverter(RaceDataSource source, double longitudeFactor, double latitudeFactor) { + this.latTop = source.getMapTopLeft().getLatitude(); + this.longLeft = source.getMapTopLeft().getLongitude(); + this.latBottom = source.getMapBottomRight().getLatitude(); + this.longRight = source.getMapBottomRight().getLongitude(); + this.longitudeFactor = longitudeFactor; + this.latitudeFactor = latitudeFactor; + } + + /** + * Converts GPS coordinates to coordinates for container. + * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap. + * + * @param lat GPS latitude + * @param lon GPS longitude + * @return GraphCoordinate (pair of doubles) + * @see GraphCoordinate + */ + private GraphCoordinate convertGPS(double lat, double lon) { + + //Calculate the width/height, in gps coordinates, of the map. + double longWidth = longRight - longLeft; + double latHeight = latBottom - latTop; + + //Calculate the distance between the specified coordinate and the edge of the map. + double longDelta = lon - longLeft; + double latDelta = lat - latTop; + + //Calculate the proportion along horizontally, from the left, the coordinate should be. + double longProportion = longDelta / longWidth; + //Calculate the proportion along vertically, from the top, the coordinate should be. + double latProportion = latDelta / latHeight; + + //Check which metric dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen. + double smallerDimension = Math.min(longitudeFactor, latitudeFactor); + + //Calculate the x and y pixel coordinates. + //We take the complement of latProportion to flip it. + int x = (int) (longProportion * smallerDimension); + int y = (int) (latProportion * smallerDimension); + + //Because we try to maintain the correct aspect ratio, we will end up with "spare" pixels along the larger dimension (e.g., width 800, height 600, 200 extra pixels along width). + double extraDistance = Math.abs(longitudeFactor - latitudeFactor); + //We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels. + if (longitudeFactor > latitudeFactor) { + x += extraDistance / 2; + } else { + y += extraDistance / 2; + } + + + //Finally, create the GraphCoordinate. + GraphCoordinate graphCoordinate = new GraphCoordinate(x, y); + + + return graphCoordinate; + + } + + /** + * Converts the GPS Coordinate to GraphCoordinate. + * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap. + * + * @param coordinate GPSCoordinate representation of Latitude and Longitude. + * @return GraphCoordinate that the GPS is coordinates are to be displayed on the map. + * @see GraphCoordinate + * @see GPSCoordinate + */ + public GraphCoordinate convertGPS(GPSCoordinate coordinate) { + return convertGPS(coordinate.getLatitude(), coordinate.getLongitude()); + } + +} diff --git a/racevisionGame/src/main/resources/css/dayMode.css b/racevisionGame/src/main/resources/css/dayMode.css index aa14c68b..b62f8391 100644 --- a/racevisionGame/src/main/resources/css/dayMode.css +++ b/racevisionGame/src/main/resources/css/dayMode.css @@ -53,5 +53,39 @@ } #arrowImage { - -fx-image: url("/visualiser/images/arrow.png"); + -fx-graphic: url("/visualiser/images/arrow.png"); +} + +#nextButton { + -fx-background-image: url("/visualiser/images/ArrowRoundRight.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#nextButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} + +#previousButton { + -fx-background-image: url("/visualiser/images/ArrowRoundLeft.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#previousButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; } diff --git a/racevisionGame/src/main/resources/css/nightMode.css b/racevisionGame/src/main/resources/css/nightMode.css index 7fe6a67b..deefa51a 100644 --- a/racevisionGame/src/main/resources/css/nightMode.css +++ b/racevisionGame/src/main/resources/css/nightMode.css @@ -57,3 +57,37 @@ #arrowImage { -fx-image: url("/visualiser/images/arrowLight.png"); } + +#nextButton { + -fx-background-image: url("/visualiser/images/ArrowRoundRight.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#nextButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} + +#previousButton { + -fx-background-image: url("/visualiser/images/ArrowRoundLeft.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#previousButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} diff --git a/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png b/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png new file mode 100644 index 00000000..1b5e39ca Binary files /dev/null and b/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png differ diff --git a/racevisionGame/src/main/resources/images/iMapLayout.png b/racevisionGame/src/main/resources/images/iMapLayout.png new file mode 100644 index 00000000..5932a60d Binary files /dev/null and b/racevisionGame/src/main/resources/images/iMapLayout.png differ diff --git a/racevisionGame/src/main/resources/images/mMapLayout.png b/racevisionGame/src/main/resources/images/mMapLayout.png new file mode 100644 index 00000000..fa0af51f Binary files /dev/null and b/racevisionGame/src/main/resources/images/mMapLayout.png differ diff --git a/racevisionGame/src/main/resources/images/oMapLayout.png b/racevisionGame/src/main/resources/images/oMapLayout.png new file mode 100644 index 00000000..ca520959 Binary files /dev/null and b/racevisionGame/src/main/resources/images/oMapLayout.png differ diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatTutorial.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTutorial.xml new file mode 100644 index 00000000..023c5090 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/boatTutorial.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml new file mode 100644 index 00000000..ca914b12 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml @@ -0,0 +1,53 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml new file mode 100644 index 00000000..ce715ba6 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml @@ -0,0 +1,43 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml new file mode 100644 index 00000000..5021bbba --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml @@ -0,0 +1,49 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml index e9e9378a..c4efcd17 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml @@ -5,7 +5,6 @@ RACE_CREATION_TIME - diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml similarity index 100% rename from racevisionGame/src/main/resources/mock/mockXML/raceTest.xml rename to racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml b/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml deleted file mode 100644 index e0b81837..00000000 --- a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 5326 - FLEET - RACE_CREATION_TIME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTutorial.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTutorial.xml new file mode 100644 index 00000000..47519d00 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/raceTutorial.xml @@ -0,0 +1,43 @@ + + + 9999 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/mock/mockXML/regattaTutorial.xml b/racevisionGame/src/main/resources/mock/mockXML/regattaTutorial.xml new file mode 100644 index 00000000..d7f6b9e7 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/regattaTutorial.xml @@ -0,0 +1,10 @@ + + 0 + Race Tutorial + Tutorial + -36.82791529 + 174.81218919 + 0.00 + 12 + 14.1 + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png new file mode 100644 index 00000000..ea4f4d64 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png new file mode 100644 index 00000000..794ba57c Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png new file mode 100644 index 00000000..751698cb Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png new file mode 100644 index 00000000..10c1b53b Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/sunsleep.png b/racevisionGame/src/main/resources/visualiser/images/sunsleep.png new file mode 100644 index 00000000..e52b2674 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/sunsleep.png differ diff --git a/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTutorial.xml b/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTutorial.xml new file mode 100644 index 00000000..db7a6978 --- /dev/null +++ b/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTutorial.xml @@ -0,0 +1,91 @@ + + + + + 99999999 + + Match + + 2011-08-06T13:25:00-0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml b/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml deleted file mode 100644 index 4308a47f..00000000 --- a/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml b/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml index 91ec81ef..b9314691 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml @@ -1,10 +1,5 @@ - - - - - @@ -17,7 +12,7 @@ - + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/main.fxml b/racevisionGame/src/main/resources/visualiser/scenes/main.fxml deleted file mode 100644 index 7aff44c0..00000000 --- a/racevisionGame/src/main/resources/visualiser/scenes/main.fxml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml deleted file mode 100644 index bbc1c077..00000000 --- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml b/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml deleted file mode 100644 index 007ef599..00000000 --- a/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java deleted file mode 100644 index 35e21a0e..00000000 --- a/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java +++ /dev/null @@ -1,47 +0,0 @@ -//package seng302.Mock; -// -//import org.junit.Before; -//import org.junit.Test; -// -//import java.util.HashMap; -//import java.util.Map; -// -//import static org.junit.Assert.assertEquals; -// -///** -// * Created by jjg64 on 21/04/17. -// */ -//public class BoatsXMLTest { -// private BoatXMLReader boatXMLReader; -// -// @Before -// public void setup() { -// try { -// boatXMLReader = new BoatXMLReader("mockXML/boatXML/boatTest.xml", false); -// } catch (Exception e) { -// e.printStackTrace(); -// //fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder"); -// } -// } -// -// @Test -// public void testInvalidParticipant() { -// Map inputParticipants = new HashMap<>(); -// inputParticipants.put(420, new StreamedBoat(420)); -// boatXMLReader.setParticipants(inputParticipants); -// boatXMLReader.read(); -// assertEquals(boatXMLReader.getBoats().size(), 0); -// } -// -// @Test -// public void testValidParticipant() { -// Map inputParticipants = new HashMap<>(); -// inputParticipants.put(101, new StreamedBoat(101)); -// boatXMLReader.setParticipants(inputParticipants); -// boatXMLReader.read(); -// assertEquals(boatXMLReader.getBoats().size(), 1); -// StreamedBoat boat = (StreamedBoat) boatXMLReader.getBoats().get(0); -// assertEquals(boat.getSourceID(), 101); -// } -// -//} diff --git a/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java deleted file mode 100644 index a299c2bf..00000000 --- a/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java +++ /dev/null @@ -1,26 +0,0 @@ -//package seng302.Mock; -// -//import org.junit.Test; -//import org.xml.sax.SAXException; -// -//import javax.xml.parsers.ParserConfigurationException; -//import java.io.IOException; -//import java.text.ParseException; -// -///** -// * Created by jjg64 on 1/05/17. -// */ -//public class FailBoatXMLTest { -// private final String path = "mockXML/boatXML/"; -// -// @Test(expected = NumberFormatException.class) -// public void invalidSourceID() throws SAXException, ParserConfigurationException, ParseException, IOException { -// new BoatXMLReader(path + "invalidSourceID.xml"); -// } -// -// @Test(expected = NullPointerException.class) -// public void insufficientInformation() throws SAXException, ParserConfigurationException, ParseException, IOException { -// new BoatXMLReader(path + "insufficientInformation.xml"); -// } -// -//} diff --git a/racevisionGame/src/test/java/shared/model/RaceTest.java b/racevisionGame/src/test/java/shared/model/RaceTest.java deleted file mode 100644 index 3334aefd..00000000 --- a/racevisionGame/src/test/java/shared/model/RaceTest.java +++ /dev/null @@ -1,345 +0,0 @@ -//package shared.model; -// -// -//import mock.model.Polars; -//import org.junit.Before; -//import org.junit.Ignore; -//import org.junit.Test; -//import org.mockito.Mockito; -//import org.xml.sax.SAXException; -// -//import javax.xml.parsers.ParserConfigurationException; -//import java.io.IOException; -//import java.text.ParseException; -//import java.util.ArrayList; -// -//import static org.junit.Assert.*; -//import static org.mockito.Mockito.*; -// -///** -// * Created by esa46 on 15/03/17. -// */ -//public class RaceTest{ -// -// -// private CompoundMark ORIGIN; -// -// private CompoundMark THREE_NM_FROM_ORIGIN; -// -// private CompoundMark FIFTEEN_NM_FROM_ORIGIN; -// -// private ArrayList TEST_LEGS = new ArrayList<>(); -// -// private int START_LEG_DISTANCE = 3; -// private int MIDDLE_LEG_DISTANCE = 12; -// -// private Leg START_LEG; -// -// private Leg MIDDLE_LEG; -// -// private Leg FINISH_LEG; -// -// -// @Before -// public void setUp() { -// -// ORIGIN = new CompoundMark( -// 1, -// "origin compound", -// new Mark(1, "test origin 1", new GPSCoordinate(0, 0)) ); -// -// -// THREE_NM_FROM_ORIGIN = new CompoundMark( -// 2, -// "3 NM from origin compound", -// new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0)) ); -// -// -// FIFTEEN_NM_FROM_ORIGIN = new CompoundMark( -// 3, -// "15 NM from origin compound", -// new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0)) ); -// -// -// START_LEG = new Leg("Start", ORIGIN, THREE_NM_FROM_ORIGIN, 0); -// -// -// MIDDLE_LEG = new Leg("Middle", THREE_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 1); -// -// -// FINISH_LEG = new Leg("Finish", FIFTEEN_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 2); -// -// -// TEST_LEGS.add(START_LEG); -// TEST_LEGS.add(MIDDLE_LEG); -// TEST_LEGS.add(FINISH_LEG); -// } -// -// @Ignore -// @Test -// public void countdownTimerSendsBoatLocations() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// testRace.initialiseBoats(); -// testRace.countdownTimer.handle(1); -// verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong()); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void countdownTimerSendsRaceStatusMessages() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); -// Race testRace = new Race(dataSource, mockOutput); -// testRace.initialiseBoats(); -// testRace.countdownTimer.handle(1); -// verify(mockOutput, atLeast(1)).parseRaceStatus(any()); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void checkPositionFinishedUpdatesNumberFinishedBoats() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); -// Race testRace = new Race(dataSource, mockOutput); -// testRace.initialiseBoats(); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(FINISH_LEG); -// testBoat.setDistanceTravelledInLeg(1); -// testRace.checkPosition(testBoat, 1); -// -// assertEquals(testRace.getNumberOfActiveBoats(), 0); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void checkPositionSetFinishedBoatVelocityTo0() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); -// Race testRace = new Race(dataSource, mockOutput); -// testRace.initialiseBoats(); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(FINISH_LEG); -// testBoat.setDistanceTravelledInLeg(1); -// testRace.checkPosition(testBoat, 1); -// -// assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void checkPositionSetsFinishTime() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); -// Race testRace = new Race(dataSource, mockOutput); -// testRace.initialiseBoats(); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(FINISH_LEG); -// testBoat.setDistanceTravelledInLeg(1); -// testRace.checkPosition(testBoat, 1); -// -// assertEquals(testBoat.getTimeFinished(), 1, 1e-8); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void checkPositionUnfinishedDoesntUpdateNumberFinishedBoats() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); -// Race testRace = new Race(dataSource, mockOutput); -// testRace.initialiseBoats(); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(START_LEG); -// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE); -// testRace.checkPosition(testBoat, 1); -// -// assertEquals(testRace.getNumberOfActiveBoats(), 1); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// -// @Ignore -// @Test -// public void distanceTravelledBeforeUpdatingLegIsRetained() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); -// Race testRace = new Race(dataSource, mockOutput); -// testRace.initialiseBoats(); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(START_LEG); -// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); -// testRace.checkPosition(testBoat, 0); -// -// assertEquals(testBoat.getDistanceTravelledInLeg(), 1, 1e-7); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void doNotFinishAnswersYesIf100PercentChance() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// -// testRace.setDnfChance(100); -// assertTrue(testRace.doNotFinish()); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void doNotFinishAnswersNoIf0PercentChance() { -// -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// testRace.setDnfChance(0); -// assertFalse(testRace.doNotFinish()); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void boatsAreSetToDNF() { -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// testRace.setDnfChance(100); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(START_LEG); -// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); -// testRace.checkPosition(testBoat, 1); -// assertEquals(testBoat.getCurrentLeg().getName(), "DNF"); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// -// } -// -// @Ignore -// @Test -// public void updatePositionIgnoresFinishedBoats() { -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(FINISH_LEG); -// testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); -// testRace.updatePosition(testBoat, 1, 1); -// assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate()); -// -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void updatePositionChangesBoatPosition() { -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// testRace.initialiseBoats(); -// Boat testBoat = testRace.getBoats().get(0); -// testBoat.setCurrentLeg(START_LEG); -// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1); -// testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); -// testRace.updatePosition(testBoat, 100, 100); -// assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate()); -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } -// } -// -// @Ignore -// @Test -// public void windDirectionCorrectValues(){ -//// try { -//// MockOutput mockOutput = Mockito.mock(MockOutput.class); -//// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml"); -//// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -//// Race testRace = new Race(raceDataSource, mockOutput); -//// testRace.setChangeWind(1); -//// testRace.setWindDir(65535); -//// testRace.changeWindDir(); -//// assertEquals(100, testRace.getWind()); -//// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -//// e.printStackTrace(); -//// fail(); -//// } -// } -// -// -//} diff --git a/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java b/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java deleted file mode 100644 index ef5e691a..00000000 --- a/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java +++ /dev/null @@ -1,100 +0,0 @@ -//package visualiser.model; -// -// -//import org.junit.Before; -//import org.junit.Test; -//import shared.dataInput.RaceXMLReader; -//import shared.dataInput.XMLReader; -//import shared.exceptions.InvalidRaceDataException; -//import shared.model.GPSCoordinate; -// -//import java.nio.charset.StandardCharsets; -//import java.util.List; -// -//import static org.junit.Assert.assertEquals; -//import static org.junit.Assert.fail; -// -///** -// * Tests only work on the current version of mockXML/raceXML/raceTest.xml -// */ -//public class VisualiserRaceTest { -// private RaceXMLReader streamedCourseXMLReader; -// private List boundary; -// -// @Before -// public void setup() { -// try { -// streamedCourseXMLReader = new RaceXMLReader(XMLReader.readXMLFileToString("mockXML/raceXML/raceTest.xml", StandardCharsets.UTF_8)); -// boundary = streamedCourseXMLReader.getBoundary(); -// } catch (InvalidRaceDataException e) { -// e.printStackTrace(); -// fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder"); -// } -// } -// -// @Test -// public void testAllBoundaryPointsRead() { -// assertEquals(boundary.size(), 10); -// } -// -// @Test -// public void testBoundaryPointData() { -// // First point -// assertEquals(boundary.get(0).getLatitude(), -36.8325, 1e-6); -// assertEquals(boundary.get(0).getLongitude(), 174.8325, 1e-6); -// -// // Last point -// assertEquals(boundary.get(boundary.size() - 1).getLatitude(), -36.83417, 1e-6); -// assertEquals(boundary.get(boundary.size() - 1).getLongitude(), 174.84767, 1e-6); -// } -// -// @Test -// public void testMapEdges() { -// double maxLatitude = streamedCourseXMLReader.getMapBottomRight().getLatitude() - streamedCourseXMLReader.getPadding(); -// double maxLongitude = streamedCourseXMLReader.getMapBottomRight().getLongitude() - streamedCourseXMLReader.getPadding(); -// double minLatitude = streamedCourseXMLReader.getMapTopLeft().getLatitude() - streamedCourseXMLReader.getPadding(); -// double minLongitude = streamedCourseXMLReader.getMapTopLeft().getLongitude() - streamedCourseXMLReader.getPadding(); -// -// assertEquals(maxLatitude, -36.81033, 1e-6); -// assertEquals(maxLongitude, 174.88217, 1e-6); -// assertEquals(minLatitude, -36.83417, 1e-6); -// assertEquals(minLongitude, 174.81983, 1e-6); -// } -// -// @Test -// public void testRaceSettings() { -// -// } -// -// @Test -// public void correctLegSequence() { -// List legs = streamedCourseXMLReader.getLegs(); -// String[] expectedNames = { -// "StartLine", -// "M1", -// "M2", -// "Gate" -// }; -// for(int i = 0; i < legs.size(); i++) { -// assertEquals(expectedNames[i], legs.get(i).getName()); -// } -// } -// -// /** -// * raceTest.xml is not compliant with this test. Markers are positioned far out of bounds. -// */ -// @Test -// @Ignore -// public void markersWithinRaceBoundaries() { -// GPSCoordinate topLeft = streamedCourseXMLReader.getMapTopLeft(); -// GPSCoordinate bottomRight = streamedCourseXMLReader.getMapBottomRight(); -// -// for(Marker compoundMark : streamedCourseXMLReader.getMarkers()) { -// GPSCoordinate centre = compoundMark.getAverageGPSCoordinate(); -// assertTrue(centre.getLatitude() < bottomRight.getLatitude()); -// assertTrue(centre.getLatitude() > topLeft.getLatitude()); -// assertTrue(centre.getLongitude() > bottomRight.getLongitude()); -// assertTrue(centre.getLongitude() < topLeft.getLongitude()); -// } -// } -//} diff --git a/settings/keyBindings.xml b/settings/keyBindings.xml deleted file mode 100644 index 2b807e17..00000000 --- a/settings/keyBindings.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - SPACE - - - - SHIFT - - - - DOWN - - - - X - - - - ENTER - - - - Z - - - - UP - - - -