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 a04a20b..b03fc2e 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 @@ -64,6 +64,11 @@ public class GameBlock extends Canvas { */ private final IntegerProperty value = new SimpleIntegerProperty(0); + /** + * The preview value of this block (0 = empty, otherwise specifies the colour to render as) + */ + private final IntegerProperty previewValue = new SimpleIntegerProperty(0); + /** * Whether the block should appear as focussed */ @@ -98,6 +103,7 @@ public class GameBlock extends Canvas { //When the value property is updated, call the internal updateValue method value.addListener(this::updateValue); + previewValue.addListener(this::updateValue); } /** @@ -121,6 +127,9 @@ public class GameBlock extends Canvas { //If the block is not empty, paint with the colour represented by the value paintColor(COLOURS[value.get()]); } + + if (previewValue.get() != 0) paintPreview(COLOURS[previewValue.get()]); + // paint focus overlay if required if (isFocussed) paintFocusOverlay(); @@ -128,6 +137,29 @@ public class GameBlock extends Canvas { if (showMiddleIndicator) paintMiddleIndicator(); } + /** + * Handle painting of the block canvas with block preview + */ + public void paintPreview(Color color) { + logger.info("Painting preview overlay on block at x: {}, y: {} with color: {}", getX(), getY(), color); + + var gc = getGraphicsContext2D(); + + gc.setFill(color.deriveColor(0, 1, 1, 0.25)); + gc.fillRect(0,0, width, height); + gc.setFill(color.deriveColor(0,1,0.8,0.25)); + gc.fillPolygon(new double[]{ + width, + width, + 0 + }, new double[]{ + 0, + height, + 0 + }, 3 + ); + } + /** * Paint this canvas empty */ @@ -283,6 +315,14 @@ public class GameBlock extends Canvas { value.bind(input); } + /** + * Bind the value of this block to another property. Used to link the visual block to a corresponding block in the Grid. + * @param input property to bind the value to + */ + public void bindPreview(ObservableValue input) { + previewValue.bind(input); + } + @Override public String toString() { return "GameBlock{" + 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 81608ca..0e29b85 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 @@ -7,6 +7,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import uk.mgrove.ac.soton.comp1206.event.BlockClickedListener; import uk.mgrove.ac.soton.comp1206.event.MouseClickListener; +import uk.mgrove.ac.soton.comp1206.game.GamePiece; import uk.mgrove.ac.soton.comp1206.game.Grid; import uk.mgrove.ac.soton.comp1206.util.Multimedia; @@ -16,7 +17,7 @@ import java.util.Set; * A GameBoard is a visual component to represent the visual GameBoard. * It extends a GridPane to hold a grid of GameBlocks. * - * The GameBoard can hold an internal grid of it's own, for example, for displaying an upcoming block. It also be + * The GameBoard can hold an internal grid of its own, for example, for displaying an upcoming block. It also be * linked to an external grid, for the main game board. * * The GameBoard is only a visual representation and should not contain game logic or model logic in it, which should @@ -81,6 +82,11 @@ public class GameBoard extends GridPane { */ protected boolean enableFocus = true; + /** + * Current game piece + */ + protected GamePiece currentPiece; + /** * Create a new GameBoard, based off a given grid, with a visual width and height. * @param grid linked grid @@ -151,6 +157,8 @@ public class GameBoard extends GridPane { logger.debug("Mouse button clicked: {}", event.getButton()); if (event.getButton().equals(MouseButton.SECONDARY) && rightClickListener != null) { rightClickListener.action(); + grid.clearPreview(); + grid.previewPiece(currentPiece, getXFocus(), getYFocus()); } else if (event.getButton().equals(MouseButton.PRIMARY) && leftClickListener != null) { leftClickListener.action(); } @@ -178,6 +186,7 @@ public class GameBoard extends GridPane { //Link the GameBlock component to the corresponding value in the Grid block.bind(grid.getGridProperty(x,y)); + block.bindPreview(grid.getPreviewGridProperty(x,y)); //Add a mouse click handler to the block to trigger GameBoard blockClicked method block.setOnMouseClicked((e) -> { @@ -212,11 +221,13 @@ public class GameBoard extends GridPane { } public void blockHovered(GameBlock gameBlock) { + grid.clearPreview(); if (enableFocus) { logger.info("Block hovered: {}", gameBlock); if (focussedBlock != null) focussedBlock.setFocussed(false); focussedBlock = gameBlock; focussedBlock.setFocussed(true); + grid.previewPiece(currentPiece, getXFocus(), getYFocus()); } } @@ -270,12 +281,28 @@ public class GameBoard extends GridPane { else return 0; } + /** + * Set listener for when secondary mouse button clicked + * @param listener listener to set + */ public void setOnRightClicked(MouseClickListener listener) { rightClickListener = listener; } + /** + * Set listener for when primary mouse button clicked + * @param listener listener to set + */ public void setOnLeftClicked(MouseClickListener listener) { leftClickListener = listener; } + /** + * Update current game piece + * @param newPiece current piece + */ + public void setCurrentPiece(GamePiece newPiece) { + currentPiece = newPiece; + } + } 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 0ee00ac..ebe772b 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 @@ -38,6 +38,11 @@ public class Grid { */ private final SimpleIntegerProperty[][] grid; + /** + * The preview grid is a 2D arrow with rows and columns of SimpleIntegerProperties, storing which blocks are currently showing previews. + */ + private final SimpleIntegerProperty[][] previewGrid; + /** * Create a new Grid with the specified number of columns and rows and initialise them * @param cols number of columns @@ -56,6 +61,16 @@ public class Grid { grid[x][y] = new SimpleIntegerProperty(0); } } + + //Create the grid itself + previewGrid = new SimpleIntegerProperty[cols][rows]; + + //Add a SimpleIntegerProperty to every block in the grid + for(var y = 0; y < rows; y++) { + for(var x = 0; x < cols; x++) { + previewGrid[x][y] = new SimpleIntegerProperty(0); + } + } } /** @@ -68,6 +83,16 @@ public class Grid { return grid[x][y]; } + /** + * Get the Integer property contained inside the preview grid at a given row and column index. Can be used for binding. + * @param x column + * @param y row + * @return the IntegerProperty at the given x and y in this grid + */ + public IntegerProperty getPreviewGridProperty(int x, int y) { + return previewGrid[x][y]; + } + /** * Update the value at the given x and y index within the grid * @param x column @@ -175,4 +200,37 @@ public class Grid { return rows; } + /** + * Preview piece on board + * @param piece piece to preview + * @param x x-coordinate of piece centre + * @param y y-coordinate of piece centre + */ + public void previewPiece(GamePiece piece, int x, int y) { + if (!canPlayPiece(piece, x, y)) return; + + logger.info("Previewing piece {} at {},{}", piece, x, y); + int value = piece.getValue(); + int[][] blocks = piece.getBlocks(); + + for (var blockX = 0; blockX < blocks.length; blockX++) { + for (var blockY = 0; blockY < blocks.length; blockY++) { + if (blocks[blockX][blockY] > 0) { + previewGrid[x + blockX - 1][y + blockY - 1].set(value); + } + } + } + } + + /** + * Clear previewed blocks from the grid + */ + public void clearPreview() { + for (var resetX = 0; resetX < previewGrid.length; resetX++) { + for (var resetY = 0; resetY < previewGrid[0].length; resetY++) { + previewGrid[resetX][resetY].set(0); + } + } + } + } 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 6f9d50b..5b043d0 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 @@ -218,6 +218,7 @@ public class ChallengeScene extends BaseScene { protected void setCurrentPiece(GamePiece currentPiece, GamePiece followingPiece) { currentPieceBoard.displayPiece(currentPiece); followingPieceBoard.displayPiece(followingPiece); + board.setCurrentPiece(currentPiece); } /** diff --git a/src/main/java/uk/mgrove/ac/soton/comp1206/util/Multimedia.java b/src/main/java/uk/mgrove/ac/soton/comp1206/util/Multimedia.java index f1ffee4..c7cb9eb 100644 --- a/src/main/java/uk/mgrove/ac/soton/comp1206/util/Multimedia.java +++ b/src/main/java/uk/mgrove/ac/soton/comp1206/util/Multimedia.java @@ -34,7 +34,7 @@ public class Multimedia { if (audioPlayer != null) audioPlayer.stop(); var media = new Media(Multimedia.class.getResource("/" + filePath).toExternalForm()); audioPlayer = new MediaPlayer(media); -// audioPlayer.play(); + audioPlayer.play(); } /** @@ -47,7 +47,7 @@ public class Multimedia { if (musicPlayer != null) musicPlayer.stop(); var media = new Media(Multimedia.class.getResource("/" + filePath).toExternalForm()); musicPlayer = new MediaPlayer(media); -// musicPlayer.setAutoPlay(true); + musicPlayer.setAutoPlay(true); musicPlayer.setCycleCount(MediaPlayer.INDEFINITE); } diff --git a/src/main/resources/style/game.css b/src/main/resources/style/game.css index 7a2bbfb..f10301b 100644 --- a/src/main/resources/style/game.css +++ b/src/main/resources/style/game.css @@ -8,6 +8,7 @@ Text { -fx-fill: white; + -fx-font-family: 'Orbitron'; } Label {