[FEAT] Score tracking, time-based gameplay, and countdown indicator bar
This commit is contained in:
@@ -207,7 +207,6 @@ public class GameBlock extends Canvas {
|
||||
public void setFocussed(boolean value) {
|
||||
logger.info("Block at x: {}, y: {} has been set to focus: {}", getX(), getY(), value);
|
||||
isFocussed = value;
|
||||
// TODO: add/remove overlay
|
||||
paint();
|
||||
}
|
||||
|
||||
@@ -228,19 +227,17 @@ public class GameBlock extends Canvas {
|
||||
logger.info("Fading out block at x: {}, y: {}", getX(), getY());
|
||||
|
||||
AnimationTimer timer = new AnimationTimer() {
|
||||
Color color = COLOURS[getValue()];
|
||||
int iterations = 0;
|
||||
private Color color = COLOURS[getValue()];
|
||||
private int iterations = 0;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (iterations < 12) {
|
||||
color = color.deriveColor(0,1,1.2,1);
|
||||
paintColor(color);
|
||||
logger.info("New color brightness is {}", color.getBrightness());
|
||||
if (isFocussed) paintFocusOverlay();
|
||||
} else if (color.getOpacity() > 0.1) {
|
||||
color = color.deriveColor(0,1,1,0.95);
|
||||
paintColor(color);
|
||||
logger.info("New color opacity is {}", color.getOpacity());
|
||||
if (isFocussed) paintFocusOverlay();
|
||||
} else {
|
||||
paintEmpty();
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package uk.mgrove.ac.soton.comp1206.component;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Class for displaying list of scores and associated users
|
||||
*/
|
||||
public class ScoresList extends VBox {
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(ScoresList.class);
|
||||
|
||||
/**
|
||||
* Scores
|
||||
*/
|
||||
private final SimpleListProperty<Pair<String,Integer>> scores;
|
||||
|
||||
/**
|
||||
* Initialise the scores list
|
||||
*/
|
||||
public ScoresList() {
|
||||
logger.info("Building scores list");
|
||||
|
||||
ObservableList<Pair<String,Integer>> observableScoresList = FXCollections.observableArrayList();
|
||||
scores = new SimpleListProperty<>(observableScoresList);
|
||||
|
||||
setOpacity(0);
|
||||
|
||||
scores.addListener((ListChangeListener<? super Pair<String, Integer>>) change -> {
|
||||
logger.info("Detected change in scores list: {}", change);
|
||||
getChildren().clear();
|
||||
for (var pair : scores.get()) {
|
||||
var score = new HBox();
|
||||
score.getChildren().addAll(new Text(pair.getKey()), new Text(pair.getValue().toString()));
|
||||
getChildren().add(score);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind scores to external property
|
||||
* @param scores external property to bind to
|
||||
*/
|
||||
public void bindScores(SimpleListProperty<Pair<String,Integer>> scores) {
|
||||
this.scores.bindBidirectional(scores);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveal the scores
|
||||
*/
|
||||
public void reveal() {
|
||||
logger.info("Revealing scores");
|
||||
FadeTransition fadeInTransition = new FadeTransition(Duration.millis(1000), this);
|
||||
fadeInTransition.setToValue(1);
|
||||
fadeInTransition.play();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package uk.mgrove.ac.soton.comp1206.event;
|
||||
|
||||
/**
|
||||
* Listener for game loop being scheduled
|
||||
*/
|
||||
public interface GameLoopListener {
|
||||
|
||||
/**
|
||||
* Process the game loop being scheduled
|
||||
* @param timerDelay delay until game loop is executed
|
||||
*/
|
||||
public void process(int timerDelay);
|
||||
|
||||
}
|
||||
@@ -4,8 +4,15 @@ import uk.mgrove.ac.soton.comp1206.component.GameBlockCoordinate;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Listener for lines being cleared on the game board
|
||||
*/
|
||||
public interface LineClearedListener {
|
||||
|
||||
/**
|
||||
* Process the line(s) being cleared
|
||||
* @param blockCoordinates coordinates of the blocks being cleared
|
||||
*/
|
||||
public void clearLine(Set<GameBlockCoordinate> blockCoordinates);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package uk.mgrove.ac.soton.comp1206.event;
|
||||
|
||||
import uk.mgrove.ac.soton.comp1206.game.Game;
|
||||
|
||||
/**
|
||||
* Listener for showing the scores scene
|
||||
*/
|
||||
public interface ShowScoresSceneListener {
|
||||
|
||||
/**
|
||||
* Show the scene
|
||||
*/
|
||||
public void show(Game game);
|
||||
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
package uk.mgrove.ac.soton.comp1206.game;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.component.GameBlock;
|
||||
import uk.mgrove.ac.soton.comp1206.component.GameBlockCoordinate;
|
||||
import uk.mgrove.ac.soton.comp1206.event.GameLoopListener;
|
||||
import uk.mgrove.ac.soton.comp1206.event.LineClearedListener;
|
||||
import uk.mgrove.ac.soton.comp1206.event.NextPieceListener;
|
||||
import uk.mgrove.ac.soton.comp1206.event.ShowScoresSceneListener;
|
||||
import uk.mgrove.ac.soton.comp1206.util.Multimedia;
|
||||
|
||||
import java.util.*;
|
||||
@@ -80,6 +83,22 @@ public class Game {
|
||||
*/
|
||||
private LineClearedListener lineClearedListener;
|
||||
|
||||
|
||||
/**
|
||||
* Game timer for time-based functionality
|
||||
*/
|
||||
private Timer gameTimer;
|
||||
|
||||
/**
|
||||
* Listener for game loop being scheduled
|
||||
*/
|
||||
private GameLoopListener gameLoopListener;
|
||||
|
||||
/**
|
||||
* Listener for showing scores scene
|
||||
*/
|
||||
private ShowScoresSceneListener showScoresListener;
|
||||
|
||||
/**
|
||||
* Create a new game with the specified rows and columns. Creates a corresponding grid model.
|
||||
* @param cols number of columns
|
||||
@@ -142,6 +161,55 @@ public class Game {
|
||||
logger.info("Initialising game");
|
||||
followingPiece = spawnPiece();
|
||||
nextPiece();
|
||||
|
||||
scheduleGameLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Game loop - ongoing time-based functionality
|
||||
*/
|
||||
private void gameLoop() {
|
||||
logger.info("Executing game loop");
|
||||
setLives(getLives() - 1);
|
||||
nextPiece();
|
||||
setMultiplier(1);
|
||||
Multimedia.playAudio("sounds/lifelose.wav");
|
||||
|
||||
if(getLives() < 0) {
|
||||
endGame();
|
||||
if (showScoresListener != null) Platform.runLater(() -> showScoresListener.show(this));
|
||||
}
|
||||
else scheduleGameLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start new game loop
|
||||
*/
|
||||
private void scheduleGameLoop() {
|
||||
logger.info("Scheduling game loop");
|
||||
|
||||
if (gameTimer != null) gameTimer.cancel();
|
||||
|
||||
TimerTask gameTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
gameLoop();
|
||||
}
|
||||
};
|
||||
gameTimer = new Timer("Timer");
|
||||
var timerDelay = getTimerDelay();
|
||||
gameTimer.schedule(gameTimerTask, timerDelay);
|
||||
|
||||
if (gameLoopListener != null) gameLoopListener.process(timerDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* End game
|
||||
*/
|
||||
public void endGame() {
|
||||
if (gameTimer != null) gameTimer.cancel();
|
||||
Multimedia.playAudio("sounds/explode.wav");
|
||||
// TODO: do processing to end game - switch to ScoresScene
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +234,6 @@ public class Game {
|
||||
logger.info("Playing piece at x: {}, y: {}", x, y);
|
||||
grid.playPiece(currentPiece,x,y);
|
||||
afterPiece();
|
||||
nextPiece();
|
||||
|
||||
Multimedia.playAudio("sounds/place.wav");
|
||||
} else {
|
||||
@@ -236,6 +303,9 @@ public class Game {
|
||||
for (var block : blocksToRemove) {
|
||||
block.set(0);
|
||||
}
|
||||
|
||||
nextPiece();
|
||||
scheduleGameLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,6 +350,14 @@ public class Game {
|
||||
if (nextPieceListener != null) nextPieceListener.nextPiece(currentPiece, followingPiece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer delay - amount of time before timer should expire
|
||||
* @return timer delay
|
||||
*/
|
||||
private int getTimerDelay() {
|
||||
return Math.max(2500, 12000 - 500 * getLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the grid model inside this game representing the game state of the board
|
||||
* @return game grid model
|
||||
@@ -412,4 +490,20 @@ public class Game {
|
||||
lineClearedListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener for game loop being executed
|
||||
* @param listener listener to set
|
||||
*/
|
||||
public void setOnGameLoop(GameLoopListener listener) {
|
||||
gameLoopListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener for showing scores scene
|
||||
* @param listener listener to set
|
||||
*/
|
||||
public void setShowScoresListener(ShowScoresSceneListener listener) {
|
||||
showScoresListener = listener;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
package uk.mgrove.ac.soton.comp1206.scene;
|
||||
|
||||
import javafx.animation.*;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.component.GameBlock;
|
||||
@@ -19,7 +28,14 @@ import uk.mgrove.ac.soton.comp1206.util.Multimedia;
|
||||
*/
|
||||
public class ChallengeScene extends BaseScene {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MenuScene.class);
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(ChallengeScene.class);
|
||||
|
||||
/**
|
||||
* The game that is running
|
||||
*/
|
||||
protected Game game;
|
||||
|
||||
/**
|
||||
@@ -37,6 +53,26 @@ public class ChallengeScene extends BaseScene {
|
||||
*/
|
||||
private GameBoard board;
|
||||
|
||||
/**
|
||||
* Progress bar to indicate time left until game loop executes (i.e. life is lost)
|
||||
*/
|
||||
private Rectangle timerCountdownBar;
|
||||
|
||||
/**
|
||||
* Fill transition for timer countdown bar
|
||||
*/
|
||||
private FillTransition timerCountdownBarFillTransition;
|
||||
|
||||
/**
|
||||
* Scale transition for timer countdown bar
|
||||
*/
|
||||
private ScaleTransition timerCountdownBarScaleTransition;
|
||||
|
||||
/**
|
||||
* Top high score property
|
||||
*/
|
||||
private final IntegerProperty highScore = new SimpleIntegerProperty(0);
|
||||
|
||||
/**
|
||||
* Create a new Single Player challenge scene
|
||||
* @param gameWindow the Game Window
|
||||
@@ -66,7 +102,7 @@ public class ChallengeScene extends BaseScene {
|
||||
var mainPane = new BorderPane();
|
||||
challengePane.getChildren().add(mainPane);
|
||||
|
||||
board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2);
|
||||
board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2f,gameWindow.getWidth()/2f);
|
||||
board.setOnRightClicked(game::rotateCurrentPiece);
|
||||
mainPane.setCenter(board);
|
||||
|
||||
@@ -77,11 +113,25 @@ public class ChallengeScene extends BaseScene {
|
||||
currentPieceBoard.setOnLeftClicked(game::rotateCurrentPiece);
|
||||
followingPieceBoard = new PieceBoard(72, 72);
|
||||
|
||||
var topHighScoreText = new Text();
|
||||
topHighScoreText.textProperty().bind(highScore.asString());
|
||||
highScore.set(getHighScore());
|
||||
|
||||
game.scoreProperty().addListener((ChangeListener<? super Number>) (change, oldValue, newValue) -> {
|
||||
if (newValue.intValue() > highScore.get()) highScore.set(newValue.intValue());
|
||||
});
|
||||
|
||||
var rightMenu = new VBox();
|
||||
rightMenu.getChildren().addAll(statsMenu,currentPieceBoard,followingPieceBoard);
|
||||
rightMenu.getChildren().addAll(statsMenu,currentPieceBoard,followingPieceBoard,topHighScoreText);
|
||||
|
||||
mainPane.setRight(rightMenu);
|
||||
|
||||
timerCountdownBar = new Rectangle(gameWindow.getWidth()/2f,48);
|
||||
BorderPane.setAlignment(timerCountdownBar, Pos.BOTTOM_CENTER);
|
||||
// TODO: alignment of timer bar
|
||||
|
||||
mainPane.setBottom(timerCountdownBar);
|
||||
|
||||
Multimedia.playMusic("music/game.wav");
|
||||
|
||||
//Handle block on game board grid being clicked
|
||||
@@ -137,19 +187,54 @@ public class ChallengeScene extends BaseScene {
|
||||
|
||||
game.setLineClearedListener(board::fadeOut);
|
||||
|
||||
game.setOnGameLoop(this::scheduleCountdown);
|
||||
|
||||
game.setShowScoresListener(this::showScores);
|
||||
|
||||
game.start();
|
||||
}
|
||||
|
||||
private void returnToMenu() {
|
||||
// TODO: any processing to end game
|
||||
|
||||
game.endGame();
|
||||
|
||||
gameWindow.startMenu();
|
||||
}
|
||||
|
||||
private void showScores(Game game) {
|
||||
if (timerCountdownBarFillTransition != null) timerCountdownBarFillTransition.stop();
|
||||
if (timerCountdownBarScaleTransition != null) timerCountdownBarScaleTransition.stop();
|
||||
gameWindow.startScores(game);
|
||||
}
|
||||
|
||||
private void setCurrentPiece(GamePiece currentPiece, GamePiece followingPiece) {
|
||||
currentPieceBoard.displayPiece(currentPiece);
|
||||
followingPieceBoard.displayPiece(followingPiece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the countdown progress bar with new game timer delay when the game loop is scheduled
|
||||
* @param timerDelay delay until game loop is executed
|
||||
*/
|
||||
private void scheduleCountdown(int timerDelay) {
|
||||
logger.info("Scheduling UI countdown timer");
|
||||
|
||||
timerCountdownBar.setArcHeight(32);
|
||||
timerCountdownBar.setArcWidth(32);
|
||||
timerCountdownBar.setFill(Color.GREEN);
|
||||
timerCountdownBarFillTransition = new FillTransition(Duration.millis(timerDelay), timerCountdownBar, Color.GREEN, Color.RED);
|
||||
timerCountdownBarScaleTransition = new ScaleTransition(Duration.millis(timerDelay), timerCountdownBar);
|
||||
timerCountdownBarScaleTransition.setFromX(1);
|
||||
timerCountdownBarScaleTransition.setToX(0);
|
||||
timerCountdownBarFillTransition.play();
|
||||
timerCountdownBarScaleTransition.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top high score
|
||||
* @return top high score
|
||||
*/
|
||||
private int getHighScore() {
|
||||
return ScoresScene.getScores("scores.txt").get(0).getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package uk.mgrove.ac.soton.comp1206.scene;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
@@ -16,6 +15,9 @@ import uk.mgrove.ac.soton.comp1206.game.GamePiece;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GamePane;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;
|
||||
|
||||
/**
|
||||
* Scene to show game instructions
|
||||
*/
|
||||
public class InstructionsScene extends BaseScene {
|
||||
|
||||
/**
|
||||
|
||||
216
src/main/java/uk/mgrove/ac/soton/comp1206/scene/ScoresScene.java
Normal file
216
src/main/java/uk/mgrove/ac/soton/comp1206/scene/ScoresScene.java
Normal file
@@ -0,0 +1,216 @@
|
||||
package uk.mgrove.ac.soton.comp1206.scene;
|
||||
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.component.ScoresList;
|
||||
import uk.mgrove.ac.soton.comp1206.game.Game;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GamePane;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* Scene to show scores and record high scores
|
||||
*/
|
||||
public class ScoresScene extends BaseScene {
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(ScoresScene.class);
|
||||
|
||||
/**
|
||||
* The game that just finished
|
||||
*/
|
||||
private final Game game;
|
||||
|
||||
/**
|
||||
* List of scores stored locally
|
||||
*/
|
||||
private SimpleListProperty<Pair<String,Integer>> localScores;
|
||||
|
||||
/**
|
||||
* Root pane in scene inside the GamePane
|
||||
*/
|
||||
private StackPane scoresPane;
|
||||
|
||||
/**
|
||||
* Node to display high scores
|
||||
*/
|
||||
private ScoresList scoresUiList;
|
||||
|
||||
/**
|
||||
* Container for prompting for username when high score achieved
|
||||
*/
|
||||
private VBox userNamePrompt;
|
||||
|
||||
/**
|
||||
* Create a new scores scene
|
||||
* @param gameWindow the Game Window
|
||||
* @param game the game that just finished
|
||||
*/
|
||||
public ScoresScene(GameWindow gameWindow, Game game) {
|
||||
super(gameWindow);
|
||||
logger.info("Creating Scores Scene");
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the layout of the scene
|
||||
*/
|
||||
@Override
|
||||
public void build() {
|
||||
logger.info("Building " + this.getClass().getName());
|
||||
|
||||
root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight());
|
||||
|
||||
scoresPane = new StackPane();
|
||||
scoresPane.setMaxWidth(gameWindow.getWidth());
|
||||
scoresPane.setMaxHeight(gameWindow.getHeight());
|
||||
scoresPane.getStyleClass().add("menu-background");
|
||||
root.getChildren().add(scoresPane);
|
||||
|
||||
var mainPane = new BorderPane();
|
||||
scoresPane.getChildren().add(mainPane);
|
||||
|
||||
ArrayList<Pair<String,Integer>> scoresList = new ArrayList<>();
|
||||
ObservableList<Pair<String,Integer>> observableScoresList = FXCollections.observableArrayList(scoresList);
|
||||
localScores = new SimpleListProperty<>(observableScoresList);
|
||||
loadScores("scores.txt");
|
||||
if (game.getScore() > localScores.get(localScores.getSize() - 1).getValue()) showHighScorePrompt(game.getScore());
|
||||
|
||||
scoresUiList = new ScoresList();
|
||||
scoresUiList.bindScores(localScores);
|
||||
mainPane.setCenter(scoresUiList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise this scene. Called after creation
|
||||
*/
|
||||
@Override
|
||||
public void initialise() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load high scores from file
|
||||
* @param filePath file path to load scores from
|
||||
*/
|
||||
public void loadScores(String filePath) {
|
||||
logger.info("Loading scores from file: {}", filePath);
|
||||
localScores = getScores(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scores from file
|
||||
* @param filePath file to load scores from
|
||||
* @return list of scores
|
||||
*/
|
||||
public static SimpleListProperty<Pair<String,Integer>> getScores(String filePath) {
|
||||
logger.info("Retrieving scores from file: {}", filePath);
|
||||
|
||||
var scores = new SimpleListProperty<Pair<String,Integer>>(FXCollections.observableArrayList());
|
||||
try {
|
||||
File scoresFile = new File(filePath);
|
||||
Scanner scanner = new Scanner(scoresFile);
|
||||
while (scanner.hasNextLine()) {
|
||||
var line = scanner.nextLine();
|
||||
if (line.matches("^.+:[0-9]+$")) {
|
||||
int splitIndex = line.lastIndexOf(":");
|
||||
String[] info = {line.substring(0,splitIndex), line.substring(splitIndex+1)};
|
||||
scores.add(new Pair<>(info[0], Integer.valueOf(info[1])));
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
} catch (FileNotFoundException e1) {
|
||||
logger.error("Unable to load scores file, writing default scores instead: {}", filePath);
|
||||
|
||||
scores.add(new Pair<>("Sam",3000));
|
||||
scores.add(new Pair<>("Jane",2000));
|
||||
scores.add(new Pair<>("Pete",1000));
|
||||
writeScores("scores.txt", scores);
|
||||
}
|
||||
logger.info("Retrieved scores: {}", scores);
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write list of scores to file
|
||||
* @param filePath file to write to
|
||||
* @param scores scores to write
|
||||
*/
|
||||
public static void writeScores(String filePath, SimpleListProperty<Pair<String,Integer>> scores) {
|
||||
logger.info("Writing scores to file: {}", filePath);
|
||||
try {
|
||||
FileWriter scoresWriter = new FileWriter(filePath);
|
||||
|
||||
for (var pair : scores.get()) {
|
||||
scoresWriter.write(pair.getKey() + ":" + pair.getValue() + "\n");
|
||||
}
|
||||
|
||||
scoresWriter.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to write scores file: {}", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new high score - prompt user for name and add to saved scores
|
||||
* @param score new high score to save
|
||||
*/
|
||||
private void showHighScorePrompt(int score) {
|
||||
logger.info("Showing high score username input prompt");
|
||||
|
||||
userNamePrompt = new VBox();
|
||||
var userNameInput = new TextField();
|
||||
userNameInput.setPromptText("Enter username...");
|
||||
userNameInput.setOnAction((event) -> saveHighScore(userNameInput.getText(), score));
|
||||
var userNameSubmit = new Button("Submit");
|
||||
userNameSubmit.setOnAction((event) -> saveHighScore(userNameInput.getText(), score));
|
||||
userNamePrompt.getChildren().addAll(new Text("Username:"), userNameInput, userNameSubmit);
|
||||
scoresPane.getChildren().add(userNamePrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save new high score
|
||||
* @param username username of user who set high score
|
||||
* @param score high score to save
|
||||
*/
|
||||
private void saveHighScore(String username, int score) {
|
||||
logger.info("Saving high score: {} for user: {}", score, username);
|
||||
|
||||
for (var i = 0; i < localScores.getSize(); i++) {
|
||||
if (localScores.get(i).getValue() < score) {
|
||||
localScores.add(i, new Pair<>(username, score));
|
||||
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);
|
||||
scoresPane.getChildren().remove(userNamePrompt);
|
||||
scoresUiList.reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local scores property
|
||||
* @return local scores property
|
||||
*/
|
||||
public SimpleListProperty<Pair<String, Integer>> localScoresProperty() {
|
||||
return localScores;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.game.Game;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
import uk.mgrove.ac.soton.comp1206.scene.*;
|
||||
import uk.mgrove.ac.soton.comp1206.scene.BaseScene;
|
||||
@@ -86,7 +87,9 @@ public class GameWindow {
|
||||
/**
|
||||
* Display the single player challenge
|
||||
*/
|
||||
public void startChallenge() { loadScene(new ChallengeScene(this)); }
|
||||
public void startChallenge() {
|
||||
loadScene(new ChallengeScene(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the multiplayer challenge
|
||||
@@ -100,6 +103,11 @@ public class GameWindow {
|
||||
*/
|
||||
public void startInstructions() { loadScene(new InstructionsScene(this)); }
|
||||
|
||||
/**
|
||||
* Display the single player challenge
|
||||
*/
|
||||
public void startScores(Game game) { loadScene(new ScoresScene(this, game)); }
|
||||
|
||||
/**
|
||||
* Set up the default settings for the stage itself (the window), such as the title and minimum width and height.
|
||||
*/
|
||||
|
||||
@@ -5,8 +5,6 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.component.PieceBoard;
|
||||
import uk.mgrove.ac.soton.comp1206.game.Game;
|
||||
|
||||
/**
|
||||
* Stats menu class to show basic stats about game status
|
||||
@@ -16,24 +14,7 @@ public class StatsMenu extends VBox {
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(Game.class);
|
||||
|
||||
/**
|
||||
* Player's current score
|
||||
*/
|
||||
private final Text score = new Text("0");
|
||||
/**
|
||||
* Player's current level
|
||||
*/
|
||||
private final Text level = new Text("0");
|
||||
/**
|
||||
* Player's remaining lives
|
||||
*/
|
||||
private final Text lives = new Text("3");
|
||||
/**
|
||||
* Player's current multiplier
|
||||
*/
|
||||
private final Text multiplier = new Text("1");
|
||||
private static final Logger logger = LogManager.getLogger(StatsMenu.class);
|
||||
|
||||
/**
|
||||
* Initialise the menu by adding basic stats and binding them to properties
|
||||
@@ -45,17 +26,21 @@ public class StatsMenu extends VBox {
|
||||
public StatsMenu(IntegerProperty score, IntegerProperty level, IntegerProperty lives, IntegerProperty multiplier) {
|
||||
logger.info("Initialising new stats menu with score {}, level {}, lives {}, and multiplier {}", score.get(), level.get(), lives.get(), multiplier.get());
|
||||
|
||||
this.score.textProperty().bind(score.asString());
|
||||
this.level.textProperty().bind(level.asString());
|
||||
this.lives.textProperty().bind(lives.asString());
|
||||
this.multiplier.textProperty().bind(multiplier.asString());
|
||||
Text score1 = new Text();
|
||||
score1.textProperty().bind(score.asString());
|
||||
Text level1 = new Text();
|
||||
level1.textProperty().bind(level.asString());
|
||||
Text lives1 = new Text();
|
||||
lives1.textProperty().bind(lives.asString());
|
||||
Text multiplier1 = new Text();
|
||||
multiplier1.textProperty().bind(multiplier.asString());
|
||||
|
||||
var scoreHeader = new Text("Score");
|
||||
var levelHeader = new Text("Level");
|
||||
var livesHeader = new Text("Lives");
|
||||
var multiplierHeader = new Text("Multiplier");
|
||||
|
||||
getChildren().addAll(scoreHeader,this.score,levelHeader,this.level,livesHeader,this.lives,multiplierHeader,this.multiplier);
|
||||
getChildren().addAll(scoreHeader, score1,levelHeader, level1,livesHeader, lives1,multiplierHeader, multiplier1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.game.Game;
|
||||
|
||||
/**
|
||||
* Class for static multimedia actions like playing sounds
|
||||
*/
|
||||
public class Multimedia {
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(Game.class);
|
||||
private static final Logger logger = LogManager.getLogger(Multimedia.class);
|
||||
|
||||
/**
|
||||
* Media player for game music
|
||||
|
||||
Reference in New Issue
Block a user