[FEAT] Show other players' boards in multiplayer
This commit is contained in:
@@ -70,7 +70,8 @@ public class Chat extends VBox {
|
|||||||
messagesContainer.setContent(messages);
|
messagesContainer.setContent(messages);
|
||||||
messagesContainer.setFitToWidth(true);
|
messagesContainer.setFitToWidth(true);
|
||||||
messagesContainer.setFitToHeight(true);
|
messagesContainer.setFitToHeight(true);
|
||||||
messagesContainer.setPrefHeight(320);
|
messagesContainer.setPrefHeight(280);
|
||||||
|
messagesContainer.setMaxHeight(280);
|
||||||
messagesContainer.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
messagesContainer.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
messagesContainer.setVvalue(1.0);
|
messagesContainer.setVvalue(1.0);
|
||||||
messagesContainer.getStyleClass().add("scroller");
|
messagesContainer.getStyleClass().add("scroller");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package uk.mgrove.ac.soton.comp1206.component;
|
package uk.mgrove.ac.soton.comp1206.component;
|
||||||
|
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
@@ -199,6 +201,14 @@ public class GameBoard extends GridPane {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get grid value property
|
||||||
|
* @return properties for value of block at given location in grid
|
||||||
|
*/
|
||||||
|
public IntegerProperty getGridProperty(int x, int y) {
|
||||||
|
return grid.getGridProperty(x,y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the listener to handle an event when a block is clicked
|
* Set the listener to handle an event when a block is clicked
|
||||||
* @param listener listener to add
|
* @param listener listener to add
|
||||||
@@ -305,4 +315,20 @@ public class GameBoard extends GridPane {
|
|||||||
currentPiece = newPiece;
|
currentPiece = newPiece;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current game piece
|
||||||
|
* @return current piece
|
||||||
|
*/
|
||||||
|
public GamePiece getCurrentPiece() {
|
||||||
|
return currentPiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether board should be focussable
|
||||||
|
* @param enableFocus whether board should be focussable
|
||||||
|
*/
|
||||||
|
public void enableFocus(boolean enableFocus) {
|
||||||
|
this.enableFocus = enableFocus;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class Leaderboard extends ScoresList {
|
|||||||
title = new Text(titleText);
|
title = new Text(titleText);
|
||||||
title.getStyleClass().add("heading");
|
title.getStyleClass().add("heading");
|
||||||
getChildren().add(title);
|
getChildren().add(title);
|
||||||
|
setSpacing(4);
|
||||||
|
|
||||||
reveal();
|
reveal();
|
||||||
scores.addListener((ListChangeListener<? super Pair<String,Pair<Integer, Integer>>>) change -> Platform.runLater(() -> {
|
scores.addListener((ListChangeListener<? super Pair<String,Pair<Integer, Integer>>>) change -> Platform.runLater(() -> {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package uk.mgrove.ac.soton.comp1206.event;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import uk.mgrove.ac.soton.comp1206.component.GameBoard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for player game boards
|
||||||
|
*/
|
||||||
|
public interface PlayerGameBoardListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action the listener with details of the board
|
||||||
|
* @param username player's username
|
||||||
|
* @param gridProperties properties for board grid contents
|
||||||
|
*/
|
||||||
|
void add(String username, SimpleIntegerProperty[][] gridProperties);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import javafx.application.Platform;
|
|||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
import javafx.beans.property.SimpleListProperty;
|
||||||
|
import javafx.beans.property.SimpleMapProperty;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -96,7 +97,12 @@ public class Game {
|
|||||||
/**
|
/**
|
||||||
* Listener for showing scores scene
|
* Listener for showing scores scene
|
||||||
*/
|
*/
|
||||||
private ShowScoresSceneListener showScoresListener;
|
protected ShowScoresSceneListener showScoresListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for when new player board is added
|
||||||
|
*/
|
||||||
|
protected PlayerGameBoardListener playerBoardAddedListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new game with the specified rows and columns. Creates a corresponding grid model.
|
* Create a new game with the specified rows and columns. Creates a corresponding grid model.
|
||||||
@@ -167,7 +173,7 @@ public class Game {
|
|||||||
/**
|
/**
|
||||||
* Game loop - ongoing time-based functionality
|
* Game loop - ongoing time-based functionality
|
||||||
*/
|
*/
|
||||||
private void gameLoop() {
|
protected void gameLoop() {
|
||||||
logger.info("Executing game loop");
|
logger.info("Executing game loop");
|
||||||
setLives(getLives() - 1);
|
setLives(getLives() - 1);
|
||||||
nextPiece();
|
nextPiece();
|
||||||
@@ -512,6 +518,22 @@ public class Game {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player boards property - always null in this class
|
||||||
|
* @return property for player boards
|
||||||
|
*/
|
||||||
|
public SimpleMapProperty<String, SimpleIntegerProperty[][]> playerBoardsProperty() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set listener for when new player board is added
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void setOnPlayerBoardAdded(PlayerGameBoardListener listener) {
|
||||||
|
this.playerBoardAddedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set listener for game failure - but in this class does nothing
|
* Set listener for game failure - but in this class does nothing
|
||||||
* @param listener listener to set
|
* @param listener listener to set
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ public class Grid {
|
|||||||
* @param y y-coordinate of piece centre
|
* @param y y-coordinate of piece centre
|
||||||
*/
|
*/
|
||||||
public void previewPiece(GamePiece piece, int x, int y) {
|
public void previewPiece(GamePiece piece, int x, int y) {
|
||||||
if (!canPlayPiece(piece, x, y)) return;
|
if (piece == null || !canPlayPiece(piece, x, y)) return;
|
||||||
|
|
||||||
logger.info("Previewing piece {} at {},{}", piece, x, y);
|
logger.info("Previewing piece {} at {},{}", piece, x, y);
|
||||||
int value = piece.getValue();
|
int value = piece.getValue();
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
package uk.mgrove.ac.soton.comp1206.game;
|
package uk.mgrove.ac.soton.comp1206.game;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
import javafx.beans.property.SimpleListProperty;
|
||||||
|
import javafx.beans.property.SimpleMapProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.text.TextAlignment;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import uk.mgrove.ac.soton.comp1206.event.GameFailureListener;
|
import uk.mgrove.ac.soton.comp1206.event.GameFailureListener;
|
||||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||||
|
import uk.mgrove.ac.soton.comp1206.util.Multimedia;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -33,15 +42,20 @@ public class MultiplayerGame extends Game {
|
|||||||
private final BlockingQueue<GamePiece> pieceQueue = new LinkedBlockingQueue<>();
|
private final BlockingQueue<GamePiece> pieceQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scores for the leaderboard
|
* Scores for the leaderboard, with structure username, score, lives
|
||||||
*/
|
*/
|
||||||
private final SimpleListProperty<Pair<String,Pair<Integer,Integer>>> leaderboardScores = new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>()));
|
private final SimpleListProperty<Pair<String,Pair<Integer,Integer>>> leaderboardScores = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for game failing
|
* Listener for game failing
|
||||||
*/
|
*/
|
||||||
private GameFailureListener gameFailureListener;
|
private GameFailureListener gameFailureListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of usernames to player boards
|
||||||
|
*/
|
||||||
|
private final SimpleMapProperty<String, SimpleIntegerProperty[][]> playerBoards = new SimpleMapProperty<>(FXCollections.observableHashMap());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new game with the specified rows and columns. Creates a corresponding grid model.
|
* Create a new game with the specified rows and columns. Creates a corresponding grid model.
|
||||||
*
|
*
|
||||||
@@ -125,11 +139,34 @@ public class MultiplayerGame extends Game {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (message.startsWith("BOARD ")) {
|
||||||
|
var info = message.replaceFirst("BOARD ", "").split(":");
|
||||||
|
var gridValues = info[1].split(" ");
|
||||||
|
if (playerBoards.containsKey(info[0])) {
|
||||||
|
logger.info("Updating board for: {}", info[0]);
|
||||||
|
for (var x = 0; x < playerBoards.get(info[0]).length; x++) {
|
||||||
|
for (var y = 0; y < playerBoards.get(info[0])[0].length; y++) {
|
||||||
|
playerBoards.get(info[0])[x][y].set(Integer.parseInt(gridValues[playerBoards.get(info[0])[0].length * x + y]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Adding board for: {}", info[0]);
|
||||||
|
var dimensions = (int) Math.sqrt(gridValues.length);
|
||||||
|
SimpleIntegerProperty[][] newProperties = new SimpleIntegerProperty[dimensions][dimensions];
|
||||||
|
for (var x = 0; x < dimensions; x++) {
|
||||||
|
for (var y = 0; y < dimensions; y++) {
|
||||||
|
newProperties[x][y] = new SimpleIntegerProperty(Integer.parseInt(gridValues[grid.getCols() * x + y]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playerBoards.put(info[0], newProperties);
|
||||||
|
if (playerBoardAddedListener != null) playerBoardAddedListener.add(info[0], newProperties);
|
||||||
|
}
|
||||||
|
logger.info("Player boards: {}", playerBoards.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse high scores from a string to list property
|
* Parse high scores from a string to list property, with structure username, score, lives
|
||||||
* @param data string to parse from
|
* @param data string to parse from
|
||||||
* @return list property containing scores loaded
|
* @return list property containing scores loaded
|
||||||
*/
|
*/
|
||||||
@@ -141,7 +178,7 @@ public class MultiplayerGame extends Game {
|
|||||||
|
|
||||||
while (scanner.hasNextLine()) {
|
while (scanner.hasNextLine()) {
|
||||||
var line = scanner.nextLine();
|
var line = scanner.nextLine();
|
||||||
if (line.matches("^.+:[0-9]+:([0-9]+|DEAD)$")) {
|
if (line.matches("^.+:[0-9]+:((-?[0-9]+)|DEAD)$")) {
|
||||||
var info = line.split(":");
|
var info = line.split(":");
|
||||||
var lives = info[2].equals("DEAD") ? -1 : Integer.parseInt(info[2]);
|
var lives = info[2].equals("DEAD") ? -1 : Integer.parseInt(info[2]);
|
||||||
scores.add(new Pair<>(info[0], new Pair<>(Integer.valueOf(info[1]), lives)));
|
scores.add(new Pair<>(info[0], new Pair<>(Integer.valueOf(info[1]), lives)));
|
||||||
@@ -162,15 +199,41 @@ public class MultiplayerGame extends Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End game
|
* Get the player boards property
|
||||||
|
* @return property for player boards
|
||||||
*/
|
*/
|
||||||
@Override
|
public SimpleMapProperty<String, SimpleIntegerProperty[][]> playerBoardsProperty() {
|
||||||
public void endGame() {
|
return playerBoards;
|
||||||
super.endGame();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify server that player is dead
|
||||||
|
*/
|
||||||
|
private void die() {
|
||||||
logger.info("Sending die message to server");
|
logger.info("Sending die message to server");
|
||||||
communicator.send("DIE");
|
communicator.send("DIE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Game loop - ongoing time-based functionality
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void gameLoop() {
|
||||||
|
logger.info("Executing game loop");
|
||||||
|
setLives(getLives() - 1);
|
||||||
|
nextPiece();
|
||||||
|
setMultiplier(1);
|
||||||
|
Multimedia.playAudio("sounds/lifelose.wav");
|
||||||
|
|
||||||
|
if(getLives() < 0) {
|
||||||
|
die();
|
||||||
|
logger.info("Ending game");
|
||||||
|
endGame();
|
||||||
|
if (showScoresListener != null) Platform.runLater(() -> showScoresListener.show(this));
|
||||||
|
}
|
||||||
|
else scheduleGameLoop();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the score, multiplier, and level depending on the number of lines and blocks that have been cleared
|
* Update the score, multiplier, and level depending on the number of lines and blocks that have been cleared
|
||||||
* @param lines number of lines cleared
|
* @param lines number of lines cleared
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ public class ChallengeScene extends BaseScene {
|
|||||||
*/
|
*/
|
||||||
protected final IntegerProperty highScore = new SimpleIntegerProperty(0);
|
protected final IntegerProperty highScore = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main stack pane for scene
|
||||||
|
*/
|
||||||
|
protected StackPane challengePane;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Single Player challenge scene
|
* Create a new Single Player challenge scene
|
||||||
* @param gameWindow the Game Window
|
* @param gameWindow the Game Window
|
||||||
@@ -98,7 +103,7 @@ public class ChallengeScene extends BaseScene {
|
|||||||
|
|
||||||
root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight());
|
root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight());
|
||||||
|
|
||||||
var challengePane = new StackPane();
|
challengePane = new StackPane();
|
||||||
challengePane.setMaxWidth(gameWindow.getWidth());
|
challengePane.setMaxWidth(gameWindow.getWidth());
|
||||||
challengePane.setMaxHeight(gameWindow.getHeight());
|
challengePane.setMaxHeight(gameWindow.getHeight());
|
||||||
challengePane.getStyleClass().add("menu-background");
|
challengePane.getStyleClass().add("menu-background");
|
||||||
@@ -163,9 +168,21 @@ public class ChallengeScene extends BaseScene {
|
|||||||
switch (event.getCode()) {
|
switch (event.getCode()) {
|
||||||
case ESCAPE -> returnToMenu();
|
case ESCAPE -> returnToMenu();
|
||||||
case ENTER, X -> game.dropPiece(board.getXFocus(),board.getYFocus());
|
case ENTER, X -> game.dropPiece(board.getXFocus(),board.getYFocus());
|
||||||
case SPACE, R -> game.swapPieces();
|
case SPACE, R -> {
|
||||||
case OPEN_BRACKET, Q, Z -> game.rotateCurrentPieceAnticlockwise();
|
game.swapPieces();
|
||||||
case CLOSE_BRACKET, E, C -> game.rotateCurrentPiece();
|
game.getGrid().clearPreview();
|
||||||
|
game.getGrid().previewPiece(board.getCurrentPiece(), board.getXFocus(), board.getYFocus());
|
||||||
|
}
|
||||||
|
case OPEN_BRACKET, Q, Z -> {
|
||||||
|
game.rotateCurrentPieceAnticlockwise();
|
||||||
|
game.getGrid().clearPreview();
|
||||||
|
game.getGrid().previewPiece(board.getCurrentPiece(), board.getXFocus(), board.getYFocus());
|
||||||
|
}
|
||||||
|
case CLOSE_BRACKET, E, C -> {
|
||||||
|
game.rotateCurrentPiece();
|
||||||
|
game.getGrid().clearPreview();
|
||||||
|
game.getGrid().previewPiece(board.getCurrentPiece(), board.getXFocus(), board.getYFocus());
|
||||||
|
}
|
||||||
case LEFT, A -> board.changeXFocus(-1);
|
case LEFT, A -> board.changeXFocus(-1);
|
||||||
case RIGHT, D -> board.changeXFocus(1);
|
case RIGHT, D -> board.changeXFocus(1);
|
||||||
case DOWN, S -> board.changeYFocus(1);
|
case DOWN, S -> board.changeYFocus(1);
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ public class LobbyScene extends BaseScene {
|
|||||||
*/
|
*/
|
||||||
private final HBox channelFunctionButtons = new HBox();
|
private final HBox channelFunctionButtons = new HBox();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer to request list of channels from server
|
||||||
|
*/
|
||||||
|
private Timer listChannelsRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new scene, passing in the GameWindow the scene will be displayed in
|
* Create a new scene, passing in the GameWindow the scene will be displayed in
|
||||||
*
|
*
|
||||||
@@ -143,7 +148,7 @@ public class LobbyScene extends BaseScene {
|
|||||||
|
|
||||||
gameWindow.getCommunicator().addListener(this::handleCommunicatorMessage);
|
gameWindow.getCommunicator().addListener(this::handleCommunicatorMessage);
|
||||||
|
|
||||||
var listChannelsRequest = new Timer("Request list of channels from communicator");
|
listChannelsRequest = new Timer("Request list of channels from communicator");
|
||||||
listChannelsRequest.schedule(new TimerTask() {
|
listChannelsRequest.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -227,14 +232,16 @@ public class LobbyScene extends BaseScene {
|
|||||||
});
|
});
|
||||||
} else if (message.equals("HOST")) {
|
} else if (message.equals("HOST")) {
|
||||||
logger.info("User is host of current channel");
|
logger.info("User is host of current channel");
|
||||||
isChannelHost = true;
|
if (!isChannelHost) {
|
||||||
Platform.runLater(() -> {
|
isChannelHost = true;
|
||||||
logger.info("Current channel host status: {}", isChannelHost);
|
Platform.runLater(() -> {
|
||||||
var startGame = new Text("Start game");
|
logger.info("Current channel host status: {}", isChannelHost);
|
||||||
startGame.getStyleClass().add("channelItem");
|
var startGame = new Text("Start game");
|
||||||
startGame.setOnMouseClicked((event) -> gameWindow.getCommunicator().send("START"));
|
startGame.getStyleClass().add("channelItem");
|
||||||
channelFunctionButtons.getChildren().add(0, startGame);
|
startGame.setOnMouseClicked((event) -> gameWindow.getCommunicator().send("START"));
|
||||||
});
|
channelFunctionButtons.getChildren().add(0, startGame);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (message.matches("^NICK .+:.+$")) {
|
} else if (message.matches("^NICK .+:.+$")) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
var usernames = message.replaceFirst("NICK ", "").split(":");
|
var usernames = message.replaceFirst("NICK ", "").split(":");
|
||||||
@@ -255,7 +262,10 @@ public class LobbyScene extends BaseScene {
|
|||||||
});
|
});
|
||||||
} else if (message.equals("START")) {
|
} else if (message.equals("START")) {
|
||||||
logger.info("Starting game");
|
logger.info("Starting game");
|
||||||
Platform.runLater(gameWindow::startMultiplayerGame);
|
Platform.runLater(() -> {
|
||||||
|
listChannelsRequest.cancel();
|
||||||
|
gameWindow.startMultiplayerGame();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
package uk.mgrove.ac.soton.comp1206.scene;
|
package uk.mgrove.ac.soton.comp1206.scene;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.MapProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleMapProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.text.TextAlignment;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import uk.mgrove.ac.soton.comp1206.component.*;
|
import uk.mgrove.ac.soton.comp1206.component.*;
|
||||||
@@ -30,6 +37,16 @@ public class MultiplayerScene extends ChallengeScene {
|
|||||||
*/
|
*/
|
||||||
private Leaderboard leaderboard;
|
private Leaderboard leaderboard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other players' live boards
|
||||||
|
*/
|
||||||
|
private MapProperty<String, SimpleIntegerProperty[][]> playerBoards = new SimpleMapProperty<>(FXCollections.observableHashMap());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grid of other players' live boards
|
||||||
|
*/
|
||||||
|
private final GridPane playerBoardGrid = new GridPane();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new multiplayer challenge scene
|
* Create a new multiplayer challenge scene
|
||||||
*
|
*
|
||||||
@@ -50,6 +67,36 @@ public class MultiplayerScene extends ChallengeScene {
|
|||||||
game = new MultiplayerGame(5, 5, gameWindow.getCommunicator());
|
game = new MultiplayerGame(5, 5, gameWindow.getCommunicator());
|
||||||
game.setOnGameFail(() -> Platform.runLater(gameWindow::startMenu));
|
game.setOnGameFail(() -> Platform.runLater(gameWindow::startMenu));
|
||||||
|
|
||||||
|
game.setOnPlayerBoardAdded((username, gridProperties) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var newGameBoard = new GameBoard(gridProperties.length, gridProperties[0].length, 100, 100);
|
||||||
|
newGameBoard.enableFocus(false);
|
||||||
|
for (var x = 0; x < gridProperties.length; x++) {
|
||||||
|
for (var y = 0; y < gridProperties[0].length; y++) {
|
||||||
|
newGameBoard.getGridProperty(x,y).bindBidirectional(gridProperties[x][y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var leaderboardScores = game.leaderboardScoresProperty();
|
||||||
|
|
||||||
|
var boardGridDimensions = (int) Math.sqrt(leaderboardScores.size());
|
||||||
|
var playerIndex = -1;
|
||||||
|
for (var player : leaderboardScores) {
|
||||||
|
playerIndex++;
|
||||||
|
if (player.getKey().equals(username)) break;
|
||||||
|
}
|
||||||
|
var rowIndex = playerIndex / boardGridDimensions;
|
||||||
|
var columnIndex = playerIndex - rowIndex * boardGridDimensions;
|
||||||
|
var newBoardTitle = new Text(username);
|
||||||
|
newBoardTitle.setTextAlignment(TextAlignment.CENTER);
|
||||||
|
newBoardTitle.getStyleClass().add("heading");
|
||||||
|
logger.debug("Creating board for: {} at row index: {}, column index: {}", username, rowIndex, columnIndex);
|
||||||
|
var newBoardContainer = new VBox(newBoardTitle, newGameBoard);
|
||||||
|
newBoardContainer.setAlignment(Pos.CENTER);
|
||||||
|
newBoardContainer.setSpacing(4);
|
||||||
|
playerBoardGrid.add(newBoardContainer, 1, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
gameWindow.getCommunicator().send("SCORES");
|
gameWindow.getCommunicator().send("SCORES");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,12 +110,32 @@ public class MultiplayerScene extends ChallengeScene {
|
|||||||
leftMenu.setSpacing(8);
|
leftMenu.setSpacing(8);
|
||||||
leftMenu.setMaxWidth(200);
|
leftMenu.setMaxWidth(200);
|
||||||
leftMenu.setPrefWidth(200);
|
leftMenu.setPrefWidth(200);
|
||||||
|
var viewOtherBoards = new Text("See others");
|
||||||
|
viewOtherBoards.getStyleClass().add("channelItem");
|
||||||
|
viewOtherBoards.setOnMouseClicked((event) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var returnButton = new Text("Back");
|
||||||
|
returnButton.getStyleClass().add("channelItem");
|
||||||
|
var playerBoardTitle = new Text("All Boards");
|
||||||
|
playerBoardTitle.getStyleClass().add("title");
|
||||||
|
playerBoardTitle.setTextAlignment(TextAlignment.CENTER);
|
||||||
|
var playerBoardGridContainer = new VBox(returnButton, playerBoardTitle, playerBoardGrid);
|
||||||
|
playerBoardGridContainer.setAlignment(Pos.CENTER);
|
||||||
|
playerBoardGridContainer.setFillWidth(true);
|
||||||
|
playerBoardGridContainer.setPrefHeight(Double.MAX_VALUE);
|
||||||
|
playerBoardGridContainer.setSpacing(12);
|
||||||
|
playerBoardGridContainer.getStyleClass().add("overlay");
|
||||||
|
returnButton.setOnMouseClicked((returnEvent) -> Platform.runLater(() -> challengePane.getChildren().remove(playerBoardGridContainer)));
|
||||||
|
playerBoardGrid.setAlignment(Pos.CENTER);
|
||||||
|
challengePane.getChildren().add(playerBoardGridContainer);
|
||||||
|
});
|
||||||
|
});
|
||||||
chat = new Chat(gameWindow.getCommunicator(), true);
|
chat = new Chat(gameWindow.getCommunicator(), true);
|
||||||
chat.setChatFocusTraversable(false);
|
chat.setChatFocusTraversable(false);
|
||||||
leaderboard = new Leaderboard("Leaderboard");
|
leaderboard = new Leaderboard("Leaderboard");
|
||||||
var chatTitle = new Text("Chat");
|
var chatTitle = new Text("Chat");
|
||||||
chatTitle.getStyleClass().add("heading");
|
chatTitle.getStyleClass().add("heading");
|
||||||
leftMenu.getChildren().addAll(leaderboard, new Separator(), chatTitle, chat);
|
leftMenu.getChildren().addAll(viewOtherBoards, leaderboard, new Separator(), chatTitle, chat);
|
||||||
mainPane.setLeft(leftMenu);
|
mainPane.setLeft(leftMenu);
|
||||||
leaderboard.bindLeaderboardScores(game.leaderboardScoresProperty());
|
leaderboard.bindLeaderboardScores(game.leaderboardScoresProperty());
|
||||||
mainPane.requestFocus();
|
mainPane.requestFocus();
|
||||||
|
|||||||
@@ -299,6 +299,7 @@ public class ScoresScene extends BaseScene {
|
|||||||
userNamePrompt.getChildren().addAll(userNameInput, userNameSubmit);
|
userNamePrompt.getChildren().addAll(userNameInput, userNameSubmit);
|
||||||
highScorePromptContainer = new VBox(usernameTitle, userNamePrompt);
|
highScorePromptContainer = new VBox(usernameTitle, userNamePrompt);
|
||||||
highScorePromptContainer.setSpacing(20);
|
highScorePromptContainer.setSpacing(20);
|
||||||
|
highScorePromptContainer.setAlignment(Pos.CENTER);
|
||||||
scoresPane.getChildren().addAll(highScorePromptContainer);
|
scoresPane.getChildren().addAll(highScorePromptContainer);
|
||||||
StackPane.setAlignment(highScorePromptContainer, Pos.CENTER);
|
StackPane.setAlignment(highScorePromptContainer, Pos.CENTER);
|
||||||
}
|
}
|
||||||
@@ -338,17 +339,19 @@ public class ScoresScene extends BaseScene {
|
|||||||
* @param score high score to save
|
* @param score high score to save
|
||||||
*/
|
*/
|
||||||
private void saveHighScore(String username, int score) {
|
private void saveHighScore(String username, int score) {
|
||||||
logger.info("Saving high score: {} for user: {}", score, username);
|
if (!game.getClass().equals(MultiplayerGame.class)) {
|
||||||
|
logger.info("Saving high score: {} for user: {}", score, username);
|
||||||
|
|
||||||
for (var i = 0; i <= localScores.getSize(); i++) {
|
for (var i = 0; i <= localScores.getSize(); i++) {
|
||||||
if (i == localScores.getSize() || localScores.get(i).getValue() < score) {
|
if (i == localScores.getSize() || localScores.get(i).getValue() < score) {
|
||||||
localScores.add(i, new Pair<>(username, score));
|
localScores.add(i, new Pair<>(username, score));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// if score isn't higher than last high score then this shouldn't have been triggered
|
||||||
|
// so while it won't get stored, this isn't an issue
|
||||||
|
writeScores("scores.txt", localScores);
|
||||||
}
|
}
|
||||||
// if score isn't higher than last high score then this shouldn't have been triggered
|
|
||||||
// so while it won't get stored, this isn't an issue
|
|
||||||
writeScores("scores.txt", localScores);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeOnlineScore(String username, int score) {
|
private void writeOnlineScore(String username, int score) {
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
-fx-background-color: black;
|
-fx-background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
-fx-background-color: rgba(0,0,0,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
-fx-fill: white;
|
-fx-fill: white;
|
||||||
-fx-font-family: 'Orbitron';
|
-fx-font-family: 'Orbitron';
|
||||||
@@ -18,6 +22,9 @@ Label {
|
|||||||
.menu-background {
|
.menu-background {
|
||||||
-fx-background-image: url("../images/1.jpg");
|
-fx-background-image: url("../images/1.jpg");
|
||||||
-fx-background-size: cover;
|
-fx-background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderPane {
|
||||||
-fx-padding: 20;
|
-fx-padding: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +202,7 @@ Label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextField, .text-field {
|
TextField, .text-field {
|
||||||
|
-fx-font-family: 'Orbitron';
|
||||||
-fx-border-color: white;
|
-fx-border-color: white;
|
||||||
-fx-border-width: 1px;
|
-fx-border-width: 1px;
|
||||||
-fx-background-color: rgba(0,0,0,0.5);
|
-fx-background-color: rgba(0,0,0,0.5);
|
||||||
|
|||||||
Reference in New Issue
Block a user