From 9f0026b4ef76ca6baff89361e29a00ac54f31065 Mon Sep 17 00:00:00 2001 From: Matthew Grove Date: Wed, 5 Apr 2023 13:28:52 +0100 Subject: [PATCH] [FEAT] Add basic instructions and show current piece during game --- .../soton/comp1206/component/GameBlock.java | 6 +- .../soton/comp1206/component/GameBoard.java | 2 +- .../soton/comp1206/component/PieceBoard.java | 24 +++++ .../comp1206/event/NextPieceListener.java | 15 +++ .../mgrove/ac/soton/comp1206/game/Game.java | 16 +++ .../mgrove/ac/soton/comp1206/game/Grid.java | 10 ++ .../soton/comp1206/scene/ChallengeScene.java | 38 ++++++- .../comp1206/scene/InstructionsScene.java | 99 +++++++++++++++++++ .../ac/soton/comp1206/scene/MenuScene.java | 39 ++++++-- .../ac/soton/comp1206/ui/GameWindow.java | 12 +++ .../ac/soton/comp1206/ui/StatsMenu.java | 2 + 11 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 src/main/java/uk/mgrove/ac/soton/comp1206/component/PieceBoard.java create mode 100644 src/main/java/uk/mgrove/ac/soton/comp1206/event/NextPieceListener.java create mode 100644 src/main/java/uk/mgrove/ac/soton/comp1206/scene/InstructionsScene.java diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBlock.java b/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBlock.java index f0dba6e..33b318b 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBlock.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBlock.java @@ -122,11 +122,11 @@ public class GameBlock extends Canvas { gc.clearRect(0,0,width,height); //Fill - gc.setFill(Color.WHITE); + gc.setFill(Color.web("111111",0.5)); gc.fillRect(0,0, width, height); //Border - gc.setStroke(Color.BLACK); + gc.setStroke(Color.GRAY); gc.strokeRect(0,0,width,height); } @@ -145,7 +145,7 @@ public class GameBlock extends Canvas { gc.fillRect(0,0, width, height); //Border - gc.setStroke(Color.BLACK); + gc.setStroke(Color.GRAY); gc.strokeRect(0,0,width,height); } diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBoard.java b/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBoard.java index 6fc2c78..2ed9c35 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBoard.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/component/GameBoard.java @@ -75,7 +75,7 @@ public class GameBoard extends GridPane { } /** - * Create a new GameBoard with it's own internal grid, specifying the number of columns and rows, along with the + * Create a new GameBoard with its own internal grid, specifying the number of columns and rows, along with the * visual width and height. * * @param cols number of columns for internal grid diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/component/PieceBoard.java b/src/main/java/uk/mgrove/ac/soton/comp1206/component/PieceBoard.java new file mode 100644 index 0000000..7cc584e --- /dev/null +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/component/PieceBoard.java @@ -0,0 +1,24 @@ +package uk.mgrove.ac.soton.comp1206.component; + +import uk.mgrove.ac.soton.comp1206.game.GamePiece; +import uk.mgrove.ac.soton.comp1206.game.Grid; + +public class PieceBoard extends GameBoard { + + /** + * Create a new GameBoard with it's own internal grid, specifying the number of columns and rows, along with the + * visual width and height. + * + * @param width the visual width + * @param height the visual height + */ + public PieceBoard(double width, double height) { + super(3, 3, width, height); + } + + public void displayPiece(GamePiece piece) { + grid.clearGrid(); + grid.playPiece(piece,1,1); + } + +} diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/event/NextPieceListener.java b/src/main/java/uk/mgrove/ac/soton/comp1206/event/NextPieceListener.java new file mode 100644 index 0000000..eabed6f --- /dev/null +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/event/NextPieceListener.java @@ -0,0 +1,15 @@ +package uk.mgrove.ac.soton.comp1206.event; + +import uk.mgrove.ac.soton.comp1206.game.GamePiece; + +/** + * The Next Piece Listener is used for listening for new pieces being provided to the game. + */ +public interface NextPieceListener { + + /** + * Handle a new piece being received by the game + * @param piece the piece that was received + */ + public void nextPiece(GamePiece piece); +} \ No newline at end of file diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/game/Game.java b/src/main/java/uk/mgrove/ac/soton/comp1206/game/Game.java index 0204f2d..df28c18 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/game/Game.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/game/Game.java @@ -5,6 +5,7 @@ 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.event.NextPieceListener; import java.util.*; @@ -61,6 +62,11 @@ public class Game { */ protected IntegerProperty multiplier = new SimpleIntegerProperty(1); + /** + * Listener for when new piece is received by the game + */ + private NextPieceListener nextPieceListener; + /** * Create a new game with the specified rows and columns. Creates a corresponding grid model. * @param cols number of columns @@ -88,6 +94,7 @@ public class Game { */ public GamePiece nextPiece() { currentPiece = spawnPiece(); + if (nextPieceListener != null) nextPieceListener.nextPiece(currentPiece); logger.info("Next piece is: {}", currentPiece); return currentPiece; } @@ -317,4 +324,13 @@ public class Game { public IntegerProperty scoreProperty() { return score; } + + /** + * Set next piece listener + * @param listener listener to set + */ + public void setNextPieceListener(NextPieceListener listener) { + nextPieceListener = listener; + } + } diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/game/Grid.java b/src/main/java/uk/mgrove/ac/soton/comp1206/game/Grid.java index 6304e86..1cb91a4 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/game/Grid.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/game/Grid.java @@ -147,6 +147,16 @@ public class Grid { } } + public void clearGrid() { + for (var x = 0; x < getCols(); x++) { + for (var y = 0; y < getRows(); y++) { + if (get(x,y) != 0) { + set(x,y,0); + } + } + } + } + /** * Get the number of columns in this game * @return number of columns diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/scene/ChallengeScene.java b/src/main/java/uk/mgrove/ac/soton/comp1206/scene/ChallengeScene.java index 319cdfa..544cb43 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/scene/ChallengeScene.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/scene/ChallengeScene.java @@ -1,11 +1,15 @@ package uk.mgrove.ac.soton.comp1206.scene; +import javafx.scene.input.KeyCode; import javafx.scene.layout.*; 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.GameBoard; +import uk.mgrove.ac.soton.comp1206.component.PieceBoard; +import uk.mgrove.ac.soton.comp1206.event.NextPieceListener; import uk.mgrove.ac.soton.comp1206.game.Game; +import uk.mgrove.ac.soton.comp1206.game.GamePiece; import uk.mgrove.ac.soton.comp1206.ui.GamePane; import uk.mgrove.ac.soton.comp1206.ui.GameWindow; import uk.mgrove.ac.soton.comp1206.ui.StatsMenu; @@ -19,6 +23,11 @@ public class ChallengeScene extends BaseScene { private static final Logger logger = LogManager.getLogger(MenuScene.class); protected Game game; + /** + * PieceBoard to display current piece + */ + private PieceBoard currentPieceBoard; + /** * Create a new Single Player challenge scene * @param gameWindow the Game Window @@ -52,7 +61,13 @@ public class ChallengeScene extends BaseScene { mainPane.setCenter(board); var statsMenu = new StatsMenu(game.scoreProperty(),game.levelProperty(),game.livesProperty(),game.multiplierProperty()); - mainPane.setRight(statsMenu); + + currentPieceBoard = new PieceBoard(100, 100); + + var rightMenu = new VBox(); + rightMenu.getChildren().addAll(statsMenu,currentPieceBoard); + + mainPane.setRight(rightMenu); Multimedia.playMusic("music/game.wav"); @@ -84,7 +99,28 @@ public class ChallengeScene extends BaseScene { @Override public void initialise() { logger.info("Initialising Challenge"); + + // exit challenge when escape key pressed + scene.setOnKeyPressed((event) -> { + if (event.getCode() == KeyCode.ESCAPE) { + returnToMenu(); + } + }); + + game.setNextPieceListener(this::setCurrentPiece); + game.start(); } + private void returnToMenu() { + // TODO: any processing to end game + + + gameWindow.startMenu(); + } + + private void setCurrentPiece(GamePiece piece) { + currentPieceBoard.displayPiece(piece); + } + } diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/scene/InstructionsScene.java b/src/main/java/uk/mgrove/ac/soton/comp1206/scene/InstructionsScene.java new file mode 100644 index 0000000..0e07214 --- /dev/null +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/scene/InstructionsScene.java @@ -0,0 +1,99 @@ +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; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.StackPane; +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.GamePiece; +import uk.mgrove.ac.soton.comp1206.ui.GamePane; +import uk.mgrove.ac.soton.comp1206.ui.GameWindow; + +public class InstructionsScene extends BaseScene { + + /** + * Logger + */ + private static final Logger logger = LogManager.getLogger(InstructionsScene.class); + + /** + * Create a new scene, passing in the GameWindow the scene will be displayed in + * + * @param gameWindow the game window + */ + public InstructionsScene(GameWindow gameWindow) { + super(gameWindow); + } + + /** + * Initialise this scene. Called after creation + */ + @Override + public void initialise() { + logger.info("Initialising Instructions"); + + // exit challenge when escape key pressed + scene.setOnKeyPressed((event) -> { + if (event.getCode() == KeyCode.ESCAPE) { + gameWindow.startMenu(); + } + }); + } + + /** + * Build the layout of the scene + */ + @Override + public void build() { + logger.info("Building " + this.getClass().getName()); + + root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight()); + + var menuPane = new StackPane(); + menuPane.setMaxWidth(gameWindow.getWidth()); + menuPane.setMaxHeight(gameWindow.getHeight()); + menuPane.getStyleClass().add("menu-background"); + root.getChildren().add(menuPane); + + var mainPane = new BorderPane(); + menuPane.getChildren().add(mainPane); + + //Awful title + var title = new Text("How to Play"); + title.getStyleClass().add("title"); + mainPane.setTop(title); + BorderPane.setAlignment(title, Pos.TOP_CENTER); + + var instructions = new ImageView(getClass().getResource("/images/Instructions.png").toExternalForm()); + instructions.setPreserveRatio(true); + instructions.setFitHeight(gameWindow.getHeight()*0.55); + mainPane.setCenter(instructions); + + var pieceGrid = new GridPane(); + pieceGrid.setVgap(8); + pieceGrid.setHgap(8); + for (var i = 0; i < GamePiece.PIECES; i++) { + var pieceBoard = new PieceBoard(48, 48); + pieceBoard.displayPiece(GamePiece.createPiece(i)); + pieceGrid.add(pieceBoard, i % 5, Math.floorDiv(i,5)); + } + + var allPieces = new VBox(); + var piecesHeader = new Text("Game Pieces"); + piecesHeader.getStyleClass().add("title"); + allPieces.getChildren().addAll(piecesHeader,pieceGrid); + allPieces.setAlignment(Pos.BOTTOM_CENTER); + pieceGrid.setAlignment(Pos.BOTTOM_CENTER); + + mainPane.setBottom(allPieces); + BorderPane.setAlignment(allPieces, Pos.BOTTOM_CENTER); + } + +} \ No newline at end of file diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/scene/MenuScene.java b/src/main/java/uk/mgrove/ac/soton/comp1206/scene/MenuScene.java index 647274c..e79aabd 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/scene/MenuScene.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/scene/MenuScene.java @@ -15,6 +15,9 @@ import uk.mgrove.ac.soton.comp1206.util.Multimedia; */ public class MenuScene extends BaseScene { + /** + * Logger + */ private static final Logger logger = LogManager.getLogger(MenuScene.class); /** @@ -49,14 +52,22 @@ public class MenuScene extends BaseScene { title.getStyleClass().add("title"); mainPane.setTop(title); - //For now, let us just add a button that starts the game. I'm sure you'll do something way better. - var button = new Button("Play"); - mainPane.setCenter(button); + var menuItems = new VBox(); + var singlePlayerButton = new Button("Single Player"); + var multiPlayerButton = new Button("Multi Player"); + var howToPlayButton = new Button("How to Play"); + var exitButton = new Button("Exit"); + menuItems.getChildren().addAll(singlePlayerButton,multiPlayerButton,howToPlayButton,exitButton); + mainPane.setCenter(menuItems); + + // TOOD: window animations Multimedia.playMusic("music/menu.mp3"); //Bind the button action to the startGame method in the menu - button.setOnAction(this::startGame); + singlePlayerButton.setOnAction(this::startSinglePlayer); + multiPlayerButton.setOnAction(this::startMultiPlayer); + howToPlayButton.setOnAction(this::showInstructions); } /** @@ -68,11 +79,27 @@ public class MenuScene extends BaseScene { } /** - * Handle when the Start Game button is pressed + * Handle when the Start Single Player Game button is pressed * @param event event */ - private void startGame(ActionEvent event) { + private void startSinglePlayer(ActionEvent event) { gameWindow.startChallenge(); } + /** + * Handle when the Start Multiplayer Game button is pressed + * @param event event + */ + private void startMultiPlayer(ActionEvent event) { + gameWindow.startMultiplayer(); + } + + /** + * Handle when the How to Play button is pressed + * @param event event + */ + private void showInstructions(ActionEvent event) { + gameWindow.startInstructions(); + } + } diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/ui/GameWindow.java b/src/main/java/uk/mgrove/ac/soton/comp1206/ui/GameWindow.java index 38583a2..d7fd1f6 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/ui/GameWindow.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/ui/GameWindow.java @@ -88,6 +88,18 @@ public class GameWindow { */ public void startChallenge() { loadScene(new ChallengeScene(this)); } + /** + * Display the multiplayer challenge + */ + public void startMultiplayer() { + // TODO: load multiplayer scene + } + + /** + * Display the instructions + */ + public void startInstructions() { loadScene(new InstructionsScene(this)); } + /** * Setup the default settings for the stage itself (the window), such as the title and minimum width and height. */ diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/ui/StatsMenu.java b/src/main/java/uk/mgrove/ac/soton/comp1206/ui/StatsMenu.java index fe92777..ddc1a74 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/ui/StatsMenu.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/ui/StatsMenu.java @@ -5,6 +5,7 @@ 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; /** @@ -53,6 +54,7 @@ public class StatsMenu extends VBox { 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); }