Initial commit
This commit is contained in:
30
src/main/java/uk/mgrove/ac/soton/comp1206/App.java
Normal file
30
src/main/java/uk/mgrove/ac/soton/comp1206/App.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package uk.ac.soton.comp1206;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
||||
/**
|
||||
* JavaFX App
|
||||
*/
|
||||
public class App extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
var javaVersion = SystemInfo.javaVersion();
|
||||
var javafxVersion = SystemInfo.javafxVersion();
|
||||
|
||||
var label = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
|
||||
var scene = new Scene(new StackPane(label), 640, 480);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
}
|
||||
|
||||
}
|
||||
13
src/main/java/uk/mgrove/ac/soton/comp1206/SystemInfo.java
Normal file
13
src/main/java/uk/mgrove/ac/soton/comp1206/SystemInfo.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package uk.ac.soton.comp1206;
|
||||
|
||||
public class SystemInfo {
|
||||
|
||||
public static String javaVersion() {
|
||||
return System.getProperty("java.version");
|
||||
}
|
||||
|
||||
public static String javafxVersion() {
|
||||
return System.getProperty("javafx.version");
|
||||
}
|
||||
|
||||
}
|
||||
252
src/main/java/uk/mgrove/ac/soton/comp1206/ui/BaseWindow.java
Normal file
252
src/main/java/uk/mgrove/ac/soton/comp1206/ui/BaseWindow.java
Normal file
@@ -0,0 +1,252 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.*;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base window class, providing template from which all windows should extend
|
||||
*/
|
||||
public abstract class BaseWindow extends Window {
|
||||
protected static final Logger logger = LogManager.getLogger(ChatWindow.class);
|
||||
|
||||
protected final App app;
|
||||
protected final Communicator communicator;
|
||||
|
||||
/**
|
||||
* Duration over which menus should slide open/closed
|
||||
*/
|
||||
protected final static Duration menuTransitionDuration = Duration.millis(300);
|
||||
/**
|
||||
* Duration over which buttons should rotate - used for menu open/close buttons
|
||||
*/
|
||||
protected final static Duration buttonRotationDuration = Duration.millis(150);
|
||||
/**
|
||||
* Duration over which windows should fade in
|
||||
*/
|
||||
protected final static Duration windowFadeInDuration = Duration.millis(500);
|
||||
|
||||
@FXML
|
||||
protected VBox rightPanel;
|
||||
@FXML
|
||||
protected VBox leftPanel;
|
||||
@FXML
|
||||
protected SplitPane mainSplitPane;
|
||||
@FXML
|
||||
protected ImageView showLeftPanel;
|
||||
@FXML
|
||||
protected ImageView showRightPanel;
|
||||
@FXML
|
||||
protected ImageView hideLeftPanel;
|
||||
@FXML
|
||||
protected ImageView hideRightPanel;
|
||||
@FXML
|
||||
protected StackPane mainStackPane;
|
||||
|
||||
protected Parent root;
|
||||
protected Node rightPanelPane;
|
||||
protected Node leftPanelPane;
|
||||
protected double[] mainSplitPaneDividerPositions;
|
||||
protected boolean[] sidePanelsVisible;
|
||||
|
||||
/**
|
||||
* Initialise the window with an FXML template
|
||||
* @param app app to use
|
||||
* @param communicator communicator to use
|
||||
* @param fxmlPath path to FXML file
|
||||
*/
|
||||
public BaseWindow(App app, Communicator communicator, String fxmlPath) {
|
||||
|
||||
this.app = app;
|
||||
this.communicator = communicator;
|
||||
|
||||
//Load the Chat Window GUI
|
||||
try {
|
||||
//Instead of building this GUI programmatically, we are going to use FXML
|
||||
var loader = new FXMLLoader(getClass().getResource(fxmlPath));
|
||||
|
||||
//Link the GUI in the FXML to this class
|
||||
loader.setController(this);
|
||||
root = loader.load();
|
||||
} catch (Exception e) {
|
||||
//Handle any exceptions with loading the FXML
|
||||
logger.error("Unable to read file: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
root.setOpacity(0);
|
||||
fadeInWindow();
|
||||
|
||||
//We are the login window
|
||||
setScene(new Scene(root));
|
||||
|
||||
rightPanelPane = mainSplitPane.getItems().get(2);
|
||||
leftPanelPane = mainSplitPane.getItems().get(0);
|
||||
mainSplitPaneDividerPositions = mainSplitPane.getDividerPositions();
|
||||
sidePanelsVisible = new boolean[2];
|
||||
Arrays.fill(sidePanelsVisible, Boolean.TRUE);
|
||||
mainStackPane.getChildren().removeAll(showLeftPanel, showRightPanel);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade the root node from invisible to visible
|
||||
*/
|
||||
protected void fadeInWindow() {
|
||||
FadeTransition ft = new FadeTransition(windowFadeInDuration, root);
|
||||
ft.setFromValue(0);
|
||||
ft.setToValue(1);
|
||||
ft.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a given pane in a ScrollPane - used to hide menus
|
||||
*
|
||||
* @param panelPane the pane to hide
|
||||
* @param dividerPositionProperty the property for the appropriate divider's position
|
||||
* @param hideButton button to hide the pane
|
||||
* @param showButton button to show the pane
|
||||
* @param panelIndex index of the panel in local info stores (e.g. list of open/closed menus)
|
||||
*/
|
||||
protected void hidePanel(Pane panelPane, DoubleProperty dividerPositionProperty, Node hideButton, Node showButton, int panelIndex) {
|
||||
panelPane.setMinWidth(0);
|
||||
|
||||
int finalDividerPosition = panelIndex == 0 ? 0 : 1;
|
||||
|
||||
KeyValue keyValue = new KeyValue(dividerPositionProperty, finalDividerPosition, Interpolator.EASE_BOTH);
|
||||
Timeline hidePanel = new Timeline(new KeyFrame(menuTransitionDuration, keyValue));
|
||||
|
||||
RotateTransition rotateButton = new RotateTransition(buttonRotationDuration, hideButton);
|
||||
rotateButton.setFromAngle(0);
|
||||
rotateButton.setToAngle(panelIndex == 0 ? 180 : -180);
|
||||
rotateButton.setOnFinished((event2) -> {
|
||||
mainStackPane.getChildren().add(showButton);
|
||||
mainStackPane.getChildren().remove(hideButton);
|
||||
hideButton.setRotate(0);
|
||||
});
|
||||
|
||||
hidePanel.play();
|
||||
|
||||
hidePanel.setOnFinished((event) -> {
|
||||
rotateButton.play();
|
||||
sidePanelsVisible[panelIndex] = false;
|
||||
mainSplitPane.getItems().remove(panelPane);
|
||||
if (panelIndex == 0 && sidePanelsVisible[1])
|
||||
mainSplitPane.setDividerPosition(0, mainSplitPaneDividerPositions[1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a given pane in a ScrollPane - used to show menus
|
||||
*
|
||||
* @param panelPane the pane to show
|
||||
* @param hideButton button to hide the pane
|
||||
* @param showButton button to show the pane
|
||||
* @param panelIndex index of the panel in local info stores (e.g. list of open/closed menus)
|
||||
*/
|
||||
protected void showPanel(Pane panelPane, Node hideButton, Node showButton, int panelIndex) {
|
||||
if (panelIndex == 0) mainSplitPane.getItems().add(0, panelPane);
|
||||
else mainSplitPane.getItems().add(panelPane);
|
||||
|
||||
DoubleProperty dividerPositionProperty = mainSplitPane.getDividers().get(panelIndex == 1 && !sidePanelsVisible[0] ? 0 : panelIndex).positionProperty();
|
||||
|
||||
int initialDividerPosition = panelIndex == 0 ? 0 : 1;
|
||||
|
||||
mainSplitPane.setDividerPosition(panelIndex == 1 && !sidePanelsVisible[0] ? 0 : panelIndex, initialDividerPosition);
|
||||
KeyValue keyValue = new KeyValue(dividerPositionProperty, mainSplitPaneDividerPositions[panelIndex], Interpolator.EASE_BOTH);
|
||||
Timeline showPanel = new Timeline(new KeyFrame(menuTransitionDuration, keyValue));
|
||||
|
||||
RotateTransition rotateButton = new RotateTransition(buttonRotationDuration, showButton);
|
||||
rotateButton.setFromAngle(0);
|
||||
rotateButton.setToAngle(panelIndex == 0 ? -180 : 180);
|
||||
rotateButton.setOnFinished((event2) -> {
|
||||
mainStackPane.getChildren().add(hideButton);
|
||||
mainStackPane.getChildren().remove(showButton);
|
||||
showButton.setRotate(0);
|
||||
});
|
||||
|
||||
showPanel.play();
|
||||
|
||||
showPanel.setOnFinished((event1) -> {
|
||||
rotateButton.play();
|
||||
sidePanelsVisible[panelIndex] = true;
|
||||
panelPane.setMinWidth(Region.USE_COMPUTED_SIZE);
|
||||
if (panelIndex == 0 && sidePanelsVisible[1])
|
||||
mainSplitPane.setDividerPosition(1, mainSplitPaneDividerPositions[1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the right-hand sidebar
|
||||
*/
|
||||
protected void toggleRightPanel() {
|
||||
if (sidePanelsVisible[1]) {
|
||||
DoubleProperty positionProperty;
|
||||
if (sidePanelsVisible[0]) {
|
||||
mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[1];
|
||||
positionProperty = mainSplitPane.getDividers().get(1).positionProperty();
|
||||
} else {
|
||||
mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[0];
|
||||
positionProperty = mainSplitPane.getDividers().get(0).positionProperty();
|
||||
}
|
||||
|
||||
hidePanel(rightPanel, positionProperty, hideRightPanel, showRightPanel, 1);
|
||||
} else {
|
||||
showPanel(rightPanel, hideRightPanel, showRightPanel, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the right-hand sidebar
|
||||
*/
|
||||
protected void toggleLeftPanel() {
|
||||
if (sidePanelsVisible[0]) {
|
||||
mainSplitPaneDividerPositions[0] = mainSplitPane.getDividerPositions()[0];
|
||||
if (sidePanelsVisible[1]) mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[1];
|
||||
|
||||
hidePanel(leftPanel, mainSplitPane.getDividers().get(0).positionProperty(), hideLeftPanel, showLeftPanel, 0);
|
||||
} else {
|
||||
if (sidePanelsVisible[1]) mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[0];
|
||||
showPanel(leftPanel, hideLeftPanel, showLeftPanel, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse click to toggle visibility of right-hand sidebar
|
||||
*
|
||||
* @param event mouse clicked
|
||||
*/
|
||||
public void toggleRightPanel(MouseEvent event) {
|
||||
toggleRightPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse click to toggle visibility of right-hand sidebar
|
||||
*
|
||||
* @param event mouse clicked
|
||||
*/
|
||||
public void toggleLeftPanel(MouseEvent event) {
|
||||
toggleLeftPanel();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
import uk.mgrove.ac.soton.comp1206.utility.Utility;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Chat window for displaying chat interface
|
||||
*/
|
||||
public class ChatWindow extends BaseWindow {
|
||||
|
||||
private final static Duration messageFadeInDuration = Duration.millis(150);
|
||||
|
||||
@FXML
|
||||
private TextField usersSearchField;
|
||||
@FXML
|
||||
private Text myUsernameText;
|
||||
@FXML
|
||||
private TextField myUsernameInput;
|
||||
@FXML
|
||||
private ScrollPane messagesContainer;
|
||||
@FXML
|
||||
private Label noMessagesYetText;
|
||||
@FXML
|
||||
private VBox messages;
|
||||
@FXML
|
||||
private TextField messageToSend;
|
||||
@FXML
|
||||
private CheckBox audioEnabled;
|
||||
@FXML
|
||||
private HBox myUsernameInputContainer;
|
||||
@FXML
|
||||
private HBox myUsernameTextContainer;
|
||||
@FXML
|
||||
private VBox myUsernameContainer;
|
||||
@FXML
|
||||
private final UserList userList;
|
||||
@FXML
|
||||
private VBox rightPanel;
|
||||
@FXML
|
||||
private VBox leftPanel;
|
||||
@FXML
|
||||
private SplitPane mainSplitPane;
|
||||
@FXML
|
||||
private ImageView showLeftPanel;
|
||||
@FXML
|
||||
private ImageView showRightPanel;
|
||||
@FXML
|
||||
private ImageView hideLeftPanel;
|
||||
@FXML
|
||||
private ImageView hideRightPanel;
|
||||
@FXML
|
||||
private StackPane mainStackPane;
|
||||
@FXML
|
||||
private Button openGame;
|
||||
private boolean messagesReceived;
|
||||
private boolean scrollToBottom;
|
||||
private boolean lastMessageFromMe;
|
||||
|
||||
/**
|
||||
* Initialise the chat window
|
||||
* @param app the app
|
||||
* @param communicator the communicator
|
||||
*/
|
||||
public ChatWindow(App app, Communicator communicator) {
|
||||
super(app, communicator, "/chat.fxml");
|
||||
|
||||
//Link the communicator to this window
|
||||
communicator.setWindow(this);
|
||||
|
||||
getScene().addPostLayoutPulseListener(this::scrollToBottom);
|
||||
|
||||
StringProperty userSearchProperty = new SimpleStringProperty("");
|
||||
userList = new UserList(userSearchProperty);
|
||||
Platform.runLater(() -> rightPanel.getChildren().add(userList));
|
||||
usersSearchField.textProperty().bindBidirectional(userSearchProperty);
|
||||
usersSearchField.setOnKeyReleased((event) -> {
|
||||
// refresh list of people
|
||||
userList.updateDisplayedUsers();
|
||||
});
|
||||
hideMyUsernameInput(false);
|
||||
|
||||
rightPanelPane = mainSplitPane.getItems().get(2);
|
||||
leftPanelPane = mainSplitPane.getItems().get(0);
|
||||
mainSplitPaneDividerPositions = mainSplitPane.getDividerPositions();
|
||||
sidePanelsVisible = new boolean[2];
|
||||
Arrays.fill(sidePanelsVisible, Boolean.TRUE);
|
||||
mainStackPane.getChildren().removeAll(showLeftPanel, showRightPanel);
|
||||
|
||||
messagesReceived = false;
|
||||
myUsernameInput.textProperty().bindBidirectional(app.usernameProperty());
|
||||
myUsernameText.textProperty().bind(app.usernameProperty());
|
||||
audioEnabled.selectedProperty().bindBidirectional(Utility.audioEnabledProperty());
|
||||
|
||||
openGame.setOnAction((e) -> {
|
||||
app.openGame();
|
||||
});
|
||||
|
||||
communicator.addListener(this::receiveMessage);
|
||||
|
||||
Platform.runLater(() -> messageToSend.requestFocus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when the user presses enter on the message to send field
|
||||
*
|
||||
* @param event key pressed
|
||||
*/
|
||||
@FXML
|
||||
protected void handleUsernameKeypress(KeyEvent event) {
|
||||
if (event.getCode() != KeyCode.ENTER) return;
|
||||
// hide input
|
||||
hideMyUsernameInput(true);
|
||||
messageToSend.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when the user presses enter on the message to send field
|
||||
*
|
||||
* @param event key pressed
|
||||
*/
|
||||
@FXML
|
||||
protected void handleMessageToSendKeypress(KeyEvent event) {
|
||||
if (event.getCode() != KeyCode.ENTER) return;
|
||||
sendCurrentMessage(messageToSend.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when the user presses the send message button
|
||||
*
|
||||
* @param event button clicked
|
||||
*/
|
||||
@FXML
|
||||
protected void handleSendMessage(ActionEvent event) {
|
||||
String message = messageToSend.getText();
|
||||
if (message.isBlank()) return;
|
||||
sendCurrentMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle event to show username input when button pressed
|
||||
*
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void showMyUsernameInput(MouseEvent event) {
|
||||
showMyUsernameInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle show username input
|
||||
*/
|
||||
public void showMyUsernameInput() {
|
||||
myUsernameContainer.getChildren().remove(myUsernameTextContainer);
|
||||
myUsernameContainer.getChildren().add(myUsernameInputContainer);
|
||||
myUsernameInput.requestFocus();
|
||||
myUsernameInput.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle event to hide username input when button pressed
|
||||
*
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void hideMyUsernameInput(MouseEvent event) {
|
||||
hideMyUsernameInput(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle hide username input, with option to only remove input container and not add text container
|
||||
*
|
||||
* @param showTextContainer add text container to window
|
||||
*/
|
||||
private void hideMyUsernameInput(boolean showTextContainer) {
|
||||
myUsernameContainer.getChildren().remove(myUsernameInputContainer);
|
||||
if (showTextContainer) {
|
||||
myUsernameContainer.getChildren().add(myUsernameTextContainer);
|
||||
messageToSend.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming message from the Communicator
|
||||
*
|
||||
* @param text The message that has been received, in the form User:Message
|
||||
*/
|
||||
public void receiveMessage(String text) {
|
||||
if (!text.contains(":")) return;
|
||||
if (!messagesReceived) {
|
||||
messages.getChildren().remove(noMessagesYetText);
|
||||
messagesReceived = true;
|
||||
}
|
||||
var components = text.split(":");
|
||||
if (components.length < 2) return;
|
||||
var username = components[0];
|
||||
var message = String.join(":", Arrays.copyOfRange(components, 1, components.length));
|
||||
userList.addUser(username);
|
||||
|
||||
Message receivedMessage = new Message(app, username, message);
|
||||
receivedMessage.setOpacity(0);
|
||||
FadeTransition fadeMessage = new FadeTransition(messageFadeInDuration, receivedMessage);
|
||||
fadeMessage.setToValue(1);
|
||||
messages.getChildren().add(receivedMessage);
|
||||
fadeMessage.play();
|
||||
|
||||
if (messagesContainer.getVvalue() == 1.0f) scrollToBottom = true;
|
||||
|
||||
if (!lastMessageFromMe) Utility.playAudio("incoming.mp3");
|
||||
else lastMessageFromMe = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to bottom of messages list.
|
||||
*/
|
||||
private void scrollToBottom() {
|
||||
if (!scrollToBottom) return;
|
||||
messagesContainer.setVvalue(1.0);
|
||||
scrollToBottom = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an outgoing message from the Chat window.
|
||||
*
|
||||
* @param text The text of the message to send to the Communicator
|
||||
*/
|
||||
private void sendCurrentMessage(String text) {
|
||||
if (!text.isBlank()) {
|
||||
if (text.strip().equals("/game")) app.openGame();
|
||||
else if (text.matches("/nick (.+)$")) app.setUsername(text.substring(6));
|
||||
else {
|
||||
communicator.send(app.getUsername() + ":" + text);
|
||||
lastMessageFromMe = true;
|
||||
}
|
||||
messageToSend.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhide this window
|
||||
*/
|
||||
public void show() {
|
||||
super.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.utility.Utility;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* Class for a message, which displays the message in a formatted way
|
||||
*/
|
||||
public class Message extends TextFlow {
|
||||
|
||||
private final App app;
|
||||
private final String username;
|
||||
private final String message;
|
||||
private final LocalDateTime received;
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
/**
|
||||
* Initialise the message with basic info
|
||||
* @param app the app
|
||||
* @param username username of user who sent the message
|
||||
* @param message the message contents
|
||||
*/
|
||||
public Message(App app, String username, String message) {
|
||||
this.app = app;
|
||||
this.username = username;
|
||||
this.message = message;
|
||||
received = LocalDateTime.now();
|
||||
build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message
|
||||
*/
|
||||
public void build() {
|
||||
Text timeField = new Text("[" + formatter.format(received) + "] ");
|
||||
timeField.getStyleClass().add("timestamp");
|
||||
Text usernameField = new Text(username + ": ");
|
||||
usernameField.getStyleClass().add("username");
|
||||
getChildren().addAll(timeField,usernameField);
|
||||
|
||||
Matcher urlMatcher = Utility.getUrlPattern().matcher(message);
|
||||
var previousMatchEnd = 0;
|
||||
while (urlMatcher.find()) {
|
||||
if (urlMatcher.start() > previousMatchEnd) {
|
||||
getChildren().add(new Text(message.substring(previousMatchEnd, urlMatcher.start())));
|
||||
}
|
||||
var url = message.substring(urlMatcher.start(), urlMatcher.end());
|
||||
var urlField = new Hyperlink(url);
|
||||
urlField.setOnAction((event) -> app.getHostServices().showDocument(url));
|
||||
getChildren().add(urlField);
|
||||
previousMatchEnd = urlMatcher.end();
|
||||
}
|
||||
if (message.length() > previousMatchEnd) {
|
||||
getChildren().add(new Text(message.substring(previousMatchEnd)));
|
||||
}
|
||||
|
||||
getStyleClass().add("message-container");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
/**
|
||||
* Interface for listeners that handle new messages being received by the Communicator class
|
||||
* @see uk.mgrove.ac.soton.comp1206.network.Communicator
|
||||
*/
|
||||
public interface MessageListener {
|
||||
|
||||
/**
|
||||
* Receive a message
|
||||
* @param message the message to receive
|
||||
*/
|
||||
void receiveMessage(String message);
|
||||
|
||||
}
|
||||
58
src/main/java/uk/mgrove/ac/soton/comp1206/ui/chat/User.java
Normal file
58
src/main/java/uk/mgrove/ac/soton/comp1206/ui/chat/User.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* Class for a user, which is an element to be displayed in the chat window
|
||||
*/
|
||||
public class User extends VBox {
|
||||
|
||||
private final String username;
|
||||
private final IntegerProperty messageCount;
|
||||
|
||||
/**
|
||||
* Initialise the user with a username
|
||||
* @param username username of user
|
||||
*/
|
||||
public User(String username) {
|
||||
setSpacing(2);
|
||||
setPadding(new Insets(0, 12,0,12));
|
||||
setFillWidth(true);
|
||||
this.username = username;
|
||||
this.messageCount = new SimpleIntegerProperty(1);
|
||||
var messageCountLabel = new Label("");
|
||||
messageCountLabel.getStyleClass().add("italic");
|
||||
messageCountLabel.setWrapText(true);
|
||||
messageCountLabel.textProperty().bind(Bindings.concat(messageCount.asString()," message(s)"));
|
||||
var usernameLabel = new Label(username);
|
||||
usernameLabel.setWrapText(true);
|
||||
getChildren().addAll(
|
||||
usernameLabel,
|
||||
messageCountLabel
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property for number of messages sent by user
|
||||
* @return property
|
||||
*/
|
||||
public IntegerProperty messageCountProperty() {
|
||||
return messageCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's username
|
||||
* @return username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* List of users to be displayed in chat window
|
||||
*/
|
||||
public class UserList extends ScrollPane {
|
||||
|
||||
private final Map<String,IntegerProperty> userMessageCounts = new HashMap<>();
|
||||
private final VBox container;
|
||||
private final ArrayList<User> userElements = new ArrayList<>();
|
||||
private final StringProperty userSearchProperty;
|
||||
|
||||
/**
|
||||
* Initialise the list of users
|
||||
* @param userSearchProperty property for content of users search field
|
||||
*/
|
||||
public UserList(StringProperty userSearchProperty) {
|
||||
setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
|
||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
getStyleClass().add("user-list");
|
||||
setFitToWidth(true);
|
||||
container = new VBox();
|
||||
container.setSpacing(12);
|
||||
container.setFillWidth(true);
|
||||
setContent(container);
|
||||
this.userSearchProperty = userSearchProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to list of users
|
||||
* @param username username of user to add
|
||||
*/
|
||||
public void addUser(String username) {
|
||||
if (userMessageCounts.containsKey(username)) {
|
||||
var property = userMessageCounts.get(username);
|
||||
property.set(property.get() + 1);
|
||||
} else {
|
||||
var newUser = new User(username);
|
||||
userMessageCounts.put(username, newUser.messageCountProperty());
|
||||
|
||||
newUser.setOpacity(0);
|
||||
FadeTransition fadeMessage = new FadeTransition(Duration.millis(150), newUser);
|
||||
fadeMessage.setToValue(1);
|
||||
userElements.add(newUser);
|
||||
fadeMessage.play();
|
||||
|
||||
sortUserElements();
|
||||
updateDisplayedUsers();
|
||||
}
|
||||
}
|
||||
|
||||
private void sortUserElements() {
|
||||
userElements.sort(Comparator.comparing(User::getUsername));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of users displayed
|
||||
*/
|
||||
public void updateDisplayedUsers() {
|
||||
if (userSearchProperty.get().isBlank()) {
|
||||
container.getChildren().setAll(userElements);
|
||||
} else {
|
||||
container.getChildren().setAll(userElements.stream().filter((user) -> user.getUsername().toLowerCase().contains(userSearchProperty.get().toLowerCase())).toList());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
/**
|
||||
* Interface for listeners that handle a game block being clicked
|
||||
*/
|
||||
public interface BlockClickedListener {
|
||||
|
||||
/**
|
||||
* Handle game block being clicked
|
||||
* @param block block that was clicked
|
||||
*/
|
||||
void blockClicked(GameBlock block);
|
||||
|
||||
}
|
||||
114
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/GameBlock.java
Normal file
114
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/GameBlock.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Class for a game block, which is a single block with a colour that can be changed
|
||||
*/
|
||||
public class GameBlock extends Canvas {
|
||||
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final Color[] colours;
|
||||
private final double width;
|
||||
private final double height;
|
||||
private final IntegerProperty value;
|
||||
private final boolean showStrokes;
|
||||
private final boolean roundedCorners;
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the game block with a set of available colours, a set size,
|
||||
* a set location in the grid, and outlines around the block
|
||||
* @param colours available colours
|
||||
* @param x column index of location in grid
|
||||
* @param y row index of location in grid
|
||||
* @param width block width
|
||||
* @param height block height
|
||||
*/
|
||||
public GameBlock(Color[] colours, int x, int y, double width, double height) {
|
||||
this(colours,x,y,width,height,true,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the game block with a set of available colours, a set size,
|
||||
* a set location in the grid, and optional outlines around the block
|
||||
* @param colours available colours
|
||||
* @param x column index of location in grid
|
||||
* @param y row index of location in grid
|
||||
* @param width block width
|
||||
* @param height block height
|
||||
* @param showStrokes whether block should have an outline
|
||||
* @param roundedCorners whether block should have rounded corners
|
||||
*/
|
||||
public GameBlock(Color[] colours, int x, int y, double width, double height, boolean showStrokes, boolean roundedCorners) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colours = colours;
|
||||
this.showStrokes = showStrokes;
|
||||
this.roundedCorners = roundedCorners;
|
||||
value = new SimpleIntegerProperty();
|
||||
|
||||
setWidth(width);
|
||||
setHeight(height);
|
||||
|
||||
value.addListener(((observableValue, oldValue, newValue) -> paint()));
|
||||
|
||||
paint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint the block - i.e. draw it
|
||||
*/
|
||||
public void paint() {
|
||||
GraphicsContext gc = getGraphicsContext2D();
|
||||
gc.clearRect(0,0,width,height);
|
||||
|
||||
if (value.get() >= 0) {
|
||||
// fill in colour block
|
||||
gc.setFill(colours[value.get()]);
|
||||
gc.setStroke(Color.BLACK);
|
||||
|
||||
if (roundedCorners) {
|
||||
gc.fillRoundRect(0,0,width,height,16,16);
|
||||
if (showStrokes) gc.strokeRoundRect(0,0,width,height,16,16);
|
||||
} else {
|
||||
gc.fillRect(0,0,width,height);
|
||||
if (showStrokes) gc.strokeRect(0,0,width,height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x-coordinate of this block in the grid
|
||||
* @return x-coordinate
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y-coordinate of this block in the grid
|
||||
* @return y-coordinate
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value property for binding, which holds the current colour value
|
||||
* @return value property
|
||||
*/
|
||||
public IntegerProperty valueProperty() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Game grid class, which displays a grid of game blocks for the game
|
||||
*/
|
||||
public class GameGrid extends GridPane {
|
||||
|
||||
private final List<BlockClickedListener> listeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Initialise game grid with an existing grid, a set size, and a set of available colours
|
||||
* @param colours available colours
|
||||
* @param grid existing grid to use
|
||||
* @param width game grid width
|
||||
* @param height game grid height
|
||||
*/
|
||||
public GameGrid(Color[] colours, Grid grid, int width, int height) {
|
||||
var cols = grid.getCols();
|
||||
var rows = grid.getRows();
|
||||
|
||||
setMaxWidth(width);
|
||||
setMaxHeight(height);
|
||||
setGridLinesVisible(true);
|
||||
|
||||
// create each internal game block
|
||||
for (var y=0; y < rows; y++) {
|
||||
for (var x=0; x < cols; x++) {
|
||||
var block = new GameBlock(colours, x, y, (double) width/cols, (double) height/rows);
|
||||
add(block,x,y);
|
||||
|
||||
block.valueProperty().bind(grid.getGridProperty(x,y));
|
||||
|
||||
// handle when game block is clicked
|
||||
block.setOnMouseClicked((e) -> blockClicked(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener for blocks being clicked
|
||||
* @param listener listener
|
||||
*/
|
||||
public void addListener(BlockClickedListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle block being clicked by calling all appropriate listeners
|
||||
* @param block block that was clicked
|
||||
*/
|
||||
public void blockClicked(GameBlock block) {
|
||||
for (BlockClickedListener listener : listeners) {
|
||||
listener.blockClicked(block);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
import uk.mgrove.ac.soton.comp1206.utility.Utility;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Game window which will display mini-game for users to play
|
||||
*/
|
||||
public class GameWindow extends BaseWindow {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(GameWindow.class);
|
||||
|
||||
private static final int maxColourChanges = 30;
|
||||
|
||||
private final Grid grid;
|
||||
private final Random random = new Random();
|
||||
private final Color[] colours;
|
||||
private final IntegerProperty currentColour = new SimpleIntegerProperty();
|
||||
private final IntegerProperty score = new SimpleIntegerProperty(0);
|
||||
private Timer gameTimer;
|
||||
private int colourChangesCount;
|
||||
private final List<String> scores = new ArrayList<>();
|
||||
@FXML
|
||||
private BorderPane gameBoardContainer;
|
||||
@FXML
|
||||
private Text currentScoreField;
|
||||
@FXML
|
||||
private VBox currentColourContainer;
|
||||
@FXML
|
||||
private VBox highScoresContainer;
|
||||
@FXML
|
||||
private VBox gameCoverContainer;
|
||||
@FXML
|
||||
private Text gameCoverTitle;
|
||||
@FXML
|
||||
private ProgressBar progressBar;
|
||||
|
||||
/**
|
||||
* Initialise the game window
|
||||
*
|
||||
* @param app the app
|
||||
* @param communicator the communicator
|
||||
*/
|
||||
public GameWindow(App app, Communicator communicator) {
|
||||
super(app, communicator, "/game.fxml");
|
||||
|
||||
colours = new Color[]{
|
||||
Color.web("0x005C84"),
|
||||
Color.web("0x74C9E5"),
|
||||
Color.web("0x4BB694"),
|
||||
Color.web("0xC1D100"),
|
||||
Color.web("0xEF7D00"),
|
||||
Color.web("0xE73037"),
|
||||
Color.web("0xD5007F"),
|
||||
Color.web("0x8D3970")
|
||||
};
|
||||
|
||||
grid = new Grid(6, 6);
|
||||
|
||||
currentScoreField.textProperty().bind(score.asString());
|
||||
|
||||
var currentColourBlock = new GameBlock(colours, 0, 0, 100, 100, false, true);
|
||||
currentColourContainer.getChildren().add(currentColourBlock);
|
||||
currentColourBlock.valueProperty().bind(currentColour);
|
||||
|
||||
communicator.addListener((message) -> {
|
||||
if (!message.startsWith("SCORES")) return;
|
||||
Platform.runLater(() -> this.receiveScore(message));
|
||||
});
|
||||
|
||||
communicator.send("SCORES");
|
||||
}
|
||||
|
||||
/**
|
||||
* Events that occur at regular intervals to maintain the game: update the current colour and handle game ending
|
||||
*/
|
||||
public void gameLoop() {
|
||||
if (colourChangesCount < maxColourChanges) {
|
||||
var randomColour = random.nextInt(colours.length);
|
||||
logger.info("Game loop - Colour chosen: {}", randomColour);
|
||||
currentColour.set(randomColour);
|
||||
colourChangesCount++;
|
||||
progressBar.setProgress((double) (maxColourChanges - colourChangesCount + 1) / maxColourChanges);
|
||||
} else {
|
||||
progressBar.setProgress(0);
|
||||
endGame();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle game block being clicked - adjust score appropriately
|
||||
*
|
||||
* @param block block that was clicked
|
||||
*/
|
||||
public void blockClicked(GameBlock block) {
|
||||
var gridBlock = grid.getGridProperty(block.getX(), block.getY());
|
||||
if (currentColour.get() == gridBlock.get()) {
|
||||
logger.info("{} is {}, scoring 5 points", currentColour.get(), gridBlock.getValue());
|
||||
score.set(score.get() + 5);
|
||||
gridBlock.set(random.nextInt(colours.length));
|
||||
} else {
|
||||
logger.info("{} is not {}, losing 1 point", currentColour.get(), gridBlock.getValue());
|
||||
if (score.get() > 0) {
|
||||
score.set(score.get() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and store scores from server
|
||||
* @param message message containing scores
|
||||
*/
|
||||
private void receiveScore(String message) {
|
||||
scores.clear();
|
||||
|
||||
for (var line : message.split("\n")) {
|
||||
if (!line.equals("SCORES")) {
|
||||
int splitIndex = line.lastIndexOf("=");
|
||||
try {
|
||||
String[] info = {line.substring(0,splitIndex), line.substring(splitIndex+1)};
|
||||
scores.add(info[0] + ": " + info[1]);
|
||||
} catch (Exception ignored) {
|
||||
logger.error("Couldn't load score from message {}",message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highScoresContainer.getChildren().clear();
|
||||
for (var i=0; i < Math.min(scores.size(),5); i++) {
|
||||
highScoresContainer.getChildren().add(new Text(scores.get(i)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show game over screen
|
||||
*/
|
||||
private void showGameOver() {
|
||||
gameCoverTitle.setText("Game Over!");
|
||||
Platform.runLater(() -> gameBoardContainer.setCenter(gameCoverContainer));
|
||||
// TODO: include a timer bar
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new game
|
||||
*/
|
||||
private void startGame() {
|
||||
colourChangesCount = 0;
|
||||
score.set(0);
|
||||
|
||||
// set up grid with random values
|
||||
for (int y = 0; y < grid.getRows(); y++) {
|
||||
for (int x = 0; x < grid.getCols(); x++) {
|
||||
grid.set(x, y, random.nextInt(colours.length));
|
||||
}
|
||||
}
|
||||
|
||||
currentColour.set(random.nextInt(colours.length));
|
||||
|
||||
var gameGrid = new GameGrid(colours, grid, 400, 350);
|
||||
gameGrid.addListener(this::blockClicked);
|
||||
|
||||
gameBoardContainer.setCenter(gameGrid);
|
||||
|
||||
TimerTask timerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
gameLoop();
|
||||
}
|
||||
};
|
||||
gameTimer = new Timer("Timer");
|
||||
gameTimer.schedule(timerTask, 0, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start game when button clicked
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void startGame(ActionEvent event) {
|
||||
startGame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return to the chat window when button clicked
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void returnToChat(ActionEvent event) {
|
||||
stopGameTimer();
|
||||
app.setGameOpen(false);
|
||||
app.returnToChat(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the game
|
||||
*/
|
||||
private void endGame() {
|
||||
// game finished
|
||||
stopGameTimer();
|
||||
currentColour.set(-1);
|
||||
communicator.send(String.format("SCORE %s %d",app.getUsername(),score.get()));
|
||||
communicator.send("SCORES");
|
||||
showGameOver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the game timer
|
||||
*/
|
||||
public void stopGameTimer() {
|
||||
if (gameTimer != null) gameTimer.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
79
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/Grid.java
Normal file
79
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/Grid.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
* Grid class, used to store a grid of game blocks for the game
|
||||
*/
|
||||
public class Grid {
|
||||
|
||||
private final int cols;
|
||||
private final int rows;
|
||||
final SimpleIntegerProperty[][] grid;
|
||||
|
||||
/**
|
||||
* Initialise the grid
|
||||
* @param cols number of columns in the grid
|
||||
* @param rows number of rows in the grid
|
||||
*/
|
||||
public Grid(int cols, int rows) {
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
|
||||
grid = new SimpleIntegerProperty[cols][rows];
|
||||
|
||||
for (var y=0; y < rows; y++) {
|
||||
for (var x=0; x < cols; x++) {
|
||||
grid[x][y] = new SimpleIntegerProperty(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property relating to a specific element in the grid
|
||||
* @param x column index
|
||||
* @param y row index
|
||||
* @return the property relating to the element at the given location
|
||||
*/
|
||||
public IntegerProperty getGridProperty(int x, int y) {
|
||||
return grid[x][y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a specific element in the grid
|
||||
* @param x column index
|
||||
* @param y row index
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void set(int x, int y, int value) {
|
||||
grid[x][y].set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a specific element in the grid
|
||||
* @param x column index
|
||||
* @param y row index
|
||||
* @return the value of the element
|
||||
*/
|
||||
public int get(int x, int y) {
|
||||
return grid[x][y].get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of columns in the grid
|
||||
* @return number of columns
|
||||
*/
|
||||
public int getCols() {
|
||||
return cols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of rows in the grid
|
||||
* @return number of rows
|
||||
*/
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user