Nearly finished
This commit is contained in:
parent
313acdb910
commit
7b75020f6b
@ -6,6 +6,7 @@ import javafx.scene.layout.GridPane;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.event.BlockClickedListener;
|
||||
import uk.ac.soton.comp1206.event.BlockRotate;
|
||||
import uk.ac.soton.comp1206.game.GamePiece;
|
||||
import uk.ac.soton.comp1206.game.Grid;
|
||||
|
||||
@ -58,9 +59,10 @@ public class GameBoard extends GridPane {
|
||||
*/
|
||||
private BlockClickedListener blockClickedListener;
|
||||
|
||||
|
||||
int keyBoardX = 0;
|
||||
int keyBoardY = 0;
|
||||
private boolean keyboardOn = false;
|
||||
private int keyBoardX = 0;
|
||||
private int keyBoardY = 0;
|
||||
private BlockRotate blockRotate;
|
||||
|
||||
/**
|
||||
* Create a new GameBoard, based off a given grid, with a visual width and height.
|
||||
@ -159,12 +161,18 @@ public class GameBoard extends GridPane {
|
||||
if(event.getButton() == MouseButton.PRIMARY) {
|
||||
this.blockClicked(event, block);
|
||||
} else if(event.getButton() == MouseButton.SECONDARY) {
|
||||
logger.info("Not yet implemented");
|
||||
this.blockRotate.rotate();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
block.setOnMouseEntered(event -> {
|
||||
this.clearHover();
|
||||
keyboardOn = false;
|
||||
block.paintHover();
|
||||
keyBoardX = block.getX();
|
||||
keyBoardY = block.getY();
|
||||
});
|
||||
|
||||
block.setOnMouseExited(event -> {
|
||||
@ -195,14 +203,25 @@ public class GameBoard extends GridPane {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the grid
|
||||
*/
|
||||
public void clear() {
|
||||
this.grid.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds piece to game board
|
||||
* @param piece the piece to add
|
||||
*/
|
||||
public void addPiece(GamePiece piece) {
|
||||
this.grid.addPiece(piece, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a piece on the board
|
||||
* @param piece piece
|
||||
*/
|
||||
public void setPiece(GamePiece piece) {
|
||||
for(int[] gameBlock: piece.getBlocks()) {
|
||||
this.clear();
|
||||
@ -211,9 +230,77 @@ public class GameBoard extends GridPane {
|
||||
}
|
||||
}
|
||||
|
||||
public void setCentreFill() {
|
||||
/**
|
||||
* Handles keyboard controls
|
||||
* @param direction
|
||||
*/
|
||||
public void keyboardInput(String direction){
|
||||
keyboardOn = true;
|
||||
this.clearHover();
|
||||
|
||||
switch (direction){
|
||||
case "up":
|
||||
if (keyBoardY > 0) {
|
||||
keyBoardY--;
|
||||
}
|
||||
break;
|
||||
case "down":
|
||||
if (keyBoardY < blocks[keyBoardX].length - 1) {
|
||||
keyBoardY++;
|
||||
}
|
||||
break;
|
||||
case "left":
|
||||
if (keyBoardX > 0) {
|
||||
keyBoardX--;
|
||||
}
|
||||
break;
|
||||
case "right":
|
||||
if (keyBoardX < blocks.length - 1) {
|
||||
keyBoardX++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Optionally handle unexpected input
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure the new coordinates are within the bounds of the array
|
||||
keyBoardX = Math.max(0, Math.min(keyBoardX, blocks.length - 1));
|
||||
keyBoardY = Math.max(0, Math.min(keyBoardY, blocks[keyBoardX].length - 1));
|
||||
|
||||
this.blocks[keyBoardX][keyBoardY].paintHover();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes hover effect from board
|
||||
*/
|
||||
public void clearHover() {
|
||||
for (int i = 0; i < this.cols; i++) {
|
||||
for (int j = 0; j < this.rows; j++) {
|
||||
this.blocks[i][j].paint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the center fill
|
||||
*/
|
||||
public void setCenterFill() {
|
||||
this.blocks[this.cols/2][this.rows/2].setPaintCentre(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables keyboard highlighting
|
||||
*/
|
||||
public void keyboardSelect() {
|
||||
this.blockClicked(null, this.getBlock(keyBoardX, keyBoardY));
|
||||
}
|
||||
|
||||
/**
|
||||
* set the block rotation
|
||||
* @param blockRotate
|
||||
*/
|
||||
public void setBlockRotate(BlockRotate blockRotate) {
|
||||
this.blockRotate = blockRotate;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,13 +11,19 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.scene.ScoresScene;
|
||||
|
||||
/**
|
||||
* Represents a visual leaderboard component in a vertical box layout.
|
||||
*/
|
||||
public class Leaderboard extends VBox {
|
||||
|
||||
Logger logger = LogManager.getLogger(ScoresScene.class);
|
||||
private SimpleListProperty<Pair<String, Integer>> leaderboard;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new Leaderboard component.
|
||||
*
|
||||
* @param leaderboard The data model for leaderboard entries, where each entry is a pair of player name and score.
|
||||
*/
|
||||
public Leaderboard(SimpleListProperty<Pair<String, Integer>> leaderboard) {
|
||||
this.leaderboard = leaderboard;
|
||||
this.leaderboard.addListener((ListChangeListener<Pair<String, Integer>>) c -> {
|
||||
@ -25,14 +31,21 @@ public class Leaderboard extends VBox {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visual display of the leaderboard when changes occur in the data model.
|
||||
*
|
||||
* @param change Details about the change that occurred in the leaderboard data.
|
||||
*/
|
||||
private void updateLeaderBoard(ListChangeListener.Change<? extends Pair<String, Integer>> change) {
|
||||
logger.info("Updating leaderboard");
|
||||
|
||||
if (change.next()) {
|
||||
|
||||
if (change.wasPermutated() || change.wasReplaced() || change.wasRemoved()) {
|
||||
super.getChildren().clear();
|
||||
}
|
||||
|
||||
|
||||
if (change.wasAdded()) {
|
||||
for (Pair<String, Integer> pair : change.getAddedSubList()) {
|
||||
var text = new Text(pair.getKey() + ": " + pair.getValue());
|
||||
@ -42,6 +55,4 @@ public class Leaderboard extends VBox {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
190
src/main/java/uk/ac/soton/comp1206/component/LobbyInterface.java
Normal file
190
src/main/java/uk/ac/soton/comp1206/component/LobbyInterface.java
Normal file
@ -0,0 +1,190 @@
|
||||
package uk.ac.soton.comp1206.component;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.event.StartMultiplayer;
|
||||
import uk.ac.soton.comp1206.tools.MultiplayerGame;
|
||||
|
||||
/**
|
||||
* The LobbyInterface class provides a user interface within the lobby of a multiplayer game.
|
||||
* It allows users to interact with the game by joining, creating, and leaving games.
|
||||
*/
|
||||
public class LobbyInterface extends BorderPane {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LobbyInterface.class);
|
||||
private VBox vbox = new VBox();
|
||||
private Text title;
|
||||
private HBox hbox = new HBox();
|
||||
private ListView<String> playerList;
|
||||
private ListView<String> messages;
|
||||
private Button leaveButton = new Button("Leave");
|
||||
private MultiplayerGame multiplayerGame;
|
||||
private SimpleStringProperty newGame;
|
||||
private TextField newGameName;
|
||||
private TextField chatBox;
|
||||
private StartMultiplayer startMultiplayer;
|
||||
private VBox messageBox = new VBox();
|
||||
private HBox buttonBox = new HBox();
|
||||
|
||||
/**
|
||||
* Constructs a new LobbyInterface with the given multiplayer game context.
|
||||
*
|
||||
* @param multiplayerGame the multiplayer game instance this interface will interact with
|
||||
*/
|
||||
public LobbyInterface(MultiplayerGame multiplayerGame) {
|
||||
super();
|
||||
this.multiplayerGame = multiplayerGame;
|
||||
title = new Text();
|
||||
title.getStyleClass().add("heading");
|
||||
vbox.getChildren().add(title);
|
||||
|
||||
hbox.getChildren().add(messageBox);
|
||||
|
||||
leaveButton.getStyleClass().add("small-button");
|
||||
messages = new ListView<>(multiplayerGame.getMessages());
|
||||
messages.setMinWidth(400);
|
||||
messageBox.getChildren().add(messages);
|
||||
|
||||
chatBox = new TextField();
|
||||
messageBox.getChildren().add(chatBox);
|
||||
|
||||
playerList = new ListView<>(multiplayerGame.getPlayers());
|
||||
playerList.setBackground(Background.fill(Color.TRANSPARENT));
|
||||
hbox.getChildren().add(playerList);
|
||||
|
||||
// Custom factory cell for player list
|
||||
playerList.setCellFactory(lv -> new ListCell<String>() {
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
getStyleClass().clear();
|
||||
} else {
|
||||
setText(item);
|
||||
if (!getStyleClass().contains("player-item")) {
|
||||
getStyleClass().add("player-item");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
messages.setCellFactory(lv -> new ListCell<String>() {
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
getStyleClass().clear();
|
||||
} else {
|
||||
setText(item);
|
||||
if (!getStyleClass().contains("message-item")) {
|
||||
getStyleClass().add("message-item");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
leaveButton.setOnAction(event -> {
|
||||
multiplayerGame.leave();
|
||||
});
|
||||
|
||||
multiplayerGame.nameProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
updateUI(multiplayerGame.getName());
|
||||
});
|
||||
});
|
||||
|
||||
this.setTop(vbox);
|
||||
this.setBottom(null);
|
||||
this.updateUI("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI elements based on the current game state.
|
||||
*
|
||||
* @param gameName the name of the current game, if any
|
||||
*/
|
||||
private void updateUI(String gameName) {
|
||||
if (!gameName.isEmpty()) {
|
||||
vbox.getChildren().clear();
|
||||
title.setText("Current game: " + gameName);
|
||||
vbox.getChildren().add(title);
|
||||
if (!vbox.getChildren().contains(hbox)) {
|
||||
vbox.getChildren().add(hbox);
|
||||
}
|
||||
vbox.getChildren().add(buttonBox);
|
||||
buttonBox.getChildren().clear();
|
||||
buttonBox.getChildren().add(leaveButton);
|
||||
} else {
|
||||
title.setText("Not currently in a game.");
|
||||
vbox.getChildren().remove(hbox);
|
||||
vbox.getChildren().clear();
|
||||
var createGame = new Text("Create Game");
|
||||
createGame.getStyleClass().add("heading");
|
||||
vbox.getChildren().add(createGame);
|
||||
newGameName = new TextField();
|
||||
vbox.getChildren().add(newGameName);
|
||||
this.setBottom(null);
|
||||
}
|
||||
|
||||
if (this.multiplayerGame.isHostProperty().get()) {
|
||||
var startGame = new Button("Start Game");
|
||||
startGame.getStyleClass().add("small-button");
|
||||
startGame.setOnAction(event -> {
|
||||
this.startMultiplayer.start();
|
||||
});
|
||||
buttonBox.getChildren().clear();
|
||||
var spacer = new Rectangle();
|
||||
spacer.setWidth(15);
|
||||
buttonBox.getChildren().addAll(startGame, spacer, leaveButton);
|
||||
} else {
|
||||
buttonBox.getChildren().removeIf(child -> child instanceof Button && "Start Game".equals(((Button) child).getText()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entered name for a new game.
|
||||
* @return a string representing the new game name
|
||||
*/
|
||||
public String getNextGameName() {
|
||||
return newGameName.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action triggered when the Enter key is pressed.
|
||||
*/
|
||||
public void callOnEnter() {
|
||||
logger.info("Enter clicked");
|
||||
if (this.multiplayerGame.getName().isEmpty()) {
|
||||
logger.info("Creating new game");
|
||||
this.multiplayerGame.createGame(newGameName.getText());
|
||||
} else {
|
||||
this.multiplayerGame.sendMessage(chatBox.getText());
|
||||
chatBox.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be taken when the multiplayer game starts.
|
||||
* @param startMultiplayer the start multiplayer event handler
|
||||
*/
|
||||
public void setOnStartMultiplayer(StartMultiplayer startMultiplayer) {
|
||||
this.startMultiplayer = startMultiplayer;
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
package uk.ac.soton.comp1206.component;
|
||||
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.*;
|
||||
import uk.ac.soton.comp1206.state.MultiplayerGame;
|
||||
|
||||
|
||||
public class LobyInterface extends BorderPane {
|
||||
private VBox vbox = new VBox();
|
||||
|
||||
public LobyInterface(MultiplayerGame multiplayerGame) {
|
||||
super();
|
||||
var title = new Text(multiplayerGame.getName());
|
||||
title.getStyleClass().add("heading");
|
||||
super.setTop(vbox);
|
||||
|
||||
multiplayerGame.nameProperty().addListener((observable, oldValue, newValue) -> {
|
||||
|
||||
title.setText("Current game: " + newValue);
|
||||
var leaveButton = new Button("Leave");
|
||||
super.setBottom(leaveButton);
|
||||
leaveButton.setOnAction(event -> {
|
||||
multiplayerGame.leave();
|
||||
});
|
||||
|
||||
var messageBox = new HBox();
|
||||
vbox.getChildren().add(messageBox);
|
||||
var textInput = new TextField();
|
||||
messageBox.getChildren().add(textInput);
|
||||
|
||||
});
|
||||
|
||||
vbox.getChildren().add(title);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -12,13 +12,20 @@ import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.event.ChannelSelected;
|
||||
import uk.ac.soton.comp1206.scene.MenuScene;
|
||||
|
||||
/**
|
||||
* The MultiplayerGameList class represents a visual list of multiplayer game channels.
|
||||
* It provides functionality to select a game channel from the list.
|
||||
*/
|
||||
public class MultiplayerGameList extends VBox {
|
||||
private static final Logger logger = LogManager.getLogger(MenuScene.class);
|
||||
private static final Logger logger = LogManager.getLogger(MultiplayerGameList.class);
|
||||
private SimpleListProperty<String> channels;
|
||||
private ChannelSelected channelSelected;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new MultiplayerGameList with the given list of game channels.
|
||||
*
|
||||
* @param channels the observable list of game channel names
|
||||
*/
|
||||
public MultiplayerGameList(SimpleListProperty<String> channels) {
|
||||
super();
|
||||
this.channels = channels;
|
||||
@ -31,7 +38,7 @@ public class MultiplayerGameList extends VBox {
|
||||
channels.addListener((ListChangeListener<String>) c -> {
|
||||
while (c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
c.getAddedSubList().forEach(channel -> {renderChannel(channel);});
|
||||
c.getAddedSubList().forEach(this::renderChannel);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
@ -39,33 +46,48 @@ public class MultiplayerGameList extends VBox {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event handler to be called when a game channel is selected.
|
||||
*
|
||||
* @param channelSelected the event handler for channel selection
|
||||
*/
|
||||
public void setOnChannelSelected(ChannelSelected channelSelected) {
|
||||
this.channelSelected = channelSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the list of game channels by rendering each channel as a button.
|
||||
*/
|
||||
private void initializeChannelList() {
|
||||
channels.forEach(this::renderChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button for each channel in the game list.
|
||||
*
|
||||
* @param channel the name of the game channel to render as a button
|
||||
*/
|
||||
private void renderChannel(String channel) {
|
||||
Platform.runLater(() -> {
|
||||
var channelButton = new Button(channel);
|
||||
channelButton.getStyleClass().add("channel-item");
|
||||
channelButton.setOnAction(event -> {
|
||||
logger.info("Button pressed "+ channel);
|
||||
this.channelSelected.channelSelected(channel);
|
||||
logger.info("Button pressed " + channel);
|
||||
if (channelSelected != null) {
|
||||
channelSelected.channelSelected(channel);
|
||||
}
|
||||
});
|
||||
|
||||
this.getChildren().add(channelButton);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the list by removing all buttons. This is typically called when the underlying data model changes significantly.
|
||||
*/
|
||||
private void reset() {
|
||||
Platform.runLater(() -> {
|
||||
this.getChildren().removeIf(node -> node instanceof Button);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -5,74 +5,122 @@ import javafx.scene.media.MediaPlayer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* The SoundPlayer class provides static methods to manage audio playback, including sound effects and background music.
|
||||
* This class is not intended to be instantiated.
|
||||
*/
|
||||
public class SoundPlayer {
|
||||
private static final Logger logger = LogManager.getLogger(SoundPlayer.class);
|
||||
private static MediaPlayer mediaPlayer;
|
||||
private static MediaPlayer secondaryMediaPlayer;
|
||||
private static int mainVolume = 100; // Default volume percentage for main sounds
|
||||
private static int secondaryVolume = 100; // Default volume percentage for background music
|
||||
private static MediaPlayer introPlayer;
|
||||
private static MediaPlayer loopPlayer;
|
||||
private static boolean audioEnabled = true;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private SoundPlayer() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a single audio file once.
|
||||
*
|
||||
* @param file the path to the audio file to play
|
||||
*/
|
||||
public static void playAudio(String file) {
|
||||
if (!audioEnabled) return;
|
||||
|
||||
public static void playSound(String filePath) {
|
||||
try {
|
||||
String toPlay = SoundPlayer.class.getResource(filePath).toExternalForm();
|
||||
Media play = new Media(toPlay);
|
||||
mediaPlayer = new MediaPlayer(play);
|
||||
mediaPlayer.setVolume(mainVolume);
|
||||
|
||||
String toPlay = SoundPlayer.class.getResource(file).toExternalForm();
|
||||
Media media = new Media(toPlay);
|
||||
MediaPlayer mediaPlayer = new MediaPlayer(media);
|
||||
mediaPlayer.play();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Unable to play audio file, disabling audio");
|
||||
audioEnabled = false;
|
||||
logger.error("Unable to play audio file: " + file + ", disabling audio", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Media createMedia(String mediaUrl) {
|
||||
return new Media(mediaUrl);
|
||||
}
|
||||
/**
|
||||
* Plays a looping background music file.
|
||||
*
|
||||
* @param loopFile the path to the audio file to loop
|
||||
*/
|
||||
public static void playBackgroundMusic(String loopFile) {
|
||||
if (!audioEnabled) return;
|
||||
|
||||
public static void playBackgroundMusic(String fileName) {
|
||||
try {
|
||||
logger.info("Playing background music: " + fileName);
|
||||
Media media = new Media(fileName);
|
||||
if (secondaryMediaPlayer != null) {
|
||||
secondaryMediaPlayer.stop(); // Stop current playing music if any
|
||||
}
|
||||
secondaryMediaPlayer = new MediaPlayer(media);
|
||||
secondaryMediaPlayer.setVolume(secondaryVolume / 100.0);
|
||||
secondaryMediaPlayer.setCycleCount(MediaPlayer.INDEFINITE); // Loop indefinitely
|
||||
secondaryMediaPlayer.play();
|
||||
String mediaUrl = SoundPlayer.class.getResource(loopFile).toExternalForm();
|
||||
Media media = new Media(mediaUrl);
|
||||
loopPlayer = new MediaPlayer(media);
|
||||
loopPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
loopPlayer.play();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error playing background music: " + fileName, e);
|
||||
audioEnabled = false;
|
||||
logger.error("Unable to play background music: " + loopFile + ", disabling audio", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopSound() {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.stop();
|
||||
/**
|
||||
* Plays a one-time intro audio followed by a looping background music file.
|
||||
*
|
||||
* @param introFile the path to the intro audio file
|
||||
* @param loopFile the path to the looping background music file
|
||||
*/
|
||||
public static void playBackgroundMusic(String introFile, String loopFile) {
|
||||
if (!audioEnabled) return;
|
||||
|
||||
try {
|
||||
// Play intro
|
||||
String introMediaUrl = SoundPlayer.class.getResource(introFile).toExternalForm();
|
||||
Media introMedia = new Media(introMediaUrl);
|
||||
introPlayer = new MediaPlayer(introMedia);
|
||||
|
||||
// Prepare loop
|
||||
String loopMediaUrl = SoundPlayer.class.getResource(loopFile).toExternalForm();
|
||||
Media loopMedia = new Media(loopMediaUrl);
|
||||
loopPlayer = new MediaPlayer(loopMedia);
|
||||
loopPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
|
||||
// When the intro finishes, start the loop
|
||||
introPlayer.setOnEndOfMedia(() -> {
|
||||
introPlayer.dispose(); // Release the intro player resources
|
||||
loopPlayer.play();
|
||||
});
|
||||
|
||||
introPlayer.play();
|
||||
} catch (Exception e) {
|
||||
audioEnabled = false;
|
||||
logger.error("Unable to play background music with intro: " + introFile + ", disabling audio", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all background music and releases associated resources.
|
||||
*/
|
||||
public static void stopBackgroundMusic() {
|
||||
if (secondaryMediaPlayer != null) {
|
||||
secondaryMediaPlayer.stop();
|
||||
if (introPlayer != null) {
|
||||
introPlayer.stop();
|
||||
introPlayer.dispose(); // Properly release resources
|
||||
introPlayer = null;
|
||||
}
|
||||
if (loopPlayer != null) {
|
||||
loopPlayer.stop();
|
||||
loopPlayer.dispose(); // Properly release resources
|
||||
loopPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setMainVolume(int volume) {
|
||||
mainVolume = volume;
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.setVolume(mainVolume / 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setBackgroundMusicVolume(int volume) {
|
||||
secondaryVolume = volume;
|
||||
if (secondaryMediaPlayer != null) {
|
||||
secondaryMediaPlayer.setVolume(secondaryVolume / 100.0);
|
||||
/**
|
||||
* Toggles the state of audio enabled/disabled.
|
||||
* If disabling, stops any playing music.
|
||||
*/
|
||||
public static void toggleAudio() {
|
||||
audioEnabled = !audioEnabled;
|
||||
if (!audioEnabled) {
|
||||
stopBackgroundMusic();
|
||||
} else {
|
||||
logger.info("Audio enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
package uk.ac.soton.comp1206.event;
|
||||
|
||||
public interface BlockRotate {
|
||||
/**
|
||||
* Rotate the next piece
|
||||
*/
|
||||
public void rotate();
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
package uk.ac.soton.comp1206.event;
|
||||
|
||||
public interface ChatMessage {
|
||||
/**
|
||||
* Chat message event
|
||||
* @param message
|
||||
*/
|
||||
public void getMessage(String message);
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
package uk.ac.soton.comp1206.event;
|
||||
|
||||
public interface StartMultiplayer {
|
||||
/**
|
||||
* Used to start multiplayer
|
||||
*/
|
||||
public void start();
|
||||
}
|
||||
@ -6,6 +6,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.component.GameBlock;
|
||||
import uk.ac.soton.comp1206.component.GameBoard;
|
||||
import uk.ac.soton.comp1206.effects.SoundPlayer;
|
||||
import uk.ac.soton.comp1206.event.GameLoopHandler;
|
||||
import uk.ac.soton.comp1206.event.LooseALife;
|
||||
import uk.ac.soton.comp1206.event.OutOfLives;
|
||||
@ -47,6 +48,7 @@ public class Game {
|
||||
private IntegerProperty lives = new SimpleIntegerProperty(3);
|
||||
private IntegerProperty score = new SimpleIntegerProperty(0);
|
||||
private IntegerProperty level = new SimpleIntegerProperty(0);
|
||||
private IntegerProperty multiplier = new SimpleIntegerProperty(1);
|
||||
|
||||
protected GamePiece currentPiece;
|
||||
protected GamePiece nextPiece;
|
||||
@ -72,8 +74,6 @@ public class Game {
|
||||
this.grid = new Grid(cols,rows);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,6 +100,9 @@ public class Game {
|
||||
|
||||
this.startGameLoop();
|
||||
|
||||
score.addListener((observable, oldValue, newValue) -> {
|
||||
level.set(newValue.intValue() / 1000);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -126,8 +129,10 @@ public class Game {
|
||||
|
||||
*/
|
||||
if (grid.canPlayPiece(currentPiece, x, y)) {
|
||||
SoundPlayer.playAudio("/sounds/place.wav");
|
||||
logger.info("Piece can be placed");
|
||||
} else {
|
||||
SoundPlayer.playAudio("/sounds/fail.wav");
|
||||
logger.info("Piece cannot be placed");
|
||||
return;
|
||||
}
|
||||
@ -182,14 +187,30 @@ public class Game {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Increment the next pieces
|
||||
*/
|
||||
public void incrementNextPieces() {
|
||||
this.score.set(this.score.get() + grid.clearLines());
|
||||
|
||||
int[] cleared = this.grid.clearLines();
|
||||
|
||||
if (cleared[0] > 0) {
|
||||
// Calculate score using the number of blocks cleared and the multiplier
|
||||
int scoreIncrement = cleared[0] * cleared[1] * 10 * multiplier.get();
|
||||
score.set(score.get() + scoreIncrement);
|
||||
|
||||
// Increment the multiplier for each line clearing event
|
||||
multiplier.set(multiplier.get() + 1);
|
||||
} else {
|
||||
// Reset multiplier if no lines are cleared
|
||||
multiplier.set(1);
|
||||
}
|
||||
|
||||
// Move to the next piece
|
||||
this.currentPiece = this.nextPiece;
|
||||
this.nextPiece = this.spawnPiece();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get current score
|
||||
* @return InterProperty score
|
||||
@ -267,6 +288,9 @@ public class Game {
|
||||
this.executor.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Game loop handles the executing of the game
|
||||
*/
|
||||
private void gameLoop() {
|
||||
logger.info("Game loop");
|
||||
this.nextLoop.cancel(true);
|
||||
@ -314,4 +338,5 @@ public class Game {
|
||||
this.looseALife = looseALife;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.effects.SoundPlayer;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
@ -32,6 +33,7 @@ public class Grid {
|
||||
*/
|
||||
private final int rows;
|
||||
|
||||
|
||||
/**
|
||||
* The grid is a 2D arrow with rows and columns of SimpleIntegerProperties.
|
||||
*/
|
||||
@ -161,55 +163,64 @@ public class Grid {
|
||||
|
||||
/**
|
||||
* Checks which lines can be cleared
|
||||
* @return number of blocks cleared
|
||||
* @return array of number of lines and blocks
|
||||
*/
|
||||
public int clearLines() {
|
||||
//List of cords to clear
|
||||
public int[] clearLines() {
|
||||
HashSet<int[]> cordsToRemove = new HashSet<>();
|
||||
|
||||
int xLines = 0;
|
||||
int yLines = 0;
|
||||
|
||||
//Count and clear lines in the x-axis
|
||||
for(int x = 0; x < cols; x++) {
|
||||
// Count and clear lines in the x-axis (columns)
|
||||
for (int x = 0; x < cols; x++) {
|
||||
boolean isLine = true;
|
||||
for(int y = 0; y < rows; y++) {
|
||||
for (int y = 0; y < rows; y++) {
|
||||
if (this.get(x, y) == 0) {
|
||||
isLine = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isLine) {
|
||||
xLines ++;
|
||||
if (isLine) {
|
||||
xLines++;
|
||||
for (int y = 0; y < rows; y++) {
|
||||
cordsToRemove.add(new int[]{x, y});
|
||||
}
|
||||
}
|
||||
}
|
||||
//Count and clear in the y-axis
|
||||
for(int y = 0; y < rows; y++) {
|
||||
|
||||
// Count and clear lines in the y-axis (rows)
|
||||
for (int y = 0; y < rows; y++) {
|
||||
boolean isLine = true;
|
||||
for(int x = 0; x < cols; x++) {
|
||||
for (int x = 0; x < cols; x++) {
|
||||
if (this.get(x, y) == 0) {
|
||||
isLine = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isLine) {
|
||||
xLines ++;
|
||||
for (int x = 0; x < rows; x++) {
|
||||
if (isLine) {
|
||||
yLines++;
|
||||
for (int x = 0; x < cols; x++) {
|
||||
cordsToRemove.add(new int[]{x, y});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int[] cords : cordsToRemove) {
|
||||
grid[cords[0]][cords[1]].set(0);
|
||||
// Clear blocks from grid
|
||||
for (int[] cords : cordsToRemove) {
|
||||
this.set(cords[0], cords[1], 0);
|
||||
}
|
||||
|
||||
// Play sound if any line was cleared
|
||||
if (!cordsToRemove.isEmpty()) {
|
||||
SoundPlayer.playAudio("/sounds/clear.wav");
|
||||
}
|
||||
|
||||
int totalLinesCleared = xLines + yLines;
|
||||
int totalBlocksCleared = cordsToRemove.size();
|
||||
|
||||
return cordsToRemove.size();
|
||||
return new int[] {totalBlocksCleared, totalLinesCleared};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the whole board
|
||||
*/
|
||||
|
||||
@ -28,11 +28,11 @@ import uk.ac.soton.comp1206.ui.GameWindow;
|
||||
*/
|
||||
public class ChallengeScene extends BaseScene {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MenuScene.class);
|
||||
private static final Logger logger = LogManager.getLogger(ChallengeScene.class);
|
||||
protected Game game;
|
||||
|
||||
protected Timer timer;
|
||||
|
||||
protected GameBoard board;
|
||||
protected GameBoard nextPiece;
|
||||
protected GameBoard nextPiece2;
|
||||
|
||||
@ -66,7 +66,7 @@ public class ChallengeScene extends BaseScene {
|
||||
var mainPane = new BorderPane();
|
||||
challengePane.getChildren().add(mainPane);
|
||||
|
||||
var board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2);
|
||||
board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2);
|
||||
mainPane.setCenter(board);
|
||||
|
||||
//Right side formatting
|
||||
@ -154,7 +154,7 @@ public class ChallengeScene extends BaseScene {
|
||||
|
||||
//Next piece grid
|
||||
this.nextPiece = new GameBoard(3, 3, this.gameWindow.getWidth()/6, this.gameWindow.getWidth()/6);
|
||||
this.nextPiece.setCentreFill();
|
||||
this.nextPiece.setCenterFill();
|
||||
this.nextPiece.setPadding(new Insets(20,20,20,20));
|
||||
rightBox.getChildren().add(nextPiece);
|
||||
|
||||
@ -191,9 +191,7 @@ public class ChallengeScene extends BaseScene {
|
||||
this.game.setOnGameLoop(this::gameLoop);
|
||||
this.game.setOnOutOfLives(this::gameOver);
|
||||
this.game.setOnLossALife(this::lossOfLife);
|
||||
|
||||
|
||||
|
||||
this.board.setBlockRotate(this::rotateNext);
|
||||
|
||||
}
|
||||
|
||||
@ -201,7 +199,6 @@ public class ChallengeScene extends BaseScene {
|
||||
* Handling loss of life events
|
||||
*/
|
||||
private void lossOfLife() {
|
||||
SoundPlayer.playSound("sounds/lifeloose.wav");
|
||||
this.game.incrementNextPieces();
|
||||
this.updateNextPieces();
|
||||
}
|
||||
@ -210,6 +207,7 @@ public class ChallengeScene extends BaseScene {
|
||||
* When the game is over
|
||||
*/
|
||||
private void gameOver() {
|
||||
SoundPlayer.playAudio("/sounds/fail.wav");
|
||||
//This is required so that it runs on the javaFX thread and not in the gameLoop thread
|
||||
Platform.runLater(() -> {
|
||||
logger.info("Game Over");
|
||||
@ -280,6 +278,8 @@ public class ChallengeScene extends BaseScene {
|
||||
@Override
|
||||
public void initialise() {
|
||||
|
||||
SoundPlayer.stopBackgroundMusic();
|
||||
SoundPlayer.playBackgroundMusic("/music/game_start.wav","/music/game.wav");
|
||||
|
||||
logger.info("Initialising Challenge");
|
||||
game.start();
|
||||
@ -302,13 +302,22 @@ public class ChallengeScene extends BaseScene {
|
||||
}
|
||||
});
|
||||
|
||||
this.game.getLives().addListener((observable, oldValue, newValue) -> {
|
||||
if(oldValue.intValue() > newValue.intValue()) {
|
||||
SoundPlayer.playAudio("/sounds/lifelose.wav");
|
||||
} else {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void endGame() {
|
||||
logger.info("Ending game");
|
||||
this.game.killGameLoop();
|
||||
//SoundPlayer.stopAll();
|
||||
SoundPlayer.stopBackgroundMusic();
|
||||
SoundPlayer.playBackgroundMusic("/music/end.wav");
|
||||
}
|
||||
|
||||
|
||||
@ -324,6 +333,7 @@ public class ChallengeScene extends BaseScene {
|
||||
* Swap the next pieces in the queue
|
||||
*/
|
||||
public void swampNextPieces() {
|
||||
SoundPlayer.playAudio("/sounds/place.wav");
|
||||
this.game.swapNextPieces();
|
||||
this.updateNextPieces();
|
||||
}
|
||||
@ -332,6 +342,7 @@ public class ChallengeScene extends BaseScene {
|
||||
* Rotates the piece that is next up
|
||||
*/
|
||||
public void rotateNext() {
|
||||
SoundPlayer.playAudio("/sounds/rotate.wav");
|
||||
this.game.getCurrentGamePiece().rotate();
|
||||
this.updateNextPieces();
|
||||
}
|
||||
@ -343,19 +354,29 @@ public class ChallengeScene extends BaseScene {
|
||||
public void handleKeyPressed(KeyEvent keyEvent) {
|
||||
logger.info("Handling key pressed");
|
||||
if(keyEvent.getCode() == KeyCode.SPACE || keyEvent.getCode() == KeyCode.R) {
|
||||
this.game.swapNextPieces();
|
||||
this.updateNextPieces();
|
||||
this.swampNextPieces();
|
||||
} else if(keyEvent.getCode() == KeyCode.ENTER) {
|
||||
|
||||
this.board.keyboardSelect();
|
||||
} else if(keyEvent.getCode() == KeyCode.Q || keyEvent.getCode() == KeyCode.OPEN_BRACKET || keyEvent.getCode() == KeyCode.Z) {
|
||||
logger.info("Rotating left");
|
||||
this.game.getCurrentGamePiece().rotate();
|
||||
this.updateNextPieces();
|
||||
return;
|
||||
} else if(keyEvent.getCode() == KeyCode.E || keyEvent.getCode() == KeyCode.C || keyEvent.getCode() == KeyCode.CLOSE_BRACKET);
|
||||
this.rotateNext();
|
||||
} else if(keyEvent.getCode() == KeyCode.E || keyEvent.getCode() == KeyCode.C || keyEvent.getCode() == KeyCode.CLOSE_BRACKET) {
|
||||
logger.info("Rotating right");
|
||||
SoundPlayer.playAudio("/sounds/rotate.wav");
|
||||
this.game.getCurrentGamePiece().rotate(3);
|
||||
this.updateNextPieces();
|
||||
} else if (keyEvent.getCode() == KeyCode.UP || keyEvent.getCode() == KeyCode.W) {
|
||||
this.board.keyboardInput("up");
|
||||
} else if (keyEvent.getCode() == KeyCode.DOWN || keyEvent.getCode() == KeyCode.S) {
|
||||
logger.info("Moving down");
|
||||
this.board.keyboardInput("down");
|
||||
} else if (keyEvent.getCode() == KeyCode.LEFT || keyEvent.getCode() == KeyCode.A) {
|
||||
logger.info("Moving left");
|
||||
this.board.keyboardInput("left");
|
||||
} else if (keyEvent.getCode() == KeyCode.RIGHT || keyEvent.getCode() == KeyCode.D) {
|
||||
logger.info("Moving right");
|
||||
this.board.keyboardInput("right");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,15 +2,18 @@ package uk.ac.soton.comp1206.scene;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import uk.ac.soton.comp1206.component.LobyInterface;
|
||||
import uk.ac.soton.comp1206.component.LobbyInterface;
|
||||
import uk.ac.soton.comp1206.component.MultiplayerGameList;
|
||||
import uk.ac.soton.comp1206.effects.SoundPlayer;
|
||||
import uk.ac.soton.comp1206.network.Communicator;
|
||||
import uk.ac.soton.comp1206.ui.GamePane;
|
||||
import uk.ac.soton.comp1206.ui.GameWindow;
|
||||
@ -22,6 +25,9 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Represents the lobby scene where players can join multiplayer games.
|
||||
*/
|
||||
public class LobbyScene extends BaseScene {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LobbyScene.class);
|
||||
@ -30,14 +36,23 @@ public class LobbyScene extends BaseScene {
|
||||
private SimpleListProperty<String> gamesList = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private MultiplayerGameList multiplayerGameList = new MultiplayerGameList(gamesList);
|
||||
private ScheduledFuture<?> nextLoop;
|
||||
private LobyInterface lobyInterface;
|
||||
private LobbyInterface lobbyInterface;
|
||||
private HBox hBox;
|
||||
private SimpleStringProperty simpleStringProperty;
|
||||
|
||||
/**
|
||||
* Constructs a new LobbyScene.
|
||||
*
|
||||
* @param gameWindow The main game window.
|
||||
*/
|
||||
public LobbyScene(GameWindow gameWindow) {
|
||||
super(gameWindow);
|
||||
lobyInterface = new LobyInterface(gameWindow.getMultiplayerGame());
|
||||
lobbyInterface = new LobbyInterface(gameWindow.getMultiplayerGame());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the scene, setting up communications and event listeners.
|
||||
*/
|
||||
@Override
|
||||
public void initialise() {
|
||||
communicator = gameWindow.getCommunicator();
|
||||
@ -46,10 +61,14 @@ public class LobbyScene extends BaseScene {
|
||||
|
||||
multiplayerGameList.setOnChannelSelected(this::handleChannelSelected);
|
||||
scene.setOnKeyPressed(e -> {
|
||||
if(this.gameWindow.getMultiplayerGame().getName() != ""){
|
||||
this.gameWindow.getMultiplayerGame().leave();
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
if (!this.gameWindow.getMultiplayerGame().getName().isEmpty()) {
|
||||
this.gameWindow.getMultiplayerGame().leave();
|
||||
}
|
||||
this.gameWindow.startMenu();
|
||||
} else if (e.getCode() == KeyCode.ENTER) {
|
||||
this.lobbyInterface.callOnEnter();
|
||||
}
|
||||
this.gameWindow.startMenu();
|
||||
});
|
||||
|
||||
gameWindow.getMultiplayerGame().nameProperty().addListener((observable, oldValue, newValue) -> {
|
||||
@ -59,8 +78,20 @@ public class LobbyScene extends BaseScene {
|
||||
showChannelSelector();
|
||||
}
|
||||
});
|
||||
|
||||
lobbyInterface.setOnStartMultiplayer(this::startGame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a multiplayer game.
|
||||
*/
|
||||
private void startGame() {
|
||||
gameWindow.startMultiplayerGame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the visual elements of the lobby scene.
|
||||
*/
|
||||
@Override
|
||||
public void build() {
|
||||
logger.info("Building Lobby");
|
||||
@ -79,24 +110,31 @@ public class LobbyScene extends BaseScene {
|
||||
BorderPane.setAlignment(title, Pos.CENTER);
|
||||
mainPane.setTop(title);
|
||||
|
||||
|
||||
renderUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the channel selector and shows the lobby interface.
|
||||
*/
|
||||
private void hideChannelSelector() {
|
||||
hBox.getChildren().clear();
|
||||
hBox.getChildren().add(lobyInterface);
|
||||
lobyInterface.setMinWidth(gameWindow.getWidth() * 0.9);
|
||||
lobyInterface.setMaxHeight(gameWindow.getHeight() * 0.8);
|
||||
|
||||
hBox.getChildren().add(lobbyInterface);
|
||||
lobbyInterface.setMinWidth(gameWindow.getWidth() * 0.9);
|
||||
lobbyInterface.setMaxHeight(gameWindow.getHeight() * 0.8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the channel selector.
|
||||
*/
|
||||
private void showChannelSelector() {
|
||||
logger.info("Showing channel selector");
|
||||
hBox.getChildren().clear();
|
||||
renderUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the user interface components inside the lobby.
|
||||
*/
|
||||
private void renderUI() {
|
||||
hBox.setPadding(new Insets(100, 30, 0, 30));
|
||||
hBox.getChildren().add(multiplayerGameList);
|
||||
@ -107,12 +145,17 @@ public class LobbyScene extends BaseScene {
|
||||
spacer.setWidth(gameWindow.getWidth() * 0.02);
|
||||
hBox.getChildren().add(spacer);
|
||||
|
||||
hBox.getChildren().add(lobyInterface);
|
||||
lobyInterface.getStyleClass().add("multiplayer-game-list");
|
||||
lobyInterface.setMaxHeight(gameWindow.getHeight() * 0.8);
|
||||
lobyInterface.setMinWidth(gameWindow.getWidth() * 0.5);
|
||||
hBox.getChildren().add(lobbyInterface);
|
||||
lobbyInterface.getStyleClass().add("multiplayer-game-list");
|
||||
lobbyInterface.setMaxHeight(gameWindow.getHeight() * 0.8);
|
||||
lobbyInterface.setMinWidth(gameWindow.getWidth() * 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles network messages related to multiplayer game listings.
|
||||
*
|
||||
* @param response The network message received.
|
||||
*/
|
||||
private void networkHandler(String response) {
|
||||
try {
|
||||
logger.info("Message received");
|
||||
@ -131,6 +174,9 @@ public class LobbyScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to list available multiplayer games and schedules the next send.
|
||||
*/
|
||||
private void senderLoop() {
|
||||
communicator.send("LIST");
|
||||
if (!executor.isShutdown()) {
|
||||
@ -138,6 +184,11 @@ public class LobbyScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the selection of a multiplayer game channel.
|
||||
*
|
||||
* @param name The name of the selected channel.
|
||||
*/
|
||||
private void handleChannelSelected(String name) {
|
||||
logger.info("Joining channel: " + name);
|
||||
gameWindow.getMultiplayerGame().joinGame(name);
|
||||
|
||||
@ -10,6 +10,7 @@ import javafx.scene.text.Text;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.App;
|
||||
import uk.ac.soton.comp1206.effects.SoundPlayer;
|
||||
import uk.ac.soton.comp1206.ui.GamePane;
|
||||
import uk.ac.soton.comp1206.ui.GameWindow;
|
||||
|
||||
@ -90,10 +91,7 @@ public class MenuScene extends BaseScene {
|
||||
leaderboardButton.setFocusTraversable(false);
|
||||
menuBox.getChildren().add(leaderboardButton);
|
||||
|
||||
var settingsButton = new Button("Settings");
|
||||
settingsButton.getStyleClass().add("menu-item");
|
||||
settingsButton.setFocusTraversable(false);
|
||||
menuBox.getChildren().add(settingsButton);
|
||||
|
||||
|
||||
var quitButton = new Button("Quit");
|
||||
quitButton.getStyleClass().add("menu-item");
|
||||
@ -119,7 +117,8 @@ public class MenuScene extends BaseScene {
|
||||
*/
|
||||
@Override
|
||||
public void initialise() {
|
||||
|
||||
SoundPlayer.stopBackgroundMusic();
|
||||
SoundPlayer.playBackgroundMusic("/music/menu.mp3");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,14 +1,24 @@
|
||||
package uk.ac.soton.comp1206.scene;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.network.Communicator;
|
||||
import uk.ac.soton.comp1206.ui.GameWindow;
|
||||
|
||||
public class MultiPlayerScene extends ChallengeScene {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MultiPlayerScene.class);
|
||||
private Communicator communicator;
|
||||
|
||||
|
||||
|
||||
public MultiPlayerScene(GameWindow gameWindow) {
|
||||
super(gameWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialise() {
|
||||
this.communicator = this.gameWindow.getCommunicator();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -27,12 +27,11 @@ public class ScoresScene extends BaseScene {
|
||||
|
||||
Logger logger = LogManager.getLogger(ScoresScene.class);
|
||||
|
||||
private SimpleListProperty<Pair<String, Integer>> localScores = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private SimpleListProperty<Pair<String, Integer>> onlineScores = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private Leaderboard localLeaderboard = new Leaderboard(localScores);
|
||||
private Leaderboard onlineLeaderboard = new Leaderboard(onlineScores);
|
||||
private Boolean gameOver = false;
|
||||
|
||||
private SimpleListProperty<Pair<String, Integer>> localScores = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private SimpleListProperty<Pair<String, Integer>> onlineScores = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private Leaderboard localLeaderboard = new Leaderboard(localScores);
|
||||
private Leaderboard onlineLeaderboard = new Leaderboard(onlineScores);
|
||||
private Boolean gameOver = false;
|
||||
|
||||
/**
|
||||
* Create a new scene, passing in the GameWindow the scene will be displayed in
|
||||
@ -43,14 +42,29 @@ public class ScoresScene extends BaseScene {
|
||||
super(gameWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* initialise
|
||||
*/
|
||||
@Override
|
||||
public void initialise() {
|
||||
loadLocalScores();
|
||||
this.onlineScores = this.getOnlineScores();
|
||||
this.scene.setOnKeyPressed(e -> this.gameWindow.startMenu());
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* load local scores
|
||||
*/
|
||||
private void loadLocalScores() {
|
||||
ObservableList<Pair<String, Integer>> scores = this.gameWindow.getLocalStorage().getTopScores();
|
||||
localScores.set(scores);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* build layout
|
||||
*/
|
||||
@Override
|
||||
public void build() {
|
||||
logger.info("Building " + this.getClass().getName());
|
||||
@ -103,19 +117,20 @@ public class ScoresScene extends BaseScene {
|
||||
var onlineScoreHeading = new Text("Online Scores");
|
||||
onlineScoreHeading.getStyleClass().add("heading");
|
||||
onlineScoreBox.getChildren().add(onlineScoreHeading);
|
||||
leaderboardBox.setAlignment(Pos.CENTER);
|
||||
|
||||
|
||||
onlineScoreBox.getChildren().add(onlineLeaderboard);
|
||||
onlineLeaderboard.setMinHeight(gameWindow.getHeight() / 4);
|
||||
onlineLeaderboard.setAlignment(Pos.CENTER);
|
||||
|
||||
|
||||
|
||||
localLeaderboard.setAlignment(Pos.CENTER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches online scores from the server
|
||||
* @return
|
||||
* Fetch online scores
|
||||
* @return online scores
|
||||
*/
|
||||
private SimpleListProperty<Pair<String, Integer>> getOnlineScores() {
|
||||
Communicator communicator = gameWindow.getCommunicator();
|
||||
@ -137,15 +152,18 @@ public class ScoresScene extends BaseScene {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a string of high scores and converts it
|
||||
* parse the string format of the online scores
|
||||
* @param scoresString
|
||||
* @return SimpleListProperty of high scores
|
||||
* @param highScoresList
|
||||
* @return
|
||||
*/
|
||||
private SimpleListProperty<Pair<String, Integer>> parseHighScores(String scoresString, SimpleListProperty<Pair<String, Integer>> highScoresList) {
|
||||
|
||||
|
||||
String[] lines = scoresString.split("\n");
|
||||
for (String line : lines) {
|
||||
String[] parts = line.split(":");
|
||||
@ -157,8 +175,13 @@ public class ScoresScene extends BaseScene {
|
||||
}
|
||||
|
||||
return highScoresList;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* setup as game over
|
||||
* @param gameOver
|
||||
*/
|
||||
public void setGameOver(boolean gameOver) {
|
||||
this.gameOver = gameOver;
|
||||
}
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
package uk.ac.soton.comp1206.state;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.network.Communicator;
|
||||
import uk.ac.soton.comp1206.scene.LobbyScene;
|
||||
|
||||
public class MultiplayerGame {
|
||||
private StringProperty name;
|
||||
private ObservableList<String> players;
|
||||
private Communicator communicator;
|
||||
Logger logger = LogManager.getLogger(MultiplayerGame.class);
|
||||
|
||||
public MultiplayerGame(Communicator communicator) {
|
||||
this.communicator = communicator;
|
||||
name = new SimpleStringProperty();
|
||||
players = FXCollections.observableArrayList();
|
||||
|
||||
}
|
||||
|
||||
public void networkHandler(String response) {
|
||||
try {
|
||||
logger.info("Message received");
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to process multiplayer games: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public void joinGame(String name) {
|
||||
this.communicator.send("JOIN " + name);
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ObservableList<String> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public void addPlayer(String player) {
|
||||
players.add(player);
|
||||
}
|
||||
|
||||
public void leave() {
|
||||
communicator.send("PART");
|
||||
name.set("");
|
||||
}
|
||||
|
||||
public void sendMessage(String message) {
|
||||
communicator.send("MSG " + message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
106
src/main/java/uk/ac/soton/comp1206/tools/LocalStorage.java
Normal file
106
src/main/java/uk/ac/soton/comp1206/tools/LocalStorage.java
Normal file
@ -0,0 +1,106 @@
|
||||
package uk.ac.soton.comp1206.tools;
|
||||
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.util.Pair;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* This class manages local storage of top scores, ensuring persistence and initialization of storage file.
|
||||
*/
|
||||
public class LocalStorage {
|
||||
|
||||
private SimpleListProperty<Pair<String, Integer>> topScores;
|
||||
private static final String SCORES_FILE_NAME = "topScores.txt";
|
||||
private Path scoresFilePath;
|
||||
|
||||
/**
|
||||
* Constructor for LocalStorage initializes an empty list of top scores and sets up the file path.
|
||||
*/
|
||||
public LocalStorage() {
|
||||
scoresFilePath = Paths.get(SCORES_FILE_NAME);
|
||||
ObservableList<Pair<String, Integer>> observableList = FXCollections.observableArrayList();
|
||||
this.topScores = new SimpleListProperty<>(observableList);
|
||||
initFile();
|
||||
loadScores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the scores file is created if it does not exist.
|
||||
*/
|
||||
private void initFile() {
|
||||
try {
|
||||
if (Files.notExists(scoresFilePath)) {
|
||||
Files.createFile(scoresFilePath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new score to the list of top scores and saves the updated list to a file.
|
||||
* @param playerName The name of the player
|
||||
* @param score the score to add
|
||||
*/
|
||||
public void addScore(String playerName, int score) {
|
||||
this.topScores.add(new Pair<>(playerName, score));
|
||||
saveScores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current list of top scores to a text file.
|
||||
*/
|
||||
public void saveScores() {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(scoresFilePath)) {
|
||||
for (Pair<String, Integer> score : topScores.get()) {
|
||||
writer.write(score.getKey() + ":" + score.getValue());
|
||||
writer.newLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads top scores from a text file.
|
||||
*/
|
||||
private void loadScores() {
|
||||
List<Pair<String, Integer>> scores = new ArrayList<>();
|
||||
try (Scanner scanner = new Scanner(scoresFilePath.toFile())) {
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
String[] parts = line.split(":");
|
||||
if (parts.length == 2) {
|
||||
String name = parts[0];
|
||||
Integer score = Integer.parseInt(parts[1]);
|
||||
scores.add(new Pair<>(name, score));
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException | NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
while (scores.size() < 10) {
|
||||
scores.add(new Pair<>("", null));
|
||||
}
|
||||
this.topScores.set(FXCollections.observableArrayList(scores));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of top scores.
|
||||
* @return an observable list of top scores
|
||||
*/
|
||||
public ObservableList<Pair<String, Integer>> getTopScores() {
|
||||
return this.topScores.get();
|
||||
}
|
||||
}
|
||||
149
src/main/java/uk/ac/soton/comp1206/tools/MultiplayerGame.java
Normal file
149
src/main/java/uk/ac/soton/comp1206/tools/MultiplayerGame.java
Normal file
@ -0,0 +1,149 @@
|
||||
package uk.ac.soton.comp1206.tools;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.network.Communicator;
|
||||
|
||||
public class MultiplayerGame {
|
||||
private StringProperty name;
|
||||
private ObservableList<String> players;
|
||||
private Communicator communicator;
|
||||
private BooleanProperty isHost = new SimpleBooleanProperty(false);
|
||||
private ObservableList<String> msg = FXCollections.observableArrayList();
|
||||
Logger logger = LogManager.getLogger(MultiplayerGame.class);
|
||||
|
||||
public MultiplayerGame(Communicator communicator) {
|
||||
this.communicator = communicator;
|
||||
name = new SimpleStringProperty("");
|
||||
players = FXCollections.observableArrayList();
|
||||
isHost.set(false);
|
||||
}
|
||||
|
||||
public void networkHandler(String response) {
|
||||
try {
|
||||
logger.info("Network response: " + response);
|
||||
if (response.startsWith("USERS")) {
|
||||
addPlayers(response.replace("USERS", "").trim());
|
||||
} else if (response.startsWith("NICK")) {
|
||||
handleNicknameChange(response.replace("NICK ", ""));
|
||||
} else if (response.startsWith("HOST")) {
|
||||
isHost.set(true);
|
||||
} else if (response.startsWith("PARTED")) {
|
||||
handlePart();
|
||||
} else if (response.startsWith("ERROR")) {
|
||||
logger.error("Error received: " + response.replace("ERROR ", ""));
|
||||
} else if (response.startsWith("MSG")) {
|
||||
handleMSG(response.replace("MSG ", "".trim()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to process multiplayer games: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMSG(String msg) {
|
||||
Platform.runLater(() -> {
|
||||
logger.info("Recv chat message");
|
||||
String[] parts = msg.split(":", 2);
|
||||
getMessages().add("[" + parts[0] + "] " + parts[1]);
|
||||
});
|
||||
}
|
||||
|
||||
private void handleNicknameChange(String nickname) {
|
||||
Platform.runLater(() -> {
|
||||
|
||||
});
|
||||
logger.info("Nickname changed to: " + nickname);
|
||||
}
|
||||
|
||||
private void handlePart() {
|
||||
Platform.runLater(() -> {
|
||||
name.set("");
|
||||
players.clear();
|
||||
isHost.set(false);
|
||||
msg.clear();
|
||||
});
|
||||
logger.info("Left the channel");
|
||||
}
|
||||
|
||||
private void addPlayers(String playerString) {
|
||||
if (playerString.isEmpty()) {
|
||||
logger.info("No players received in the string");
|
||||
return;
|
||||
}
|
||||
String[] playerNames = playerString.split("\n");
|
||||
Platform.runLater(() -> {
|
||||
players.clear();
|
||||
for (String name : playerNames) {
|
||||
if (!name.trim().isEmpty()) {
|
||||
players.add(name.trim());
|
||||
}
|
||||
}
|
||||
logger.info("Players added: " + players);
|
||||
});
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ObservableList<String> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public BooleanProperty isHostProperty() {
|
||||
return isHost;
|
||||
}
|
||||
|
||||
public void joinGame(String name) {
|
||||
communicator.send("JOIN " + name);
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
public void createGame(String name){
|
||||
communicator.send("CREATE " + name);
|
||||
this.name.set(name);
|
||||
isHost.set(true);
|
||||
}
|
||||
|
||||
public void leave() {
|
||||
communicator.send("PART");
|
||||
handlePart();
|
||||
}
|
||||
|
||||
public void sendMessage(String message) {
|
||||
communicator.send("MSG " + message);
|
||||
}
|
||||
|
||||
public void changeNickname(String newNickname) {
|
||||
communicator.send("NICK " + newNickname);
|
||||
}
|
||||
|
||||
public ObservableList getMessages() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void startGame() {
|
||||
if (isHost.get()) {
|
||||
communicator.send("START");
|
||||
} else {
|
||||
logger.error("Attempt to start a game without host privileges");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,9 +9,11 @@ import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.ac.soton.comp1206.App;
|
||||
import uk.ac.soton.comp1206.effects.SoundPlayer;
|
||||
import uk.ac.soton.comp1206.network.Communicator;
|
||||
import uk.ac.soton.comp1206.scene.*;
|
||||
import uk.ac.soton.comp1206.state.MultiplayerGame;
|
||||
import uk.ac.soton.comp1206.tools.LocalStorage;
|
||||
import uk.ac.soton.comp1206.tools.MultiplayerGame;
|
||||
|
||||
/**
|
||||
* The GameWindow is the single window for the game where everything takes place. To move between screens in the game,
|
||||
@ -33,7 +35,11 @@ public class GameWindow {
|
||||
private Scene scene;
|
||||
|
||||
final Communicator communicator;
|
||||
|
||||
private MultiplayerGame multiplayerGame;
|
||||
|
||||
private LocalStorage localStorage;
|
||||
|
||||
/**
|
||||
* Create a new GameWindow attached to the given stage with the specified width and height
|
||||
* @param stage stage
|
||||
@ -63,6 +69,12 @@ public class GameWindow {
|
||||
|
||||
//Set up the multiplayer handler
|
||||
multiplayerGame = new MultiplayerGame(communicator);
|
||||
|
||||
//Setup local storage
|
||||
localStorage = new LocalStorage();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,4 +200,8 @@ public class GameWindow {
|
||||
public MultiplayerGame getMultiplayerGame() {
|
||||
return multiplayerGame;
|
||||
}
|
||||
|
||||
public LocalStorage getLocalStorage() {
|
||||
return localStorage;
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
-fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1);
|
||||
}
|
||||
|
||||
|
||||
.big-heading {
|
||||
-fx-fill: white;
|
||||
-fx-font-family: 'Orbitron';
|
||||
@ -229,6 +230,15 @@ TextField {
|
||||
-fx-background-color: none;
|
||||
}
|
||||
|
||||
.small-button {
|
||||
-fx-text-fill: white;
|
||||
-fx-font-family: 'Orbitron';
|
||||
-fx-font-size: 16px;
|
||||
-fx-font-weight: 700;
|
||||
-fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1);
|
||||
-fx-background-color: none;
|
||||
}
|
||||
|
||||
.channel-item {
|
||||
-fx-text-fill: white;
|
||||
-fx-font-family: 'Orbitron';
|
||||
@ -238,6 +248,11 @@ TextField {
|
||||
-fx-background-color: none;
|
||||
}
|
||||
|
||||
.message-box {
|
||||
-fx-border-color: white;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.multiplayer-game-list {
|
||||
-fx-background-color: rgba(0, 0, 0, 0.7);
|
||||
-fx-background-radius: 5px;
|
||||
@ -250,4 +265,50 @@ TextField {
|
||||
|
||||
.title-image {
|
||||
-fx-max-width: 100;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.messages-list, .players-list {
|
||||
-fx-font-family: 'Orbitron';
|
||||
-fx-background-color: rgba(0, 0, 0, 0.7);
|
||||
-fx-background-insets: 5;
|
||||
-fx-border-width: 1;
|
||||
-fx-border-insets: 5;
|
||||
-fx-padding: 10px;
|
||||
}
|
||||
|
||||
.player-item {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 6px;
|
||||
-fx-font-family: 'Orbitron';
|
||||
-fx-font-size: 16px;
|
||||
-fx-text-fill: Yellow;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
-fx-background-color: transparent;;
|
||||
-fx-font-family: 'Orbitron';
|
||||
-fx-font-size: 12px;
|
||||
-fx-text-fill: black;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 2px;
|
||||
-fx-font-family: 'Orbitron';
|
||||
}
|
||||
|
||||
|
||||
|
||||
.message-input {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.5);
|
||||
-fx-text-fill: #FFFFFF;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
-fx-background-color: #334499;
|
||||
-fx-text-fill: #FFFFFF;
|
||||
-fx-font-weight: bold;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
0
src/main/resources/topScores.txt
Normal file
0
src/main/resources/topScores.txt
Normal file
Loading…
x
Reference in New Issue
Block a user