diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java new file mode 100644 index 0000000000000000000000000000000000000000..c88cfdb38c9fd55ad276d24847e757aba98bafda --- /dev/null +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java @@ -0,0 +1,5 @@ +package uk.ac.soton.comp1206.event; + +public interface ScoreUpdatedListener { + void updateHighScore(); +} diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java index b8751b677ec55ba14f1ac2530e3a3e9dd816a914..68ab9a82ba4991acb6089294d09ca3612531496b 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java @@ -7,10 +7,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.GameBlockCoordinate; -import uk.ac.soton.comp1206.event.GameLoopListener; -import uk.ac.soton.comp1206.event.GameOverListener; -import uk.ac.soton.comp1206.event.LineClearedListener; -import uk.ac.soton.comp1206.event.NextPieceListener; +import uk.ac.soton.comp1206.event.*; import uk.ac.soton.comp1206.utility.Multimedia; import java.util.*; @@ -50,6 +47,7 @@ public class Game { protected LineClearedListener lineClearedListener = null; protected GameOverListener gameOverListener = null; protected GameLoopListener gameLoopListener = null; + protected ScoreUpdatedListener scoreUpdatedListener = null; protected Timer timer; @@ -327,6 +325,10 @@ public class Game { this.gameLoopListener = listener; } + public void setScoreUpdatedListener(ScoreUpdatedListener listener) { + this.scoreUpdatedListener = listener; + } + public void rotatePiece(int rotations) { this.currentPiece.rotate(rotations); } @@ -354,6 +356,10 @@ public class Game { public void setScore(int score) { logger.info("Score is now " + score); this.score.set(score); + + if (this.scoreUpdatedListener != null) { + scoreUpdatedListener.updateHighScore(); + } } public void setLevel(int level) { diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java index a0f2afa6a0041bd78a8b6798bb67bbfbe86049a1..7ebc981c6eb4f216cdc20daa67c2c76244a5f178 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java @@ -1,12 +1,17 @@ package uk.ac.soton.comp1206.scene; import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ObservableValue; import javafx.geometry.Pos; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; import javafx.scene.text.Text; +import javafx.util.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,6 +22,7 @@ import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; import uk.ac.soton.comp1206.utility.Multimedia; +import java.util.ArrayList; import java.util.HashSet; /** @@ -35,6 +41,8 @@ public class ChallengeScene extends BaseScene { private int keyBoardPosX = 0; private int keyBoardPosY = 0; + private IntegerProperty hiscore = new SimpleIntegerProperty(0); + /** * Create a new Single Player challenge scene * @@ -54,9 +62,12 @@ public class ChallengeScene extends BaseScene { Multimedia.playBackgroundMusic("game_start.wav", false); Multimedia.getBackgroundPlayer().setOnEndOfMedia(() -> Multimedia.playBackgroundMusic("game.wav", true)); + this.hiscore.set(getHighScore()); + this.game.setNextPieceListener(this::nextPiece); this.game.setLineClearedListener(this::lineCleared); this.game.setGameLoopListener(this::gameTimer); + this.game.setScoreUpdatedListener(this::updateHiScore); this.scene.setOnKeyPressed(this::keyHandler); @@ -70,6 +81,13 @@ public class ChallengeScene extends BaseScene { game.start(); } + private void updateHiScore() { + if (this.hiscore.get() < this.game.getScore().get()) { + logger.info("Updating Highscore"); + this.hiscore.set(this.game.getScore().get()); + } + } + private void keyHandler(KeyEvent e) { this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), false); if (e.getCode().equals(KeyCode.ESCAPE)) { @@ -189,7 +207,8 @@ public class ChallengeScene extends BaseScene { hiScoreTitle.getStyleClass().add("heading"); gameInfo.getChildren().add(hiScoreTitle); - var hiScoreVal = new Text("10000"); + var hiScoreVal = new Text(); + hiScoreVal.textProperty().bind(this.hiscore.asString()); hiScoreVal.getStyleClass().add("hiscore"); gameInfo.getChildren().add(hiScoreVal); @@ -279,6 +298,11 @@ public class ChallengeScene extends BaseScene { } } + private int getHighScore() { + ArrayList<Pair<String, Integer>> localScores = ScoresScene.loadScores(); + return localScores.get(0).getValue(); + } + /** * Setup the game object and model */ diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java index cee420bb45199f12656323a30cca3f909b09d91b..cf60f59754cced920e054e8289b630f1ddee83b9 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java @@ -76,12 +76,7 @@ public class MenuScene extends BaseScene { Text exitLabel = new Text("Exit"); exitLabel.getStyleClass().add("menuItem"); - exitLabel.setOnMouseClicked(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent mouseEvent) { - App.getInstance().shutdown(); - } - }); + exitLabel.setOnMouseClicked(e -> shutDown()); menuButtons.getChildren().add(exitLabel); menuButtons.setAlignment(Pos.CENTER); @@ -100,7 +95,8 @@ public class MenuScene extends BaseScene { */ @Override public void initialise() { - Multimedia.playBackgroundMusic("menu.mp3", true); + Multimedia.playBackgroundMusic("menumusic.mp3", true); + this.scene.setOnKeyPressed(e -> shutDown()); } /** @@ -116,4 +112,8 @@ public class MenuScene extends BaseScene { gameWindow.displayInstructions(); } + private void shutDown() { + App.getInstance().shutdown(); + } + } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java index 8f0eab47b67f9a6ac84abc74e97b625420f73bb9..f3c2c357ccb516c98df76d6b1bf784ad19082841 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java @@ -1,23 +1,47 @@ package uk.ac.soton.comp1206.scene; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; 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.ac.soton.comp1206.game.Game; import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; +import uk.ac.soton.comp1206.ui.ScoresList; import uk.ac.soton.comp1206.utility.Multimedia; +import java.io.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + public class ScoresScene extends BaseScene{ private static final Logger logger = LogManager.getLogger(ScoresScene.class); + private final Game game; + + private ObservableList<Pair<String, Integer>> localScores; + private Pair<String, Integer> gameScore; + + private VBox widgets; + private ScoresList localList; + /** * Create a new scene, passing in the GameWindow the scene will be displayed in * @@ -25,13 +49,15 @@ public class ScoresScene extends BaseScene{ */ public ScoresScene(GameWindow gameWindow, Game game) { super(gameWindow); - + this.game = game; logger.info("Creating Scores Scene"); } @Override public void initialise() { - this.scene.setOnKeyPressed(this::escapeToMenu); + this.scene.setOnKeyPressed(e -> { + if (e.getCode().equals(KeyCode.ESCAPE)) escapeToMenu(); + }); Multimedia.playSound("explode.wav"); Multimedia.playBackgroundMusic("end.wav", false); Multimedia.getBackgroundPlayer().setOnEndOfMedia(this.gameWindow::startMenu); @@ -43,6 +69,8 @@ public class ScoresScene extends BaseScene{ root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight()); + this.localScores = FXCollections.observableArrayList(this.loadScores()); + StackPane instructionsPane = new StackPane(); instructionsPane.setMaxWidth(gameWindow.getWidth()); instructionsPane.setMaxHeight(gameWindow.getHeight()); @@ -52,31 +80,153 @@ public class ScoresScene extends BaseScene{ BorderPane mainPane = new BorderPane(); instructionsPane.getChildren().add(mainPane); - VBox widgets = new VBox(); + widgets = new VBox(); ImageView title = new ImageView(getClass().getResource("/images/TetrECS.png").toExternalForm()); title.setPreserveRatio(true); - title.setFitWidth(this.gameWindow.getWidth() / 1.25); + title.setFitWidth(this.gameWindow.getWidth() / 1.75); widgets.getChildren().add(title); Text gameOver = new Text("Game Over"); gameOver.getStyleClass().add("bigtitle"); widgets.getChildren().add(gameOver); - Text hiScorestitle = new Text("High Scores"); - hiScorestitle.getStyleClass().add("title"); - widgets.getChildren().add(hiScorestitle); + localList = new ScoresList("Local Scores"); + + checkHiScore(); + + HBox scoreLists = new HBox(); + widgets.getChildren().add(scoreLists); + + scoreLists.getChildren().add(localList); + + Text onlineScoresTitle = new Text("Online Scores"); + onlineScoresTitle.getStyleClass().add("heading"); + scoreLists.getChildren().add(onlineScoresTitle); + + scoreLists.setAlignment(Pos.CENTER); + scoreLists.setSpacing(100); widgets.setAlignment(Pos.CENTER); widgets.setSpacing(20); mainPane.setCenter(widgets); + + } + + public void checkHiScore() { + boolean isHiScore = false; + int score = this.game.getScore().get(); + + for (Pair<String, Integer> nameScore : this.localScores) { + if (nameScore.getValue() < score) { + isHiScore = true; + break; + } + } + + if (isHiScore) { + logger.info("User has got a high score"); + + Text hiScoreTitle = new Text("You've got a new High Score!"); + hiScoreTitle.getStyleClass().add("title"); + widgets.getChildren().add(hiScoreTitle); + + TextField usernameEntry = new TextField(); + usernameEntry.setPromptText("Enter username"); + usernameEntry.requestFocus(); + widgets.getChildren().add(usernameEntry); + + Button submitBtn = new Button("Submit"); + widgets.getChildren().add(submitBtn); + + submitBtn.setOnAction(e -> { + this.localScores.add(new Pair<>(usernameEntry.getText(), score)); + + this.widgets.getChildren().remove(usernameEntry); + this.widgets.getChildren().remove(submitBtn); + + this.writeScores((List<Pair<String, Integer>>) localScores); + + this.localScores.sort(new Comparator<Pair<String, Integer>>() { + @Override + public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) { + if (l1.getValue() > l2.getValue()) { + return -1; + } else if (l1.getValue().equals(l2.getValue())) { + return 0; + } else { + return 1; + } + } + }); + localList.getScores().bind(new SimpleListProperty<>(this.localScores)); + localList.reveal(); + Multimedia.playSound("pling.wav"); + }); + } else { + logger.info("User does not have a high score"); + Text hiScoreTitle = new Text("High Scores"); + hiScoreTitle.getStyleClass().add("title"); + widgets.getChildren().add(hiScoreTitle); + + localList.getScores().bind(new SimpleListProperty<>(this.localScores)); + localList.reveal(); + } } - private void escapeToMenu(KeyEvent e) { - if (e.getCode().equals(KeyCode.ESCAPE)) { - Multimedia.stopSound(); - this.gameWindow.startMenu(); + public static ArrayList<Pair<String, Integer>> loadScores() { + logger.info("Loading scores from file"); + ArrayList<Pair<String, Integer>> localScores = new ArrayList<>(); + try{ + BufferedReader br = new BufferedReader(new FileReader("localscores.txt")); + + String line = br.readLine(); + while(line != null) { + String[] values = line.split(":"); + localScores.add(new Pair<>(values[0], Integer.parseInt(values[1]))); + line = br.readLine(); + } + br.close(); + + } catch(IOException e) { + logger.info("Error reading file: " + e.getMessage()); } + + localScores.sort(new Comparator<Pair<String, Integer>>() { + @Override + public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) { + if (l1.getValue() > l2.getValue()) { + return -1; + } else if (l1.getValue().equals(l2.getValue())) { + return 0; + } else { + return 1; + } + } + }); + + return localScores; + } + + public void writeScores(List<Pair<String, Integer>> nameScores) { + logger.info("Writing scores to text file"); + + try { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("localscores.txt"))); + + for (Pair<String, Integer> nameScore : nameScores) { + bw.write(nameScore.getKey() + ":" + nameScore.getValue()); + bw.newLine(); + } + bw.close(); + } catch (Exception e) { + logger.info("Error writing to file: " + e.getMessage()); + } + } + + private void escapeToMenu() { + Multimedia.stopSound(); + this.gameWindow.startMenu(); } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java new file mode 100644 index 0000000000000000000000000000000000000000..af28a6ac90c94a01d2e593d090ea73ce1acff15c --- /dev/null +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java @@ -0,0 +1,91 @@ +package uk.ac.soton.comp1206.ui; + +import javafx.animation.Animation; +import javafx.animation.FadeTransition; +import javafx.animation.SequentialTransition; +import javafx.animation.Transition; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.ListChangeListener; +import javafx.geometry.Pos; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import javafx.util.Duration; +import javafx.util.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.component.GameBlock; + +import java.util.ArrayList; + + +public class ScoresList extends VBox { + private static final Logger logger = LogManager.getLogger(ScoresList.class); + + public final SimpleListProperty<Pair<String, Integer>> scores = new SimpleListProperty(); + + private ArrayList<Text> nameScores = new ArrayList<>(); + + private String listName; + + public ScoresList(String listName) { + setVisible(false); + + getStyleClass().add("scorelist"); + + this.listName = listName; + + setSpacing(5); + setAlignment(Pos.CENTER); + + this.scores.addListener((ListChangeListener<? super Pair<String, Integer>>) e -> updateList()); + } + + public void updateList() { + logger.info("Updating score list"); + + nameScores.clear(); + getChildren().clear(); + + Text title = new Text(listName); + title.getStyleClass().add("heading"); + getChildren().add(title); + + int i = 0; + for (Pair<String, Integer> score : scores) { + i++; + if (i > 10) { + break; + } + + Text nameScore = new Text(score.getKey() + ": " + score.getValue()); + nameScore.setTextAlignment(TextAlignment.CENTER); + nameScore.setFill(GameBlock.COLOURS[i]); + + nameScores.add(nameScore); + getChildren().add(nameScore); + } + + } + + public void reveal() { + logger.info("Playing reveal animations"); + + setVisible(true); + SequentialTransition sq = new SequentialTransition(); + + for (Text nameScore : nameScores) { + FadeTransition ft = new FadeTransition(Duration.millis(250), nameScore); + ft.setFromValue(0); + ft.setToValue(1); + sq.getChildren().add(ft); + } + + sq.play(); + } + + public ListProperty<Pair<String, Integer>> getScores() { + return this.scores; + } +} diff --git a/tetrecs/src/main/resources/style/game.css b/tetrecs/src/main/resources/style/game.css index df6fa61442ab1589ac24570a3cd468d9301a6e33..9eee303a2c77680d1a7eb381db628d104aa5544c 100644 --- a/tetrecs/src/main/resources/style/game.css +++ b/tetrecs/src/main/resources/style/game.css @@ -114,7 +114,7 @@ } .scorelist { - -fx-font-size: 20px; + -fx-font-size: 18px; -fx-font-family: 'Orbitron'; } diff --git a/tetrecs/target/classes/module-info.class b/tetrecs/target/classes/module-info.class index b6eb5d5dc3206e20460f1afbfd5d3d22095a88f2..861f46b22632e03297e81eb1e4ffa1c4d743d09d 100644 Binary files a/tetrecs/target/classes/module-info.class and b/tetrecs/target/classes/module-info.class differ diff --git a/tetrecs/target/classes/style/game.css b/tetrecs/target/classes/style/game.css index df6fa61442ab1589ac24570a3cd468d9301a6e33..9eee303a2c77680d1a7eb381db628d104aa5544c 100644 --- a/tetrecs/target/classes/style/game.css +++ b/tetrecs/target/classes/style/game.css @@ -114,7 +114,7 @@ } .scorelist { - -fx-font-size: 20px; + -fx-font-size: 18px; -fx-font-family: 'Orbitron'; } diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class index 97e05b42bbb29b8c097703f72b56f60458245a6a..f240a5fb067647a6e97467e55deccf97ca0b1c87 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class index c376e054ce27bc4038ee3abe557de47110c82a4b..190abd750f56f6ec1d22c4f9aeec33d970d691b1 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class index 6fa262bd093cd2c01b74abc8920c2ac3a1ac22ec..5a5424bebb75fe9dda472b832ce79f17ba0d6c4e 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class differ