diff --git a/tetrecs/localscores.txt b/tetrecs/localscores.txt deleted file mode 100644 index 580fb291bd0d28dd72d860177e825cc8f8f52f66..0000000000000000000000000000000000000000 --- a/tetrecs/localscores.txt +++ /dev/null @@ -1,11 +0,0 @@ -w:21950 -2:21850 -test6:21850 -test1234:2700 -Ayaz:2690 -aa:2290 -Player2:1890 -Player1:540 -t:540 -a:450 -rtwe:350 diff --git a/tetrecs/scores.txt b/tetrecs/scores.txt new file mode 100644 index 0000000000000000000000000000000000000000..fa50493f9e0e70b8374273024091103556c47729 --- /dev/null +++ b/tetrecs/scores.txt @@ -0,0 +1,11 @@ +test10:10000 +test9:9000 +test8:8000 +test7:7000 +test6:6000 +test5:5000 +test4:4000 +test3:3000 +Ayaz:2320 +rrr:2240 +test2:2000 diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/App.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/App.java index aa834864199b060c4604914d1de6719af368ec67..012c0ba65eb01dcdabf70ae371b671b534e843dd 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/App.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/App.java @@ -13,69 +13,72 @@ import uk.ac.soton.comp1206.ui.GameWindow; */ public class App extends Application { - /** - * Base resolution width - */ - private final int width = 800; - - /** - * Base resolution height - */ - private final int height = 600; - - private static App instance; - private static final Logger logger = LogManager.getLogger(App.class); - private Stage stage; - - /** - * Start the game - * @param args commandline arguments - */ - public static void main(String[] args) { - logger.info("Starting client"); - launch(); - } - - /** - * Called by JavaFX with the primary stage as a parameter. Begins the game by opening the Game Window - * @param stage the default stage, main window - */ - @Override - public void start(Stage stage) { - instance = this; - this.stage = stage; - - //Open game window - openGame(); - } - - /** - * Create the GameWindow with the specified width and height - */ - public void openGame() { - logger.info("Opening game window"); - - //Change the width and height in this class to change the base rendering resolution for all game parts - var gameWindow = new GameWindow(stage,width,height); - - //Display the GameWindow - stage.show(); - } - - /** - * Shutdown the game - */ - public void shutdown() { - logger.info("Shutting down"); - System.exit(0); - } - - /** - * Get the singleton App instance - * @return the app - */ - public static App getInstance() { - return instance; - } + /** + * Base resolution width + */ + private final int width = 800; + + /** + * Base resolution height + */ + private final int height = 600; + + private static App instance; + private static final Logger logger = LogManager.getLogger(App.class); + private Stage stage; + + /** + * Start the game + * + * @param args commandline arguments + */ + public static void main(String[] args) { + logger.info("Starting client"); + launch(); + } + + /** + * Called by JavaFX with the primary stage as a parameter. Begins the game by opening the Game Window + * + * @param stage the default stage, main window + */ + @Override + public void start(Stage stage) { + instance = this; + this.stage = stage; + + //Open game window + openGame(); + } + + /** + * Create the GameWindow with the specified width and height + */ + public void openGame() { + logger.info("Opening game window"); + + //Change the width and height in this class to change the base rendering resolution for all game parts + var gameWindow = new GameWindow(stage, width, height); + + //Display the GameWindow + stage.show(); + } + + /** + * Shutdown the game + */ + public void shutdown() { + logger.info("Shutting down"); + System.exit(0); + } + + /** + * Get the singleton App instance + * + * @return the app + */ + public static App getInstance() { + return instance; + } } \ No newline at end of file diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/Launcher.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/Launcher.java index 4fd4e2193586008fb87b04d044dfbba690a82b23..d3c1e0fde69dde479e280b64a4bc4dcc69c08ed9 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/Launcher.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/Launcher.java @@ -6,12 +6,13 @@ package uk.ac.soton.comp1206; */ public class Launcher { - /** - * Launch the JavaFX Application, passing through the commandline arguments - * @param args commandline arguments - */ - public static void main(String[] args) { - App.main(args); - } + /** + * Launch the JavaFX Application, passing through the commandline arguments + * + * @param args commandline arguments + */ + public static void main(String[] args) { + App.main(args); + } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java index a234baa73c732eea793dcf4c9bf14edfad7ae214..49a1b08488d1ed01471248d7345eceebcfd6d07a 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java @@ -120,10 +120,12 @@ public class GameBlock extends Canvas { paintColor(COLOURS[value.get()]); } + //Paint the block if the user is hovering on it if (isHovering) { paintHoverTile(); } + //Paint the centre of a piece if (isCentre) { paintCentreCircle(); } @@ -165,38 +167,47 @@ public class GameBlock extends Canvas { gc.setFill(colour); gc.fillRect(0, 0, width, height); - gc.setFill(Color.color(1,1,1,0.3)); + gc.setFill(Color.color(1, 1, 1, 0.3)); gc.fillPolygon(new double[]{0, width, 0}, new double[]{0, 0, height}, 3); - gc.setFill(Color.color(1,1,1,0.6)); + gc.setFill(Color.color(1, 1, 1, 0.6)); gc.fillRect(width - 3, 0, width, height); - gc.setFill(Color.color(1,1,1,0.6)); - gc.fillRect(0,0, width, 3); + gc.setFill(Color.color(1, 1, 1, 0.6)); + gc.fillRect(0, 0, width, 3); - gc.setFill(Color.color(0,0,0,0.4)); + gc.setFill(Color.color(0, 0, 0, 0.4)); gc.fillRect(0, height - 3, width, height); - gc.setFill(Color.color(0,0,0,0.4)); - gc.fillRect(0,0, 3, height); + gc.setFill(Color.color(0, 0, 0, 0.4)); + gc.fillRect(0, 0, 3, height); //Border - gc.setStroke(Color.color(0,0,0,0.4)); + gc.setStroke(Color.color(0, 0, 0, 0.4)); gc.strokeRect(0, 0, width, height); } + /** + * Paint the centre circle of a piece + */ private void paintCentreCircle() { GraphicsContext gc = getGraphicsContext2D(); gc.setFill(Color.color(1.0, 1.0, 1.0, 0.6)); gc.fillOval(this.width / 4, this.height / 4, this.width / 2, this.height / 2); } + /** + * Paints a hover graphic on a tile if the user is hovering on it + */ private void paintHoverTile() { GraphicsContext gc = getGraphicsContext2D(); gc.setFill(Color.color(1.0, 1.0, 1.0, 0.4)); - gc.fillRect(0,0, width, height); + gc.fillRect(0, 0, width, height); } + /** + * Sets the block to a centre block + */ public void setCentre() { this.isCentre = true; paint(); @@ -207,11 +218,17 @@ public class GameBlock extends Canvas { paint(); } + /** + * Starts fade out animation when a line is cleared + */ public void startAnim() { FadeAnimation fadeAnimation = new FadeAnimation(); fadeAnimation.start(); } + /** + * Custom animation class which paints the block empty then paints it green which then fades out + */ private class FadeAnimation extends AnimationTimer { double opacity = 1.0; @@ -226,16 +243,16 @@ public class GameBlock extends Canvas { opacity = opacity - 0.03; if (this.opacity > 0) { GraphicsContext gc = GameBlock.this.getGraphicsContext2D(); - gc.setFill(Color.color(0,1,0,opacity)); - gc.fillRect(0,0, GameBlock.this.getWidth(), GameBlock.this.getHeight()); + gc.setFill(Color.color(0, 1, 0, opacity)); + gc.fillRect(0, 0, GameBlock.this.getWidth(), GameBlock.this.getHeight()); } else { stop(); + GameBlock.this.paint(); } } } - /** * Get the column of this block * diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java index 0a5913c9e7d7b571acf6ad400d6d0a1432fe3c19..ab638eb6e532ed62101ba97f2a49323d2bee6aca 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java @@ -1,13 +1,13 @@ package uk.ac.soton.comp1206.component; import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.GridPane; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import uk.ac.soton.comp1206.event.BlockClickedListener; +import uk.ac.soton.comp1206.event.BoardHoverListener; import uk.ac.soton.comp1206.event.RightClickListener; -import uk.ac.soton.comp1206.game.Game; +import uk.ac.soton.comp1206.game.GamePiece; import uk.ac.soton.comp1206.game.Grid; /** @@ -55,15 +55,15 @@ public class GameBoard extends GridPane { GameBlock[][] blocks; /** - * The listener to call when a specific block is clicked + * The listeners to call when a specific block is clicked, right clicked or hovered over */ protected BlockClickedListener blockClickedListener; protected RightClickListener rightClickListener; - protected boolean isPieceBoard = false; + protected BoardHoverListener boardHoverListener; - private GameBlock hoveringBlock; + protected boolean isPieceBoard = false; /** * Create a new GameBoard, based off a given grid, with a visual width and height. @@ -156,9 +156,7 @@ public class GameBoard extends GridPane { //Link the GameBlock component to the corresponding value in the Grid block.bind(grid.getGridProperty(x, y)); - //Add a mouse click handler to the block to trigger GameBoard blockClicked method - //block.setOnMouseClicked((e) -> blockClicked(e, block)); - + //Mouse clicked handler which rotates or places a block block.setOnMouseClicked(e -> { if (e.getButton() == MouseButton.PRIMARY) { blockClicked(block); @@ -167,9 +165,11 @@ public class GameBoard extends GridPane { } }); - block.setOnMouseEntered(e -> setHover(block, true)); + //Shows a piece hovering when the mouse enters a block + block.setOnMouseEntered(e -> blockHover(block, true)); - block.setOnMouseExited(e -> setHover(block, false)); + //Removes piece hovering when mouse leaves a block + block.setOnMouseExited(e -> blockHover(block, false)); return block; } @@ -183,10 +183,24 @@ public class GameBoard extends GridPane { this.blockClickedListener = listener; } + /** + * Sets the right click listener to handle right click events + * + * @param listener listener to add + */ public void setOnRightClick(RightClickListener listener) { this.rightClickListener = listener; } + /** + * Set the hover listener to handle block hover events + * + * @param listener listener to add + */ + public void setOnHover(BoardHoverListener listener) { + this.boardHoverListener = listener; + } + /** * Triggered when a block is clicked. Call the attached listener. * @@ -198,25 +212,83 @@ public class GameBoard extends GridPane { } } + /** + * Triggered when a block is right clicked and calls the attached listener + * + * @param block block right clicked on + */ private void blockRightClicked(GameBlock block) { if (this.rightClickListener != null) { - this.rightClickListener.rightClicked(); + this.rightClickListener.rightClicked(block); + } + } + + /** + * Triggered when the user hovers over a block and calls the attached listener + * + * @param block block which the user hovers over + */ + private void blockHover(GameBlock block, boolean isHovering) { + if (this.boardHoverListener != null) { + this.boardHoverListener.pieceHovering(block, isHovering); } } - public void setHover(GameBlock block, boolean isHovering) { + /** + * Starts fade out animation + * + * @param block block which has been cleared in a line + */ + public void fadeOutAnim(GameBlock block) { + block.startAnim(); + } + + /** + * Sets a piece to hover or unhover + * + * @param currentPiece Piece to show hovering + * @param block block which the user is hovering over + * @param isHovering boolean which determines whether to hover or unhover + */ + public void setHover(GamePiece currentPiece, GameBlock block, boolean isHovering) { if (isPieceBoard) return; - if (this.hoveringBlock != null) { - this.hoveringBlock.setHovering(false); + int[][] pieceBlocks = currentPiece.getBlocks(); + int placeX = block.getX() - 1; + int placeY = block.getY() - 1; + + for (int x = 0; x < pieceBlocks.length; x++) { + for (int y = 0; y < pieceBlocks[x].length; y++) { + int value = pieceBlocks[x][y]; + if (value == 0) continue; + try { + blocks[x + placeX][y + placeY].setHovering(isHovering); + } catch (Exception ignored) { + } + } } + } + + /** + * Unhovers any blocks on the board + */ + public void unhover() { + for (GameBlock[] row : blocks) { + for (GameBlock block : row) { + block.setHovering(false); + } + } + } - this.hoveringBlock = block; - block.setHovering(isHovering); + public int getCols() { + return cols; } - public void fadeOutAnim(GameBlock block) { - block.startAnim(); + public int getRows() { + return rows; } + public Grid getGrid() { + return grid; + } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameLoopTimer.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameLoopTimer.java index 767d31fa1465557f404e03c18e9fdfda00ff0551..8a2b62490dcd1d130401f422a65056e2cc58dae9 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameLoopTimer.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameLoopTimer.java @@ -5,19 +5,30 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.util.Duration; -import java.security.Key; - +/** + * Timer for the game which resets at the end of each game loop or when the user places a block + */ public class GameLoopTimer extends Rectangle { double width; Timeline tl; KeyFrame[] frames; + /** + * Create a game loop timer + * + * @param width The width of the timer + */ public GameLoopTimer(double width) { this.width = width; setHeight(20); } + /** + * Creates the key frames of the timer animation + * + * @param duration The duration of the timer animation + */ public void setupFrames(int duration) { KeyValue widthVal = new KeyValue(this.widthProperty(), width); KeyFrame widthStart = new KeyFrame(Duration.ZERO, widthVal); @@ -29,17 +40,16 @@ public class GameLoopTimer extends Rectangle { KeyFrame greenStart = new KeyFrame(Duration.ZERO, colourVal); colourVal = new KeyValue(this.fillProperty(), Color.YELLOW); - KeyFrame yellowMid = new KeyFrame(Duration.millis(duration/2), colourVal); + KeyFrame yellowMid = new KeyFrame(Duration.millis(duration / 2), colourVal); colourVal = new KeyValue(this.fillProperty(), Color.RED); KeyFrame redEnd = new KeyFrame(Duration.millis(duration), colourVal); - frames = new KeyFrame[] {widthStart, widthFrame, greenStart, yellowMid, redEnd}; + frames = new KeyFrame[]{widthStart, widthFrame, greenStart, yellowMid, redEnd}; } public void startAnimation() { tl = new Timeline(frames); tl.play(); } - } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LoadingBoard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LetterBoard.java similarity index 59% rename from tetrecs/src/main/java/uk/ac/soton/comp1206/component/LoadingBoard.java rename to tetrecs/src/main/java/uk/ac/soton/comp1206/component/LetterBoard.java index e10808a7faf2cb984d0f87fbc220830271f20fe3..588e3b2eefacb7cac86bbc75e059820754e193e7 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LoadingBoard.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LetterBoard.java @@ -2,9 +2,13 @@ package uk.ac.soton.comp1206.component; import uk.ac.soton.comp1206.game.GamePiece; -public class LoadingBoard extends GameBoard{ - public LoadingBoard(double width, double height) { - super(5,5, width, height); +/** + * Letter board which is a 5x5 grid which displays a letter + * It is used in the loading screen and the menu to display the logos + */ +public class LetterBoard extends GameBoard { + public LetterBoard(double width, double height) { + super(5, 5, width, height); this.isPieceBoard = true; } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java index 1a8b872733e69851353703e1798479c7b754e41f..9d90e9c34ccb6a055f5cc2676eb27780070e1af4 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java @@ -3,6 +3,9 @@ package uk.ac.soton.comp1206.component; import uk.ac.soton.comp1206.event.BlockClickedListener; import uk.ac.soton.comp1206.game.GamePiece; +/** + * Piece board which displays the current and next pieces to play + */ public class PieceBoard extends GameBoard { public PieceBoard(double width, double height) { @@ -23,12 +26,19 @@ public class PieceBoard extends GameBoard { this.grid.clear(); } - public void setOnClick(BlockClickedListener handler) { - this.blockClickedListener = handler; + /** + * Set the listener to handle an event when the piece board is clicked + * + * @param listener listener to add + */ + public void setOnClick(BlockClickedListener listener) { + this.blockClickedListener = listener; } + /** + * Draws the centre circle on the piece + */ public void drawCentre() { this.blocks[1][1].setCentre(); } - } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/BoardHoverListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/BoardHoverListener.java new file mode 100644 index 0000000000000000000000000000000000000000..8119d7a83e0311dfd711736dd382993ec35be825 --- /dev/null +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/BoardHoverListener.java @@ -0,0 +1,18 @@ +package uk.ac.soton.comp1206.event; + +import uk.ac.soton.comp1206.component.GameBlock; + +/** + * The Board Hover listener is used to handle when a user hovers over a block in the game board. + * It passes the game block which is being hovered over the a boolean to determine whether to hover or unhover + */ +public interface BoardHoverListener { + + /** + * Handle a block hover event + * + * @param block the block that was clicked + * @param isHovering boolean which determines whether to hover or unhover + */ + void pieceHovering(GameBlock block, boolean isHovering); +} diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/BoardUpdatedListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/BoardUpdatedListener.java new file mode 100644 index 0000000000000000000000000000000000000000..b4013de624f0d5e37ff00d67f6573593f8d2ba24 --- /dev/null +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/BoardUpdatedListener.java @@ -0,0 +1,5 @@ +package uk.ac.soton.comp1206.event; + +public interface BoardUpdatedListener { + void updateBoard(String playerName, String[] values); +} diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameLoopListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameLoopListener.java index bccf5f939fda0218b05a5a4d1a549e0cb4f6d261..690e82a71b768f904b7c26cd36a53dd4bb46de2e 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameLoopListener.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameLoopListener.java @@ -1,5 +1,14 @@ package uk.ac.soton.comp1206.event; +/** + * The Game loop listener is used to listen for the end of a game loop + */ public interface GameLoopListener { + + /** + * Handles when a game loop is finished + * + * @param delay the delay of the game loop + */ void gameLoop(int delay); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameOverListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameOverListener.java index 6f3a397e7a9f1a2ad04f363a0aa919a8d086402c..dadc970ba260023b4cba8c9a4ac11f1c4a158125 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameOverListener.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/GameOverListener.java @@ -1,5 +1,9 @@ package uk.ac.soton.comp1206.event; + +/** + * The Game over listener is used to listen for when the game ends + */ public interface GameOverListener { void gameOver(); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/LineClearedListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/LineClearedListener.java index 14cfeb59ce88e1f2f08ad0de8753f2519735aa11..b2d5624c75779069722425af2b23dab761d2bcb6 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/LineClearedListener.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/LineClearedListener.java @@ -4,6 +4,16 @@ import uk.ac.soton.comp1206.component.GameBlockCoordinate; import java.util.HashSet; + +/** + * The line cleared listener is used to listen for when a line is cleared in the game + */ public interface LineClearedListener { + + /** + * Clears a line when the listener is called + * + * @param blocksToRemove Set of blocks which will be removed when the line is cleared + */ void lineCleared(HashSet<GameBlockCoordinate> blocksToRemove); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java index 77fc3c91c8c568c75114d240290192215a793db2..8b8ef2f2ad09346c7dd135c962a3b2c88d35aa39 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java @@ -2,6 +2,15 @@ package uk.ac.soton.comp1206.event; import uk.ac.soton.comp1206.game.GamePiece; +/** + * The next piece listener is used to handle when the next piece is spawned + */ public interface NextPieceListener { + + /** + * Handles the next piece spawned + * + * @param piece The next piece spawned which needs to be handled + */ void nextPiece(GamePiece piece); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java index 9b2c5abb20bc053d570ece6d50f1230353e4f2a8..a716819cbdcd636eb11178b6cbda15d05503b5b1 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java @@ -1,5 +1,16 @@ package uk.ac.soton.comp1206.event; +import uk.ac.soton.comp1206.component.GameBlock; + +/** + * The right click listener is used for listening to right clicks on the game board + */ public interface RightClickListener { - void rightClicked(); + + /** + * Handles a block right-clicked event + * + * @param block The block which has been right clicked + */ + void rightClicked(GameBlock block); } 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 index 18576c5c61d2a1308f8f3a97ea76bba5eaaa21ed..9a74229aacfd362e0b6b54507617054572d44378 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java @@ -1,6 +1,9 @@ package uk.ac.soton.comp1206.event; + +/** + * The score updated listener is used for listening to score updates in the game + */ public interface ScoreUpdatedListener { void updateScore(); - } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java index 5f6be97cccaca1aca26f53d6356187a06d1ee545..2873393fd36b8b2d8b4b237d44ec1092beb92e90 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java @@ -2,11 +2,16 @@ package uk.ac.soton.comp1206.game; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.event.BoardUpdatedListener; import java.util.Random; -public class ChallengeGame extends Game{ +/** + * Challenge game is the single player version of the game + */ +public class ChallengeGame extends Game { private static final Logger logger = LogManager.getLogger(ChallengeGame.class); + /** * Create a new game with the specified rows and columns. Creates a corresponding grid model. * @@ -17,6 +22,11 @@ public class ChallengeGame extends Game{ super(cols, rows); } + /** + * Method to spawn a piece in the Challenge game + * + * @return current piece + */ @Override public GamePiece spawnPiece() { Random rand = new Random(); @@ -25,6 +35,16 @@ public class ChallengeGame extends Game{ return piece; } + @Override + public void setScore(int score) { + logger.info("Score is now " + score); + this.score.set(score); + + if (this.scoreUpdatedListener != null) { + scoreUpdatedListener.updateScore(); + } + } + @Override public void sendChatMessage(String message) { } @@ -32,4 +52,8 @@ public class ChallengeGame extends Game{ @Override public void updateLeaderboard(String[] data) { } + + @Override + public void setBoardUpdatedListener(BoardUpdatedListener listener) { + } } 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 f3561547919ae8f5019ea7298b57c4b1c0f9e4e2..42f2f75c851442af5c1e9f22e31c2d3c5729e15a 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 @@ -1,6 +1,5 @@ package uk.ac.soton.comp1206.game; -import javafx.application.Platform; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; @@ -93,6 +92,7 @@ public abstract class Game { this.lives.set(3); this.multiplier.set(1); + //Spawns new pieces to set as the current and next piece this.nextPiece = spawnPiece(); nextPiece(); } @@ -107,7 +107,7 @@ public abstract class Game { int x = gameBlock.getX(); int y = gameBlock.getY(); - //Get the new value for this block + //Places the piece if it is possible and then restarts the game loop and spawns a new piece logger.info("Block clicked at x = {} y = {}", x, y); boolean placed = grid.playPiece(currentPiece, x, y); if (placed) { @@ -124,6 +124,9 @@ public abstract class Game { abstract GamePiece spawnPiece(); + /** + * Spawns a new piece to set as the next piece and changes the current piece + */ public void nextPiece() { this.currentPiece = this.nextPiece; this.nextPiece = spawnPiece(); @@ -134,21 +137,21 @@ public abstract class Game { logger.info("Current piece is : {}", this.currentPiece); logger.info("Next piece is: {}", this.nextPiece); - } + /** + * Checks the board after a piece is placed to check for any full lines + */ public void afterPiece() { ArrayList<Integer> fullCols = new ArrayList<>(); ArrayList<Integer> fullRows = new ArrayList<>(); HashSet<GameBlockCoordinate> blocksToClear = new HashSet<>(); - /* - * Check each vertical column to see if it is full - */ - for (int x = 0; x < grid.getCols(); x++) { + //Check each vertical column to see if it is full + for (int x = 0; x < this.cols; x++) { boolean full = true; - for (int y = 0; y < grid.getRows(); y++) { + for (int y = 0; y < this.rows; y++) { if (grid.get(x, y) == 0) { full = false; } @@ -158,12 +161,10 @@ public abstract class Game { } } - /* - * Check each horizontal row to see if it is full - */ - for (int y = 0; y < grid.getCols(); y++) { + //Check each horizontal row to see if it is full + for (int y = 0; y < this.cols; y++) { boolean full = true; - for (int x = 0; x < grid.getRows(); x++) { + for (int x = 0; x < this.rows; x++) { if (grid.get(x, y) == 0) { full = false; } @@ -175,11 +176,10 @@ public abstract class Game { int numGridBlocks = 0; - /* - * Removes all columns which are full - */ + + //Removes all columns which are full for (int x : fullCols) { - for (int y = 0; y < grid.getCols(); y++) { + for (int y = 0; y < this.cols; y++) { if (grid.get(x, y) != 0) { blocksToClear.add(new GameBlockCoordinate(x, y)); grid.set(x, y, 0); @@ -188,11 +188,9 @@ public abstract class Game { } } - /* - Removes all rows which are full - */ + //Removes all rows which are full for (int y : fullRows) { - for (int x = 0; x < grid.getCols(); x++) { + for (int x = 0; x < this.cols; x++) { if (grid.get(x, y) != 0) { blocksToClear.add(new GameBlockCoordinate(x, y)); grid.set(x, y, 0); @@ -201,10 +199,11 @@ public abstract class Game { } } - + //Calculates and sets the new score based on how many lines were cleared int newScore = score.get() + (fullCols.size() + fullRows.size()) * numGridBlocks * 10 * multiplier.get(); this.setScore(newScore); + //Sets the user level depending on their score this.setLevel(Math.floorDiv(this.score.get(), 1000)); if (fullCols.size() != 0 || fullRows.size() != 0) { @@ -216,17 +215,24 @@ public abstract class Game { } } + //Calls the line cleared listener which runs the line clear animation in the game scene if (this.lineClearedListener != null) { this.lineClearedListener.lineCleared(blocksToClear); } } + /** + * Swaps the current and next piece + */ public void swapPiece() { GamePiece tempPiece = this.currentPiece; this.currentPiece = this.nextPiece; this.nextPiece = tempPiece; } + /** + * Starts a game loop which decreases the user's lives and spawns a new piece when the timer finishes + */ public void startGameLoop() { timer = new Timer(); logger.info("Starting game loop with delay: {}", getTimerDelay()); @@ -237,13 +243,16 @@ public abstract class Game { } }; - timer.schedule(gameLoopTask , getTimerDelay()); + timer.schedule(gameLoopTask, getTimerDelay()); if (this.gameLoopListener != null) { this.gameLoopListener.gameLoop(getTimerDelay()); } } + /** + * Runs when the game loop timer finishes + */ public void gameLoop() { if (this.multiplier.get() > 1) { this.setMultiplier(1); @@ -261,16 +270,25 @@ public abstract class Game { } } + /** + * Restarts the game loop + */ public void restartLoop() { stopTimer(); startGameLoop(); } + /** + * Stops the game loop timer + */ public void stopTimer() { timer.cancel(); timer.purge(); } + /** + * Calls the game over listener to end the game + */ public void endGame() { if (this.gameOverListener != null) { this.gameOverListener.gameOver(); @@ -304,22 +322,45 @@ public abstract class Game { return rows; } + /** + * Get the current piece + * + * @return current piece + */ public GamePiece getCurrentPiece() { return currentPiece; } + /** + * Get the next piece + * + * @return next piece + */ public GamePiece getNextPiece() { return nextPiece; } + /** + * Gets the leaderboard of scores in a multiplayer game + * + * @return leaderboard list + */ public ObservableList<Pair<String, Integer>> getLeaderboardList() { return leaderboardList; } + /** + * Gets the chat message sent in a multiplayer game + * + * @return chat message + */ public SimpleStringProperty getChatMessage() { return chatMessage; } + /** + * Method to send a chat message + */ public abstract void sendChatMessage(String message); public void setNextPieceListener(NextPieceListener listener) { @@ -342,6 +383,9 @@ public abstract class Game { this.scoreUpdatedListener = listener; } + /** + * Rotates a piece based on the number of rotations + */ public void rotatePiece(int rotations) { this.currentPiece.rotate(rotations); } @@ -366,23 +410,27 @@ public abstract class Game { return myName; } + /** + * Sets the user's name + */ public void setMyName(String name) { this.myName.set(name); } + /** + * Get the game loop timer delay + * + * @return timer delay + */ public int getTimerDelay() { return Math.max(2500, 12000 - 500 * this.level.get()); } - public void setScore(int score) { - logger.info("Score is now " + score); - this.score.set(score); - - if (this.scoreUpdatedListener != null) { - scoreUpdatedListener.updateScore(); - } - } + abstract void setScore(int score); + /** + * Sets the user's level + */ public void setLevel(int level) { if (level > this.level.get()) { logger.info("Increasing level to " + level); @@ -391,11 +439,17 @@ public abstract class Game { this.level.set(level); } + /** + * Decreases the user's lives + */ public void decreaseLives(int lives) { Multimedia.playSound("lifelose.wav"); this.lives.set(lives); } + /** + * Sets the multiplier + */ public void setMultiplier(int multiplier) { if (multiplier != 1) { logger.info("Increasing multiplier to " + multiplier); @@ -405,5 +459,7 @@ public abstract class Game { this.multiplier.set(multiplier); } - public abstract void updateLeaderboard(String[] data); + abstract void updateLeaderboard(String[] data); + + public abstract void setBoardUpdatedListener(BoardUpdatedListener listener); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java index d7fa689ab321eba3dc0d6ce57282c2907865aca5..8e33490ae429bd32f75beaee6451ac3d53272daf 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java @@ -132,39 +132,39 @@ public class GamePiece { throw new IndexOutOfBoundsException("No such piece: " + piece); } - public static GamePiece createLoadingPiece(String letter, int colour) { + public static GamePiece createLetterPiece(String letter, int colour) { switch (letter) { case "T" -> { - int[][] blocks = {{1,0,0,0,0},{1,0,0,0,0},{1,1,1,1,1},{1,0,0,0,0},{1,0,0,0,0}}; + int[][] blocks = {{1, 0, 0, 0, 0}, {1, 0, 0, 0, 0}, {1, 1, 1, 1, 1}, {1, 0, 0, 0, 0}, {1, 0, 0, 0, 0}}; return new GamePiece("T", blocks, colour); } case "E" -> { - int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,1,0,1},{1,0,1,0,1},{0,0,0,0,0}}; - return new GamePiece("E", blocks,colour); + int[][] blocks = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}, {1, 0, 1, 0, 1}, {1, 0, 1, 0, 1}, {0, 0, 0, 0, 0}}; + return new GamePiece("E", blocks, colour); } case "R" -> { - int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,1,0,0},{1,0,1,1,0},{1,1,0,1,1}}; - return new GamePiece("R", blocks,colour); + int[][] blocks = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}, {1, 0, 1, 0, 0}, {1, 0, 1, 1, 0}, {1, 1, 0, 1, 1}}; + return new GamePiece("R", blocks, colour); } case "C" -> { - int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,0,0,1},{1,0,0,0,1},{0,0,0,0,0}}; - return new GamePiece("C", blocks,colour); + int[][] blocks = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}, {1, 0, 0, 0, 1}, {1, 0, 0, 0, 1}, {0, 0, 0, 0, 0}}; + return new GamePiece("C", blocks, colour); } case "S" -> { - int[][] blocks = {{0,0,0,0,0},{1,1,1,0,1},{1,0,1,0,1},{1,0,1,1,1},{0,0,0,0,0}}; - return new GamePiece("S", blocks,colour); + int[][] blocks = {{0, 0, 0, 0, 0}, {1, 1, 1, 0, 1}, {1, 0, 1, 0, 1}, {1, 0, 1, 1, 1}, {0, 0, 0, 0, 0}}; + return new GamePiece("S", blocks, colour); } case "M" -> { - int[][] blocks = {{1,1,1,1,1},{1,0,0,0,0},{0,1,1,0,0},{1,0,0,0,0},{1,1,1,1,1}}; - return new GamePiece("M", blocks,colour); + int[][] blocks = {{1, 1, 1, 1, 1}, {1, 0, 0, 0, 0}, {0, 1, 1, 0, 0}, {1, 0, 0, 0, 0}, {1, 1, 1, 1, 1}}; + return new GamePiece("M", blocks, colour); } case "A" -> { - int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,1,0,0},{1,1,1,1,1},{0,0,0,0,0}}; - return new GamePiece("A", blocks,colour); + int[][] blocks = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}, {1, 0, 1, 0, 0}, {1, 1, 1, 1, 1}, {0, 0, 0, 0, 0}}; + return new GamePiece("A", blocks, colour); } case "G" -> { - int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,0,0,1},{1,0,1,0,1},{1,0,1,1,1}}; - return new GamePiece("G", blocks,colour); + int[][] blocks = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}, {1, 0, 0, 0, 1}, {1, 0, 1, 0, 1}, {1, 0, 1, 1, 1}}; + return new GamePiece("G", blocks, colour); } } @@ -185,9 +185,9 @@ public class GamePiece { return newPiece; } - public static GamePiece createLoadingPiece(String piece,int colour, int rotation) { - var newPiece = createLoadingPiece(piece, colour); - newPiece.rotateLoadingBoards(rotation); + public static GamePiece createLetterPiece(String piece, int colour, int rotation) { + var newPiece = createLetterPiece(piece, colour); + newPiece.rotateLetterBoards(rotation); return newPiece; } @@ -262,22 +262,22 @@ public class GamePiece { blocks = rotated; } - public void rotateLoadingBoards(int rotations) { + public void rotateLetterBoards(int rotations) { for (int rotated = 0; rotated < rotations; rotated++) { - rotateLoadingBoards(); + rotateLetterBoards(); } } - public void rotateLoadingBoards() { + public void rotateLetterBoards() { int[][] result = new int[blocks.length][]; - for(int i = 0; i < blocks.length; i++) { + for (int i = 0; i < blocks.length; i++) { result[i] = new int[blocks[i].length]; System.arraycopy(blocks[i], 0, result[i], 0, blocks[i].length); } - for(int x = 0; x < 5; x++) { - for(int y = 0; y < 5; y++) { + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { blocks[x][y] = result[5 - y - 1][x]; } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java index 133f8973e970894317c3e1e6d446c994849161c8..aa368b77d383bd49a53215bc6a3b5e493bf2a717 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java @@ -95,6 +95,13 @@ public class Grid { } } + /** + * Checks whether a piece can be played + * + * @param gamePiece the piece to be played + * @param placeX the x-coordinate of the piece to be placed + * @param placeY the y-coordinate of the piece to be placed + */ public boolean canPlayPiece(GamePiece gamePiece, int placeX, int placeY) { int[][] blocks = gamePiece.getBlocks(); placeX = placeX - 1; @@ -112,6 +119,13 @@ public class Grid { return true; } + /** + * Places a piece if it is possible + * + * @param gamePiece the piece to be played + * @param placeX the x-coordinate of the piece to be placed + * @param placeY the y-coordinate of the piece to be placed + */ public boolean playPiece(GamePiece gamePiece, int placeX, int placeY) { if (!this.canPlayPiece(gamePiece, placeX, placeY)) return false; @@ -129,6 +143,9 @@ public class Grid { return true; } + /** + * Clears the grid + */ public void clear() { for (int y = 0; y < this.rows; y++) { for (int x = 0; x < this.cols; x++) { diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java index 5458649e0449f630aa9c8387d84a672e15d04200..c98de7abd3465c95bfee4238aa43309448f4b6e1 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java @@ -4,6 +4,8 @@ import javafx.application.Platform; import javafx.util.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.component.GameBlock; +import uk.ac.soton.comp1206.event.BoardUpdatedListener; import uk.ac.soton.comp1206.network.Communicator; import uk.ac.soton.comp1206.utility.Multimedia; @@ -12,7 +14,10 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.Random; -public class MultiplayerGame extends Game{ +/** + * The multiplayer version of the game + */ +public class MultiplayerGame extends Game { private static final Logger logger = LogManager.getLogger(MultiplayerGame.class); private Communicator communicator; @@ -20,9 +25,13 @@ public class MultiplayerGame extends Game{ private ArrayDeque<GamePiece> pieces; private boolean gameStarted = false; + private BoardUpdatedListener boardUpdatedListener = null; + /** - * Create a new game with the specified rows and columns. Creates a corresponding grid model. + * Create a new game with the specified rows, columns and a communicator to communicate with the server. + * Creates a corresponding grid model. * + * @param communicator The communicator which sends and receives messages from the server * @param cols number of columns * @param rows number of rows */ @@ -42,16 +51,21 @@ public class MultiplayerGame extends Game{ this.multiplier.set(1); this.pieces = new ArrayDeque<>(); + //Communicator message receive listener this.communicator.addListener(data -> { Platform.runLater(() -> receiveCommunication(data)); }); this.communicator.send("SCORES"); + //Spawns three pieces initially for (int i = 0; i < 3; i++) { this.communicator.send("PIECE"); } } + /** + * Receives the message from the server and calls the relevant method to handle the message + */ private void receiveCommunication(String message) { String[] components = message.split(" ", 2); switch (components[0]) { @@ -79,13 +93,27 @@ public class MultiplayerGame extends Game{ gameStarted = true; } } + case "BOARD" -> { + String[] boardVals = components[1].split(":", 2); + if (!boardVals[0].equals(myName.get())) { + if (boardUpdatedListener != null) { + boardUpdatedListener.updateBoard(boardVals[0],boardVals[1].split(" ")); + } + } + } } } + /** + * Sends a SCORE message to the server which updates the user's score + */ private void updateScore() { this.communicator.send("SCORE " + this.getScore().get()); } + /** + * Parses the message received from the server and displays it in the chat + */ private void parseMessage(String[] data) { if (data.length > 1) { chatMessage.set("[" + data[0] + "]:" + data[1]); @@ -94,6 +122,9 @@ public class MultiplayerGame extends Game{ } } + /** + * Updates the leaderboard with the user's score and position + */ public void updateLeaderboard(String[] score) { int i = -1; for (Pair<String, Integer> pair : leaderboardList) { @@ -119,6 +150,9 @@ public class MultiplayerGame extends Game{ } } + /** + * Loads all the current scores and adds it to the leaderboard list + */ private void loadScores(String[] scores) { onlineScores.clear(); for (String score : scores) { @@ -142,10 +176,32 @@ public class MultiplayerGame extends Game{ leaderboardList.addAll(this.onlineScores); } + /** + * Sends a chat message + */ public void sendChatMessage(String message) { this.communicator.send("MSG " + message); } + @Override + public void blockClicked(GameBlock gameBlock) { + super.blockClicked(gameBlock); + this.communicator.send("BOARD" + boardToString()); + } + + public String boardToString() { + StringBuilder board = new StringBuilder(); + for (int x = 0; x < this.cols; x++) { + for (int y = 0; y < this.rows; y++) { + board.append(" ").append(this.grid.get(x, y)); + } + } + return board.toString(); + } + + /** + * Spawns a piece by sending a PARSE message to the server + */ @Override public GamePiece spawnPiece() { logger.info("Spawning Piece"); @@ -161,4 +217,7 @@ public class MultiplayerGame extends Game{ this.updateScore(); } + public void setBoardUpdatedListener(BoardUpdatedListener listener) { + this.boardUpdatedListener = listener; + } } 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 360ffc22b206e4dba4b21c5174b92f7274dc9c18..e4e4163b129a9109d06b3cde5e8ed58b14c456e4 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,10 +1,9 @@ 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.HPos; import javafx.geometry.Pos; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; @@ -47,7 +46,7 @@ public class ChallengeScene extends BaseScene { protected HBox header; protected VBox gameInfo; - private IntegerProperty hiscore = new SimpleIntegerProperty(0); + private final IntegerProperty hiscore = new SimpleIntegerProperty(0); /** * Create a new Single Player challenge scene @@ -70,6 +69,7 @@ public class ChallengeScene extends BaseScene { this.hiscore.set(getHighScore()); + //Sets the listeners for the game this.game.setNextPieceListener(this::nextPiece); this.game.setLineClearedListener(this::lineCleared); this.game.setGameLoopListener(this::gameTimer); @@ -85,8 +85,12 @@ public class ChallengeScene extends BaseScene { }); this.game.start(); + this.board.unhover(); } + /** + * Updates the high score shown in game if a user beats it + */ private void updateHiScore() { if (this.hiscore.get() < this.game.getScore().get()) { logger.info("Updating Highscore"); @@ -94,8 +98,13 @@ public class ChallengeScene extends BaseScene { } } - private void keyHandler(KeyEvent e) { - this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), false); + /** + * Handles a key pressed event to add keyboard support for the game + * + * @param e the key which is pressed + */ + protected void keyHandler(KeyEvent e) { + this.board.setHover(this.game.getCurrentPiece(), this.board.getBlock(keyBoardPosX, keyBoardPosY), false); if (e.getCode().equals(KeyCode.ESCAPE)) { endGame(); this.gameWindow.startMenu(); @@ -104,9 +113,9 @@ public class ChallengeScene extends BaseScene { } else if (e.getCode().equals(KeyCode.SPACE) || e.getCode().equals(KeyCode.R)) { this.swapPiece(); } else if (e.getCode().equals(KeyCode.E) || e.getCode().equals(KeyCode.C) || e.getCode().equals(KeyCode.CLOSE_BRACKET)) { - this.rotateBlock(1); + this.rotateBlock(this.board.getBlock(keyBoardPosX, keyBoardPosY), 1); } else if (e.getCode().equals(KeyCode.Q) || e.getCode().equals(KeyCode.Z) || e.getCode().equals(KeyCode.OPEN_BRACKET)) { - this.rotateBlock(3); + this.rotateBlock(this.board.getBlock(keyBoardPosX, keyBoardPosY), 3); } else if (e.getCode().equals(KeyCode.UP) || e.getCode().equals(KeyCode.W)) { if (keyBoardPosY > 0) keyBoardPosY--; } else if (e.getCode().equals(KeyCode.DOWN) || e.getCode().equals(KeyCode.S)) { @@ -115,13 +124,8 @@ public class ChallengeScene extends BaseScene { if (keyBoardPosX > 0) keyBoardPosX--; } else if (e.getCode().equals(KeyCode.RIGHT) || e.getCode().equals(KeyCode.D)) { if (keyBoardPosX < this.game.getCols() - 1) keyBoardPosX++; - } else if (e.getCode().equals(KeyCode.SHIFT)) { - endGame(); - Platform.runLater(() -> { - this.gameWindow.showScores(this.game); - }); } - this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), true); + this.board.setHover(this.game.getCurrentPiece(), this.board.getBlock(keyBoardPosX, keyBoardPosY), true); } /** @@ -148,15 +152,16 @@ public class ChallengeScene extends BaseScene { HBox header = buildHeader(); mainPane.setTop(header); + //Builds the boards of the scene and includes the game information HBox boards = buildBoards(); mainPane.setCenter(boards); + //Instantiates a new game loop timer timer = new GameLoopTimer(this.gameWindow.getWidth()); mainPane.setBottom(timer); } public HBox buildHeader() { - //Header of Challenge Scene header = new HBox(); //VBox to hold the Score title and value @@ -198,12 +203,15 @@ public class ChallengeScene extends BaseScene { HBox boards = new HBox(); this.board = new GameBoard(this.game.getGrid(), gameWindow.getWidth() / 2, gameWindow.getWidth() / 2); - //Handle block on gameboard grid being clicked + + //Handle block on gameboard grid being clicked or hovered this.board.setOnBlockClick(this::blockClicked); this.board.setOnRightClick(this::rotateBlock); + this.board.setOnHover(this::hoverPiece); boards.getChildren().add(this.board); + //VBox to hold all game information such as high score, user level etc. gameInfo = new VBox(); Text hiScoreTitle = new Text("High Score"); @@ -215,19 +223,37 @@ public class ChallengeScene extends BaseScene { hiScoreVal.getStyleClass().add("hiscore"); gameInfo.getChildren().add(hiScoreVal); + GridPane levelAndMult = new GridPane(); + levelAndMult.setAlignment(Pos.CENTER); + levelAndMult.setHgap(10); + gameInfo.getChildren().add(levelAndMult); + Text levelTitle = new Text("Level"); levelTitle.getStyleClass().add("heading"); - gameInfo.getChildren().add(levelTitle); + levelAndMult.add(levelTitle, 0, 0); Text levelVal = new Text(); levelVal.textProperty().bind(this.game.getLevel().asString()); levelVal.getStyleClass().add("level"); - gameInfo.getChildren().add(levelVal); + levelAndMult.add(levelVal, 0, 1); + + Text multTitle = new Text("Multiplier"); + multTitle.getStyleClass().add("heading"); + levelAndMult.add(multTitle, 1, 0); + + Text multValue = new Text(); + multValue.textProperty().bind(this.game.getMultiplier().asString()); + multValue.getStyleClass().add("level"); + levelAndMult.add(multValue, 1, 1); + + GridPane.setHalignment(multValue, HPos.CENTER); + GridPane.setHalignment(levelVal, HPos.CENTER); Text incomingTitle = new Text("Incoming"); incomingTitle.getStyleClass().add("heading"); gameInfo.getChildren().add(incomingTitle); + //Piece boards to display the current and next piece this.currentPieceBoard = new PieceBoard(gameWindow.getWidth() / 6, gameWindow.getWidth() / 6); this.currentPieceBoard.setOnClick(this::rotateBlock); this.currentPieceBoard.drawCentre(); @@ -249,6 +275,11 @@ public class ChallengeScene extends BaseScene { return boards; } + /** + * Runs the game loop timer animation + * + * @param delay how long the timer runs for + */ protected void gameTimer(int delay) { logger.info("Running game loop animation"); timer.setupFrames(delay); @@ -259,12 +290,30 @@ public class ChallengeScene extends BaseScene { swapPiece(); } + /** + * Swaps the current and next piece so that it is displayed in the correct board + */ protected void swapPiece() { logger.info("Swapping current and next piece"); + this.board.unhover(); Multimedia.playSound("rotate.wav"); this.currentPieceBoard.displayPiece(this.game.getNextPiece()); this.nextPieceBoard.displayPiece(this.game.getCurrentPiece()); this.game.swapPiece(); + this.board.setHover(this.game.getCurrentPiece(), this.board.getBlock(keyBoardPosX, keyBoardPosY), true); + } + + /** + * Shows the current piece hovering on the grid + * + * @param block hovers the piece with this block as the centre + * @param isHovering boolean which determines whether to hover or unhover a piece + */ + protected void hoverPiece(GameBlock block, boolean isHovering) { + this.board.setHover(this.game.getCurrentPiece(), this.board.getBlock(keyBoardPosX, keyBoardPosY), false); + this.keyBoardPosX = block.getX(); + this.keyBoardPosY = block.getY(); + this.board.setHover(this.game.getCurrentPiece(), block, isHovering); } /** @@ -278,21 +327,32 @@ public class ChallengeScene extends BaseScene { game.blockClicked(gameBlock); } - protected void rotateBlock(int rotations) { + /** + * Rotates a piece + * + * @param block rotates the piece about the block clicked + * @param rotations number of rotations + */ + protected void rotateBlock(GameBlock block, int rotations) { + this.hoverPiece(block, false); logger.info("Rotating block"); Multimedia.playSound("rotate.wav"); this.game.rotatePiece(rotations); + this.hoverPiece(block, true); this.currentPieceBoard.displayPiece(this.game.getCurrentPiece()); } protected void rotateBlock(GameBlock gameBlock) { - rotateBlock(1); - } - - protected void rotateBlock() { - rotateBlock(1); + this.hoverPiece(gameBlock, false); + rotateBlock(gameBlock, 1); + this.hoverPiece(gameBlock, true); } + /** + * Runs the line cleared animation + * + * @param blocksToRemove Set which holds all the blocks to remove + */ protected void lineCleared(HashSet<GameBlockCoordinate> blocksToRemove) { if (blocksToRemove.size() == 0) return; Multimedia.playSound("clear.wav"); @@ -301,6 +361,11 @@ public class ChallengeScene extends BaseScene { } } + /** + * Gets the current local high score + * + * @return high score + */ private int getHighScore() { ArrayList<Pair<String, Integer>> localScores = ScoresScene.loadScores(); return localScores.get(0).getValue(); @@ -316,8 +381,13 @@ public class ChallengeScene extends BaseScene { game = new ChallengeGame(5, 5); } + /** + * Method to handle when a new piece is spawned + */ protected void nextPiece(GamePiece nextPiece) { logger.info("Next piece to place: " + nextPiece); + this.board.unhover(); + this.board.setHover(nextPiece, this.board.getBlock(keyBoardPosX, keyBoardPosY), true); this.currentPieceBoard.displayPiece(nextPiece); this.nextPieceBoard.displayPiece(this.game.getNextPiece()); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java index 98e620f41a97fd1940c41fb4faa86c6b01a93562..56757513543dd380d815d97d6c2627d4a2a6c8b0 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java @@ -16,20 +16,19 @@ import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; 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.PieceBoard; -import uk.ac.soton.comp1206.game.Game; import uk.ac.soton.comp1206.game.GamePiece; import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; import uk.ac.soton.comp1206.utility.Multimedia; -public class InstructionScene extends BaseScene{ +public class InstructionScene extends BaseScene { private static final Logger logger = LogManager.getLogger(InstructionScene.class); private int pieceNumber = 0; private PieceBoard pieceBoard; + /** * Create a new scene, passing in the GameWindow the scene will be displayed in * @@ -41,11 +40,17 @@ public class InstructionScene extends BaseScene{ logger.info("Creating Instructions Scene"); } + /** + * Initialise the instructions screen + */ @Override public void initialise() { this.scene.setOnKeyPressed(this::escapeToMenu); } + /** + * Build the instructions screen layout + */ @Override public void build() { logger.info("Building " + this.getClass().getName()); @@ -91,12 +96,19 @@ public class InstructionScene extends BaseScene{ mainPane.setCenter(widgets); } + /** + * Creates a HBox which dynamically shows each Game piece + * Includes right and left arrows which can be used to cycle through the pieces + * + * @return pieceBoardLayout Horizontal layout box + */ private HBox buildPieceBoards() { HBox pieceBoardLayout = new HBox(); - Canvas prevArrow = new Canvas(this.gameWindow.getWidth()/18, this.gameWindow.getHeight()/18); + Canvas prevArrow = new Canvas(this.gameWindow.getWidth() / 18, this.gameWindow.getHeight() / 18); prevArrow.getGraphicsContext2D().setFill(Color.WHITE); - prevArrow.getGraphicsContext2D().fillPolygon(new double[]{prevArrow.getWidth()/2, prevArrow.getWidth(), prevArrow.getWidth()}, new double[]{prevArrow.getHeight()/2, 0, prevArrow.getHeight()}, 3); + prevArrow.getGraphicsContext2D().fillPolygon(new double[]{prevArrow.getWidth() / 2, prevArrow.getWidth(), prevArrow.getWidth()}, new double[]{prevArrow.getHeight() / 2, 0, prevArrow.getHeight()}, 3); + //Sets it to display the previous piece if left arrow is clicked prevArrow.setOnMouseClicked(this::displayPrevPiece); pieceBoardLayout.getChildren().add(prevArrow); @@ -105,9 +117,10 @@ public class InstructionScene extends BaseScene{ pieceBoard.displayPiece(piece); pieceBoardLayout.getChildren().add(pieceBoard); - Canvas nextArrow = new Canvas(this.gameWindow.getWidth()/18, this.gameWindow.getHeight()/18); + Canvas nextArrow = new Canvas(this.gameWindow.getWidth() / 18, this.gameWindow.getHeight() / 18); nextArrow.getGraphicsContext2D().setFill(Color.WHITE); - nextArrow.getGraphicsContext2D().fillPolygon(new double[]{0, nextArrow.getWidth()/2, 0}, new double[] {0, nextArrow.getHeight()/2, nextArrow.getHeight()}, 3); + nextArrow.getGraphicsContext2D().fillPolygon(new double[]{0, nextArrow.getWidth() / 2, 0}, new double[]{0, nextArrow.getHeight() / 2, nextArrow.getHeight()}, 3); + //Sets it to display the next piece if right arrow is clicked nextArrow.setOnMouseClicked(this::displayNextPiece); pieceBoardLayout.getChildren().add(nextArrow); @@ -115,9 +128,11 @@ public class InstructionScene extends BaseScene{ pieceBoardLayout.setSpacing(20); return pieceBoardLayout; - } + /** + * Displays the next piece in the cycle, loops around if all pieces are shown + */ private void displayNextPiece(MouseEvent event) { Multimedia.playSound("rotate.wav"); if (pieceNumber != 14) { @@ -130,6 +145,9 @@ public class InstructionScene extends BaseScene{ pieceBoard.displayPiece(piece); } + /** + * Displays the previous piece in the cycle, loops around if at the start + */ private void displayPrevPiece(MouseEvent event) { Multimedia.playSound("rotate.wav"); if (pieceNumber != 0) { diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java index 0a82b16a433db0068b73244b5341f92bdee2c16c..1277bf9cfe69ffa656a40084f491ee74ddf79f49 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java @@ -2,7 +2,6 @@ package uk.ac.soton.comp1206.scene; import javafx.animation.ScaleTransition; import javafx.application.Platform; -import javafx.event.ActionEvent; import javafx.geometry.Pos; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; @@ -11,7 +10,7 @@ import javafx.scene.layout.VBox; import javafx.util.Duration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import uk.ac.soton.comp1206.component.LoadingBoard; +import uk.ac.soton.comp1206.component.LetterBoard; import uk.ac.soton.comp1206.game.GamePiece; import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; @@ -21,19 +20,20 @@ import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; -public class LoadingScene extends BaseScene{ +/** + * The initial loading screen of the game. Plays an animation which displays the ECS Games logo. + */ +public class LoadingScene extends BaseScene { private static final Logger logger = LogManager.getLogger(LoadingScene.class); - private Timer timer; private Timer pieceTimer; private int pieceNum = 0; private VBox titleBox; - private ArrayList<LoadingBoard> boards = new ArrayList<>(); - private int[] rotations = {2,2,3,3,2,3,3,4}; - private int[] colours = {8,8,8,10,10,10,10,10}; + private final ArrayList<LetterBoard> boards = new ArrayList<>(); + private final int[] rotations = {2, 2, 3, 3, 2, 3, 3, 4}; /** - * Create a new scene, passing in the GameWindow the scene will be displayed in + * Create a new Loading screen scene, passing in the GameWindow the scene will be displayed in * * @param gameWindow the game window */ @@ -41,6 +41,9 @@ public class LoadingScene extends BaseScene{ super(gameWindow); } + /** + * Initialise the loading screen + */ @Override public void initialise() { pieceTimer = new Timer(); @@ -58,6 +61,9 @@ public class LoadingScene extends BaseScene{ pieceTimer.scheduleAtFixedRate(pieceTask, 0, 850); } + /** + * Build the loading screen layout, includes a VBox which holds grids which represent each letter in the ECS Games logo + */ @Override public void build() { logger.info("Building " + this.getClass().getName()); @@ -73,6 +79,7 @@ public class LoadingScene extends BaseScene{ BorderPane mainPane = new BorderPane(); loadingPane.getChildren().add(mainPane); + //Vertical Layout box holds the grids used to show the letters of the ECS Games logo titleBox = new VBox(); titleBox.setAlignment(Pos.CENTER); titleBox.setSpacing(20); @@ -82,9 +89,10 @@ public class LoadingScene extends BaseScene{ ecs.setAlignment(Pos.CENTER); ecs.setSpacing(5); + //For loop used to add 3 grids on the top for (int i = 0; i < 3; i++) { - LoadingBoard grid = new LoadingBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12); - ecs .getChildren().add(grid); + LetterBoard grid = new LetterBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12); + ecs.getChildren().add(grid); boards.add(grid); } @@ -92,8 +100,9 @@ public class LoadingScene extends BaseScene{ games.setAlignment(Pos.CENTER); games.setSpacing(5); + //Second for loop used to add 5 grids on the bottom for (int i = 0; i < 5; i++) { - LoadingBoard grid = new LoadingBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12); + LetterBoard grid = new LetterBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12); games.getChildren().add(grid); boards.add(grid); } @@ -102,21 +111,30 @@ public class LoadingScene extends BaseScene{ titleBox.getChildren().add(games); } + /** + * Method which is used to place a letter piece in the grids of the loading screen + * + * @param rotations The number of rotations on the piece + */ public void placePiece(int rotations) throws InterruptedException { - String[] letters= {"E","C","S","G","A","M","E","S"}; - GamePiece piece = GamePiece.createLoadingPiece(letters[pieceNum], colours[pieceNum], rotations); + String[] letters = {"E", "C", "S", "G", "A", "M", "E", "S"}; + int[] colours = {8, 8, 8, 10, 10, 10, 10, 10}; + //Creates a new letter piece with a given colour and number of rotations + GamePiece piece = GamePiece.createLetterPiece(letters[pieceNum], colours[pieceNum], rotations); boards.get(pieceNum).displayPiece(piece); + //Dynamically animates the letter rotating using a for loop for (int i = 0; i < 4 - rotations; i++) { Thread.sleep(200); Multimedia.playSound("rotate.wav"); - piece.rotateLoadingBoards(1); + piece.rotateLetterBoards(1); boards.get(pieceNum).displayPiece(piece); } Multimedia.playSound("place.wav"); - + //pieceNum is a counter variable which counts how many letters have been placed so far pieceNum++; + //Cancels the repeating timer when all letters have been shown if (pieceNum == 8) { pieceTimer.cancel(); pieceTimer.purge(); @@ -124,17 +142,24 @@ public class LoadingScene extends BaseScene{ } } + /** + * Plays the starting intro sound and scales the ECS Games logo to fit the screen + */ public void startSound() { Multimedia.playSound("intro.mp3"); - ScaleTransition st = new ScaleTransition(Duration.millis(6000),titleBox); + //Scale transition to enlarge the logo whilst the sound is playing + ScaleTransition st = new ScaleTransition(Duration.millis(6000), titleBox); st.setToX(1.5); st.setToY(1.5); st.play(); - st.setOnFinished(e -> {this.toMenu();}); + //Goes to the main menu when the animation is finished + st.setOnFinished(e -> this.toMenu()); } - + /** + * Goes to the main menu + */ public void toMenu() { Platform.runLater(this.gameWindow::startMenu); } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java index b9859579cbe1bf714ae641ff0de5a7175ae19cb9..30647ca25f4bc925293378ed806972becdc4a5e2 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java @@ -1,15 +1,12 @@ package uk.ac.soton.comp1206.scene; import javafx.animation.FadeTransition; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; import javafx.application.Platform; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; -import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.input.KeyCode; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.text.Text; @@ -22,13 +19,18 @@ 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.Timer; import java.util.TimerTask; -public class LobbyScene extends BaseScene{ + +/** + * The lobby scene holds the UI for the multiplayer lobby where players can join or create multiplayer games + */ +public class LobbyScene extends BaseScene { private static final Logger logger = LogManager.getLogger(LobbyScene.class); - private Communicator communicator; + private final Communicator communicator; private Timer timer; private VBox currentGames; @@ -39,10 +41,13 @@ public class LobbyScene extends BaseScene{ private Label infoBox; private boolean isInChannel = false; - private SimpleStringProperty myName = new SimpleStringProperty(""); + private final SimpleStringProperty myName = new SimpleStringProperty(""); + private final ArrayList<Text> currentChannels = new ArrayList<>(); + private String myChannel; + private ArrayList<String> players = new ArrayList<>(); /** - * Create a new scene, passing in the GameWindow the scene will be displayed in + * Create a new lobby scene, passing in the GameWindow the scene will be displayed in * * @param gameWindow the game window */ @@ -53,16 +58,18 @@ public class LobbyScene extends BaseScene{ logger.info("Creating lobby scene"); } + /** + * Initialises the lobby + */ @Override public void initialise() { this.scene.setOnKeyPressed(e -> { if (e.getCode().equals(KeyCode.ESCAPE)) backToMenu(); }); - this.communicator.addListener(data -> { - Platform.runLater(() -> receiveCommunication(data)); - }); + this.communicator.addListener(data -> Platform.runLater(() -> receiveCommunication(data))); + //Timer which checks for new multiplayer channels every 2 seconds timer = new Timer(); TimerTask task = new TimerTask() { @Override @@ -73,6 +80,9 @@ public class LobbyScene extends BaseScene{ timer.scheduleAtFixedRate(task, 0, 2000); } + /** + * Method which handles any messages received from the server + */ public void receiveCommunication(String message) { String[] components = message.split(" ", 2); switch (components[0]) { @@ -89,6 +99,7 @@ public class LobbyScene extends BaseScene{ } break; case "JOIN": + myChannel = components[1]; setupChat(); isInChannel = true; break; @@ -103,16 +114,16 @@ public class LobbyScene extends BaseScene{ case "HOST": { Button start = new Button("Start"); buttons.getChildren().add(0, start); - start.setOnAction(e -> { - startGame(); - }); + start.setOnAction(e -> startGame()); break; } case "USERS": { playerBox.getChildren().clear(); + players.clear(); Multimedia.playSound("message.wav"); String[] names = components[1].split("\n"); for (String name : names) { + players.add(name); Text playerName = new Text(name); if (name.equals(myName.get())) { playerName.getStyleClass().add("myname"); @@ -142,22 +153,28 @@ public class LobbyScene extends BaseScene{ timer.cancel(); timer.purge(); } - this.gameWindow.startMultiplayerGame(this.myName.get()); + this.players.remove(myName.get()); + this.gameWindow.startMultiplayerGame(this.myName.get(), this.players.toArray(new String[players.size()])); } break; case "ERROR": { - if (components[1].equals("Channel already exists")) { - infoBox.setText("Error: There is already a game with that channel name!"); - infoBox.getStyleClass().add("instructions"); - infoBox.setTextFill(Color.RED); - } else if (components[1].equals("That name is already in use")) { - infoBox.setText("Error: That name is already in use!"); - infoBox.getStyleClass().add("instructions"); - infoBox.setTextFill(Color.RED); - } else if (components[1].equals("You are already in a channel")) { - infoBox.setText("Error: You are already in a channel!"); - infoBox.getStyleClass().add("instructions"); - infoBox.setTextFill(Color.RED); + Multimedia.playSound("fail.wav"); + switch (components[1]) { + case "Channel already exists" -> { + infoBox.setText("Error: There is already a game with that channel name!"); + infoBox.getStyleClass().add("instructions"); + infoBox.setTextFill(Color.RED); + } + case "That name is already in use" -> { + infoBox.setText("Error: That name is already in use!"); + infoBox.getStyleClass().add("instructions"); + infoBox.setTextFill(Color.RED); + } + case "You are already in a channel" -> { + infoBox.setText("Error: You are already in a channel!"); + infoBox.getStyleClass().add("instructions"); + infoBox.setTextFill(Color.RED); + } } FadeTransition ft = new FadeTransition(Duration.millis(5000), infoBox); @@ -170,35 +187,75 @@ public class LobbyScene extends BaseScene{ } } + /** + * Sends a START message to start the game if the user is the host + */ private void startGame() { this.communicator.send("START"); } + /** + * Requests for all current channels by sending a LIST message to the server + */ private void requestCurrentChannels() { this.communicator.send("LIST"); } + /** + * Sends a request to create a channel + */ private void sendCreateRequest(String channelName) { this.communicator.send("CREATE " + channelName); } + /** + * Method to leave a channel + */ private void leaveChannel() { logger.info("Leaving channel"); isInChannel = false; + myChannel = ""; this.communicator.send("PART"); + for (Text channel : currentChannels) { + channel.getStyleClass().remove("selected"); + } } + /** + * Method to join a channel + */ private void joinChannel(String channelName) { if (!isInChannel) { logger.info("Joining channel: {}", channelName); this.communicator.send("JOIN " + channelName); + for (Text channel : currentChannels) { + if (channel.getText().equals(channelName)) { + channel.getStyleClass().add("selected"); + } + } + } else { + Multimedia.playSound("fail.wav"); + infoBox.setText("Error: You are already in a channel!"); + infoBox.getStyleClass().add("instructions"); + infoBox.setTextFill(Color.RED); + + FadeTransition ft = new FadeTransition(Duration.millis(5000), infoBox); + ft.setFromValue(1); + ft.setToValue(0); + ft.play(); } } + /** + * Sends a chat message to the server with the MSG command + */ private void sendMessage(String message) { this.communicator.send("MSG " + message); } + /** + * Parses any chat messages received and adds it to the chat + */ private void parseMessage(String[] data) { Multimedia.playSound("message.wav"); Text message = new Text(); @@ -211,11 +268,17 @@ public class LobbyScene extends BaseScene{ messages.getChildren().add(message); } + /** + * Changes the user's nickname with the NICK command + */ private void changeNickName(String name) { logger.info("Changing nickname to {}", name); this.communicator.send("NICK " + name); } + /** + * Creates the UI for when the user wants to create a channel + */ private void startCreateOptions() { if (!isInChannel) { createGame.getChildren().clear(); @@ -237,21 +300,30 @@ public class LobbyScene extends BaseScene{ infoBox.setWrapText(true); createGame.getChildren().add(infoBox); + //If the user presses enter after typing the channel name, it sends a CREATE request channelName.setOnKeyPressed(e -> { if (e.getCode().equals(KeyCode.ENTER)) { sendCreateRequest(channelName.getText()); } }); - submit.setOnAction(e -> { - sendCreateRequest(channelName.getText()); - }); + //If the user clicks submit after typing the channel name, it sends a CREATE request + submit.setOnAction(e -> sendCreateRequest(channelName.getText())); } } + /** + * Sets up the chat UI for when a user joins a channel + */ private void setupChat() { createGame.getChildren().clear(); + for (Text channel : currentChannels) { + if (channel.getText().equals(myChannel)) { + channel.getStyleClass().add("selected"); + } + } + playerBox = new HBox(); playerBox.setSpacing(5); playerBox.getStyleClass().add("playerBox"); @@ -265,7 +337,7 @@ public class LobbyScene extends BaseScene{ Text nickInstructions = new Text("Use /nick [NewName] to change username"); welcome.getStyleClass().add("instructions"); nickInstructions.getStyleClass().add("instructions"); - instructions.getChildren().addAll(welcome,nickInstructions); + instructions.getChildren().addAll(welcome, nickInstructions); ScrollPane chatBox = new ScrollPane(); chatBox.getStyleClass().add("scroller"); @@ -305,9 +377,7 @@ public class LobbyScene extends BaseScene{ Button leave = new Button("Leave"); - leave.setOnAction(e -> { - this.leaveChannel(); - }); + leave.setOnAction(e -> this.leaveChannel()); buttons.getChildren().add(leave); buttons.setAlignment(Pos.CENTER); @@ -315,6 +385,9 @@ public class LobbyScene extends BaseScene{ createGame.getChildren().add(buttons); } + /** + * Builds the main lobby scene UI + */ @Override public void build() { logger.info("Building " + this.getClass().getName()); @@ -360,6 +433,7 @@ public class LobbyScene extends BaseScene{ hostGameTitle.setOnMouseClicked(e -> this.startCreateOptions()); createGameVBox.getChildren().add(hostGameTitle); + //VBox to hold all the current channels currentGames = new VBox(); currentGames.setAlignment(Pos.CENTER); currentGames.setSpacing(10); @@ -368,6 +442,7 @@ public class LobbyScene extends BaseScene{ currentGames.setMinHeight(500); currentGameVBox.getChildren().add(currentGames); + //VBox to hold the UI where a user can create a channel or type in game chat createGame = new VBox(); createGame.getStyleClass().add("gameBox"); createGame.setMinWidth(350); @@ -382,17 +457,24 @@ public class LobbyScene extends BaseScene{ mainPane.setCenter(widgets); } + + /** + * Shows all current games when a CHANNELS message is received + */ public void showCurrentGames(String[] channels) { logger.info("Current number of available games: {}", channels.length); + currentChannels.clear(); currentGames.getChildren().clear(); for (String channel : channels) { Text channelName = new Text(channel); channelName.getStyleClass().add("channelItem"); - channelName.setOnMouseClicked(e -> { - this.joinChannel(channelName.getText()); - }); + if (channel.equals(myChannel)) { + channelName.getStyleClass().add("selected"); + } + currentChannels.add(channelName); + channelName.setOnMouseClicked(e -> this.joinChannel(channelName.getText())); currentGames.getChildren().add(channelName); } } 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 a0ea97303eb39c952015487a11ff671d04e27ad8..673bd3a4ed7ab0680f3e41ea44888651281a060b 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 @@ -1,25 +1,21 @@ package uk.ac.soton.comp1206.scene; -import javafx.event.EventHandler; +import javafx.animation.*; import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; -import javafx.scene.media.MediaPlayer; import javafx.scene.text.Text; -import javafx.util.Pair; +import javafx.util.Duration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import uk.ac.soton.comp1206.App; -import uk.ac.soton.comp1206.component.LoadingBoard; +import uk.ac.soton.comp1206.component.LetterBoard; import uk.ac.soton.comp1206.game.GamePiece; import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; import uk.ac.soton.comp1206.utility.Multimedia; - /** * The main menu of the game. Provides a gateway to the rest of the game. */ @@ -27,6 +23,8 @@ public class MenuScene extends BaseScene { private static final Logger logger = LogManager.getLogger(MenuScene.class); + private HBox title; + /** * Create a new menu scene * @@ -57,8 +55,8 @@ public class MenuScene extends BaseScene { VBox menuWidgets = new VBox(); - //Awful title - HBox title = getGridTitle(); + //Title is a Horizontal layout box which holds 7 grids to represent each letter of the 'Tetrecs' logo + title = getGridTitle(); menuWidgets.getChildren().add(title); VBox menuButtons = new VBox(); @@ -96,7 +94,6 @@ public class MenuScene extends BaseScene { menuWidgets.setSpacing(150); mainPane.setCenter(menuWidgets); - } /** @@ -104,50 +101,104 @@ public class MenuScene extends BaseScene { */ @Override public void initialise() { - Multimedia.playBackgroundMusic("menuMusic.wav", true); + //Plays background music on loop + Multimedia.playBackgroundMusic("menu.mp3", true); + + //Runs an animation on the title logo + runAnimation(); + + //Shuts down the App if the Escape key is pressed this.scene.setOnKeyPressed(e -> { if (e.getCode().equals(KeyCode.ESCAPE)) shutDown(); }); } /** - * Handle when the Start Game button is pressed - * + * Creates the main logo by creating 7 grids to represent each letter of the Tetrecs logo */ - private void startGame(MouseEvent event) { - Multimedia.stopSound(); - gameWindow.startChallenge(); - } - private HBox getGridTitle() { HBox grids = new HBox(); grids.setSpacing(5); grids.setAlignment(Pos.CENTER); - String[] letters = {"T","E","T","R","E","C","S"}; - int[] colours = {2,3,4,5,10,13,15}; + String[] letters = {"T", "E", "T", "R", "E", "C", "S"}; + int[] colours = {2, 3, 4, 5, 10, 13, 15}; for (int i = 0; i < 7; i++) { - LoadingBoard lb = new LoadingBoard(gameWindow.getWidth() / 8, gameWindow.getWidth() / 8); - lb.displayPiece(GamePiece.createLoadingPiece(letters[i], colours[i])); + LetterBoard lb = new LetterBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12); + lb.displayPiece(GamePiece.createLetterPiece(letters[i], colours[i])); grids.getChildren().add(lb); } return grids; } + /** + * Runs an animation on the main logo with rotations and scale transitions + */ + private void runAnimation() { + ScaleTransition st = new ScaleTransition(new Duration(2500), title); + st.setToX(1.5); + st.setToY(1.5); + st.play(); + + RotateTransition rt = new RotateTransition(new Duration(2000),title); + rt.setFromAngle(0); + rt.setToAngle(-7.5); + st.setOnFinished(e -> rt.play()); + + RotateTransition rt2 = new RotateTransition(new Duration(2000),title); + rt2.setFromAngle(-7.5); + rt2.setToAngle(7.5); + rt.setOnFinished(e -> rt2.play()); + + RotateTransition rt3 = new RotateTransition(new Duration(2000),title); + rt3.setFromAngle(7.5); + rt3.setToAngle(0); + rt2.setOnFinished(e -> rt3.play()); + + ScaleTransition st2 = new ScaleTransition(new Duration(2500),title); + st2.setToX(1); + st2.setToY(1); + rt3.setOnFinished(e -> st2.play()); + + st2.setOnFinished(e -> st.play()); + + } + + /** + * Handle when the Start Game button is pressed + */ + private void startGame(MouseEvent event) { + Multimedia.stopSound(); + gameWindow.startChallenge(); + } + + /** + * Handle when Multiplayer button is pressed + */ private void startLobby(MouseEvent event) { gameWindow.startLobby(); } + /** + * Handle when the Instructions button is pressed + */ private void showInstructions(MouseEvent event) { gameWindow.displayInstructions(); } + /** + * Handle when the Options button is pressed + */ private void showOptions(MouseEvent event) { gameWindow.showOptions(); } + /** + * Handle when Exit is pressed + */ private void shutDown() { App.getInstance().shutdown(); } + } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java index 95c2a92d02db3b6a54c897d4eab2219d3d338b0a..fc3e846017294b79920980fc7b20c58639f1aeea 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java @@ -4,26 +4,36 @@ import javafx.application.Platform; import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleStringProperty; import javafx.geometry.Pos; +import javafx.scene.canvas.Canvas; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import javafx.scene.text.Text; 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.GameBoard; -import uk.ac.soton.comp1206.component.PieceBoard; +import uk.ac.soton.comp1206.component.LetterBoard; +import uk.ac.soton.comp1206.game.Grid; import uk.ac.soton.comp1206.game.MultiplayerGame; import uk.ac.soton.comp1206.network.Communicator; import uk.ac.soton.comp1206.ui.GameWindow; import uk.ac.soton.comp1206.ui.Leaderboard; import uk.ac.soton.comp1206.utility.Multimedia; +import java.util.ArrayList; +import java.util.Arrays; -public class MultiplayerScene extends ChallengeScene{ +/** + * The multiplayer scene holds the UI for the multiplayer game + */ +public class MultiplayerScene extends ChallengeScene { private static final Logger logger = LogManager.getLogger(MultiplayerScene.class); private boolean chatMode = false; @@ -32,17 +42,26 @@ public class MultiplayerScene extends ChallengeScene{ private final Communicator communicator; private SimpleStringProperty myName = new SimpleStringProperty(""); private Leaderboard leaderboard; + private String[] players; + private ArrayList<VBox> playerBoards = new ArrayList<>(); + private ArrayList<GameBoard> playerGrids = new ArrayList<>(); + private HBox boardDisplay; + private int boardNumber = 0; /** - * Create a new Single Player challenge scene + * Create a new multiplayer challenge scene * * @param gameWindow the Game Window */ - public MultiplayerScene(GameWindow gameWindow, String name) { + public MultiplayerScene(GameWindow gameWindow, String name, String[] players) { super(gameWindow); logger.info("Starting Multiplayer Scene"); this.myName.set(name); + this.players = players; + for (String player : players) { + System.out.println(player); + } this.communicator = gameWindow.getCommunicator(); } @@ -55,6 +74,8 @@ public class MultiplayerScene extends ChallengeScene{ this.game.setNextPieceListener(this::nextPiece); this.game.setLineClearedListener(this::lineCleared); this.game.setGameLoopListener(this::gameTimer); + this.game.setBoardUpdatedListener(this::updatePlayerBoard); + //Sets the username based on the username entered in the lobby this.game.setMyName(this.myName.get()); this.scene.setOnKeyPressed(this::keyHandler); @@ -71,15 +92,42 @@ public class MultiplayerScene extends ChallengeScene{ }); this.game.start(); + this.board.unhover(); + } + + private void updatePlayerBoard(String name, String[] values) { + logger.info("Updating the board of : {}", name); + System.out.println(Arrays.toString(values)); + int index = -1; + for(int i = 0; i < players.length; i++) { + if(players[i].equals(name)) { + index = i; + break; + } + } + + if (index != -1) { + GameBoard board = playerGrids.get(index); + int counter = 0; + for (int x = 0; x < board.getCols(); x++) { + for (int y = 0; y < board.getRows(); y++) { + board.getGrid().set(x,y, Integer.parseInt(values[counter])); + counter++; + } + } + } } @Override public void setupGame() { logger.info("Starting a new multiplayer game"); - game = new MultiplayerGame(this.communicator,5, 5); + game = new MultiplayerGame(this.communicator, 5, 5); } + /** + * Handles communcation received from the server + */ private void receiveCommunication(String message) { String[] components = message.split(" "); switch (components[0]) { @@ -108,6 +156,42 @@ public class MultiplayerScene extends ChallengeScene{ leaderboard.getScores().bind(new SimpleListProperty<>(this.game.getLeaderboardList())); gameInfo.getChildren().add(0, leaderboard); + for (int i = 0; i < this.players.length; i++) { + LetterBoard playerBoard = new LetterBoard(gameWindow.getWidth() / 8, gameWindow.getWidth() / 8); + playerGrids.add(playerBoard); + + Text playerName = new Text(players[i]); + playerName.getStyleClass().add("leaderboard"); + playerName.setFill(GameBlock.COLOURS[i+1]); + + VBox player = new VBox(); + player.setAlignment(Pos.CENTER); + player.setSpacing(5); + player.getChildren().addAll(playerBoard, playerName); + + playerBoards.add(player); + } + + boardDisplay = new HBox(); + boardDisplay.setAlignment(Pos.CENTER); + boardDisplay.setSpacing(10); + + Canvas prevArrow = new Canvas(this.gameWindow.getWidth() / 20, this.gameWindow.getHeight() / 20); + prevArrow.getGraphicsContext2D().setFill(Color.WHITE); + prevArrow.getGraphicsContext2D().fillPolygon(new double[]{prevArrow.getWidth() / 2, prevArrow.getWidth(), prevArrow.getWidth()}, new double[]{prevArrow.getHeight() / 2, 0, prevArrow.getHeight()}, 3); + prevArrow.setOnMouseClicked(this::displayPrevBoard); + boardDisplay.getChildren().add(prevArrow); + + boardDisplay.getChildren().add(playerBoards.get(0)); + + Canvas nextArrow = new Canvas(this.gameWindow.getWidth() / 20, this.gameWindow.getHeight() / 20); + nextArrow.getGraphicsContext2D().setFill(Color.WHITE); + nextArrow.getGraphicsContext2D().fillPolygon(new double[]{0, nextArrow.getWidth() / 2, 0}, new double[]{0, nextArrow.getHeight() / 2, nextArrow.getHeight()}, 3); + nextArrow.setOnMouseClicked(this::displayNextBoard); + boardDisplay.getChildren().add(nextArrow); + + gameInfo.getChildren().add(boardDisplay); + VBox chatAndTimer = new VBox(); VBox chatMessages = new VBox(); @@ -118,6 +202,7 @@ public class MultiplayerScene extends ChallengeScene{ chatMessages.getChildren().add(chatMessage); chatAndTimer.getChildren().add(chatMessages); + //Adds a chat text field to allow users to message during the game chatInput = new TextField(); chatInput.getStyleClass().add("TextField"); chatInput.setVisible(false); @@ -131,8 +216,37 @@ public class MultiplayerScene extends ChallengeScene{ } - private void keyHandler(KeyEvent e) { - this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), false); + public void displayPrevBoard(MouseEvent e) { + if (playerBoards.size() > 1) { + if (boardNumber != 0) { + boardNumber--; + } else { + boardNumber = this.players.length - 1; + } + + boardDisplay.getChildren().remove(1); + boardDisplay.getChildren().add(1, playerBoards.get(boardNumber)); + } + } + + public void displayNextBoard(MouseEvent e) { + if (playerBoards.size() > 1) { + if (boardNumber != this.players.length - 1) { + boardNumber++; + } else { + boardNumber = 0; + } + + boardDisplay.getChildren().remove(1); + boardDisplay.getChildren().add(1, playerBoards.get(boardNumber)); + } + } + + + /** + * Handles key pressed and checks whether it was pressed in the chat field or in game + */ + protected void keyHandler(KeyEvent e) { if (e.getCode().equals(KeyCode.T)) { chatMode = true; chatInput.setVisible(true); @@ -145,32 +259,7 @@ public class MultiplayerScene extends ChallengeScene{ chatMode = false; chatInput.setVisible(false); } else if (!chatMode) { - if (e.getCode().equals(KeyCode.ESCAPE)) { - endGame(); - this.gameWindow.startMenu(); - } else if (e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.X)) { - this.blockClicked(this.board.getBlock(keyBoardPosX, keyBoardPosY)); - } else if (e.getCode().equals(KeyCode.SPACE) || e.getCode().equals(KeyCode.R)) { - this.swapPiece(); - } else if (e.getCode().equals(KeyCode.E) || e.getCode().equals(KeyCode.C) || e.getCode().equals(KeyCode.CLOSE_BRACKET)) { - this.rotateBlock(1); - } else if (e.getCode().equals(KeyCode.Q) || e.getCode().equals(KeyCode.Z) || e.getCode().equals(KeyCode.OPEN_BRACKET)) { - this.rotateBlock(3); - } else if (e.getCode().equals(KeyCode.UP) || e.getCode().equals(KeyCode.W)) { - if (keyBoardPosY > 0) keyBoardPosY--; - } else if (e.getCode().equals(KeyCode.DOWN) || e.getCode().equals(KeyCode.S)) { - if (keyBoardPosY < this.game.getRows() - 1) keyBoardPosY++; - } else if (e.getCode().equals(KeyCode.LEFT) || e.getCode().equals(KeyCode.A)) { - if (keyBoardPosX > 0) keyBoardPosX--; - } else if (e.getCode().equals(KeyCode.RIGHT) || e.getCode().equals(KeyCode.D)) { - if (keyBoardPosX < this.game.getCols() - 1) keyBoardPosX++; - } else if (e.getCode().equals(KeyCode.SHIFT)) { - endGame(); - Platform.runLater(() -> { - this.gameWindow.showScores(this.game); - }); - } - this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), true); + super.keyHandler(e); } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java index 00326c2c55b52e30cff3ae5421c35ec5db9d23ea..fcd2c7980eaad721867245ddf39ad3e0b2587a15 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java @@ -2,7 +2,6 @@ package uk.ac.soton.comp1206.scene; import javafx.beans.property.DoubleProperty; import javafx.geometry.Pos; -import javafx.scene.control.Label; import javafx.scene.control.Slider; import javafx.scene.input.KeyCode; import javafx.scene.layout.BorderPane; @@ -16,12 +15,13 @@ import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; import uk.ac.soton.comp1206.utility.Multimedia; -public class OptionsScene extends BaseScene{ +public class OptionsScene extends BaseScene { private static final Logger logger = LogManager.getLogger(OptionsScene.class); - private static Slider musicVolumeSlider = new Slider(0,1,0.5); + //Instantiates two sliders to changes the volume of music and sound effects + private static final Slider musicVolumeSlider = new Slider(0, 1, 0.5); + private static final Slider soundVolumeSlider = new Slider(0, 1, 0.5); - private static Slider soundVolumeSlider = new Slider(0,1,0.5); /** * Create a new scene, passing in the GameWindow the scene will be displayed in * @@ -31,6 +31,9 @@ public class OptionsScene extends BaseScene{ super(gameWindow); } + /** + * Initialise the options scene + */ @Override public void initialise() { this.scene.setOnKeyPressed(e -> { @@ -40,6 +43,9 @@ public class OptionsScene extends BaseScene{ }); } + /** + * Builds the options screen layout + */ @Override public void build() { logger.info("Building " + this.getClass().getName()); @@ -61,7 +67,7 @@ public class OptionsScene extends BaseScene{ mainPane.setTop(widgets); Text optionsTitle = new Text("Game Options"); - optionsTitle .getStyleClass().add("title"); + optionsTitle.getStyleClass().add("title"); widgets.getChildren().add(optionsTitle); HBox options = new HBox(); @@ -106,10 +112,16 @@ public class OptionsScene extends BaseScene{ soundOptions.getChildren().add(soundVolumeSlider); } + /** + * Method to return the value property of the music volume slider + */ public static DoubleProperty getMusicLevel() { return musicVolumeSlider.valueProperty(); } + /** + * Method to return the value property of the sound volume slider + */ public static DoubleProperty getSoundLevel() { return soundVolumeSlider.valueProperty(); } 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 333c8ebfe55ac69a26d63ce0ad6fe71cfe32c867..7df61150ebbdc10fa495c694b1223a0f7c459611 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 @@ -28,7 +28,11 @@ import uk.ac.soton.comp1206.utility.Multimedia; import java.io.*; import java.util.*; -public class ScoresScene extends BaseScene{ + +/** + * The scene which displays the local and online highscores + */ +public class ScoresScene extends BaseScene { private static final Logger logger = LogManager.getLogger(ScoresScene.class); private final Game game; @@ -54,6 +58,7 @@ public class ScoresScene extends BaseScene{ * Create a new scene, passing in the GameWindow the scene will be displayed in * * @param gameWindow the game window + * @param game The game which the scores will be taken from */ public ScoresScene(GameWindow gameWindow, Game game) { super(gameWindow); @@ -76,6 +81,9 @@ public class ScoresScene extends BaseScene{ loadOnlineScores(); } + /** + * Builds the UI for the scores scene + */ @Override public void build() { logger.info("Building " + this.getClass().getName()); @@ -132,6 +140,9 @@ public class ScoresScene extends BaseScene{ mainPane.setCenter(widgets); } + /** + * Checks whether the user's score is a local or online high score + */ public void checkHiScore() { logger.info("Checking for High score"); isLocalHiScore = false; @@ -163,6 +174,7 @@ public class ScoresScene extends BaseScene{ } + //If it is a high score, the user will be asked to enter their username which is then added to the scores.txt file and the online leaderboard if (isLocalHiScore || isOnlineHiScore) { logger.info("User has got a high score"); @@ -224,8 +236,11 @@ public class ScoresScene extends BaseScene{ } } + /** + * Adds the user's score to either or both of the local and online score lists + */ public void parseHiScore(int score, String name) { - if (this.isLocalHiScore){ + if (this.isLocalHiScore) { logger.info("User has a local high score"); this.localList.setName(name); this.localScores.add(new Pair<>(name, score)); @@ -267,6 +282,9 @@ public class ScoresScene extends BaseScene{ } } + /** + * Handles communication received + */ public void receiveCommunication(String message) { logger.info("Received online scores"); String[] components = message.split(" "); @@ -284,6 +302,9 @@ public class ScoresScene extends BaseScene{ } } + /** + * Loads the online scores received from the server + */ public void parseOnlineScores(String scores) { logger.info("Loading online scores"); onlineScores = new ArrayList<>(); @@ -315,25 +336,31 @@ public class ScoresScene extends BaseScene{ this.communicator.send("HISCORES"); } + /** + * Sends the new high score to the server with the HISCORE command + */ public void writeOnlineScore(Pair<String, Integer> pair) { if (pair != null) { this.communicator.send("HISCORE " + pair.getKey() + ":" + pair.getValue()); } } + /** + * Loads the local high scores from a text file + */ public static ArrayList<Pair<String, Integer>> loadScores() { logger.info("Loading scores from file"); ArrayList<Pair<String, Integer>> localScores = new ArrayList<>(); - try{ - File f = new File("localscores.txt"); + try { + File f = new File("scores.txt"); if (!f.exists()) { writeDefaultScores(); } - BufferedReader br = new BufferedReader(new FileReader("localscores.txt")); + BufferedReader br = new BufferedReader(new FileReader("scores.txt")); String line = br.readLine(); int lineCounter = 0; - while(line != null) { + while (line != null) { lineCounter++; if (lineCounter <= 10) { String[] values = line.split(":"); @@ -345,7 +372,7 @@ public class ScoresScene extends BaseScene{ } br.close(); - } catch(IOException e) { + } catch (IOException e) { logger.info("Error reading file: " + e.getMessage()); } @@ -365,12 +392,15 @@ public class ScoresScene extends BaseScene{ return localScores; } + /** + * Writes default scores to a text file if it does not exist + */ public static void writeDefaultScores() { try { logger.info("Writing default scores to file"); - BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("localscores.txt"))); - for (int i = 10; i > 0; i --) { - bw.write("test" + i + ":" + (1000*i)); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("scores.txt"))); + for (int i = 10; i > 0; i--) { + bw.write("test" + i + ":" + (1000 * i)); bw.newLine(); } bw.close(); @@ -379,11 +409,14 @@ public class ScoresScene extends BaseScene{ } } + /** + * Writes any new high scores to the local text file + */ public static 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"))); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("scores.txt"))); for (Pair<String, Integer> nameScore : nameScores) { bw.write(nameScore.getKey() + ":" + nameScore.getValue()); diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java index 18541f1a31f5ff1c974717d1042f28f27c385d0d..d9058c9beee9e69d5702b747c7e7f083c6fb4886 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java @@ -13,6 +13,8 @@ import uk.ac.soton.comp1206.game.Game; import uk.ac.soton.comp1206.network.Communicator; import uk.ac.soton.comp1206.scene.*; +import java.util.ArrayList; + /** * The GameWindow is the single window for the game where everything takes place. To move between screens in the game, * we simply change the scene. @@ -75,9 +77,13 @@ public class GameWindow { Font.loadFont(getClass().getResourceAsStream("/style/Orbitron-ExtraBold.ttf"), 32); } + /** + * Display the single player challenge + */ public void startLoadingScene() { loadScene(new LoadingScene(this)); } + /** * Display the main menu */ @@ -92,24 +98,42 @@ public class GameWindow { loadScene(new ChallengeScene(this)); } + /** + * Display the multiplayer lobby + */ public void startLobby() { loadScene(new LobbyScene(this)); } + /** + * Display the game options + */ public void showOptions() { loadScene(new OptionsScene(this)); } + /** + * Display the game instructions + */ public void displayInstructions() { loadScene(new InstructionScene(this)); } + /** + * Display the highscore leaderboards + * @param game The current instance of the game being played + */ public void showScores(Game game) { loadScene(new ScoresScene(this, game)); } - public void startMultiplayerGame(String name) { - loadScene(new MultiplayerScene(this, name)); + /** + * Display the multiplayer game + * @param name The name of the user + * @param players + */ + public void startMultiplayerGame(String name, String[] players) { + loadScene(new MultiplayerScene(this, name, players)); } /** diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java index 59a43f1339be4cc7dad73c3582da0579ab67f644..46e3d9d3ac163a871de5f4184a52749a7b9a8d5a 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java @@ -4,13 +4,17 @@ import javafx.scene.text.Text; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - +/** + * Leaderboard which is displayed during the multiplayer game + */ public class Leaderboard extends ScoresList { private static final Logger logger = LogManager.getLogger(Leaderboard.class); public Leaderboard(String listName) { super(listName); setVisible(true); + getStyleClass().remove("scorelist"); + getStyleClass().add("leaderboard"); } @Override @@ -19,6 +23,9 @@ public class Leaderboard extends ScoresList { reveal(); } + /** + * Adds any dead players to the dead players list who are then crossed out in the leaderboard + */ public void setDead(String player) { logger.info("Setting {} as dead", player); for (Text nameScore : nameScores) { 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 index 847e02b8ac85aea5928a19857961cef1eb41b96a..a579e97c24da90bed342b34d18c3468464ff6456 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java @@ -17,7 +17,9 @@ import uk.ac.soton.comp1206.component.GameBlock; import java.util.ArrayList; - +/** + * Custom VBox component which dislays the list of high scores and animates the reveal + */ public class ScoresList extends VBox { private static final Logger logger = LogManager.getLogger(ScoresList.class); @@ -41,6 +43,7 @@ public class ScoresList extends VBox { title.getStyleClass().add("heading"); getChildren().add(title); + //Listener added to the score list to allow list changes when the high scores are updated in the Scores scene this.scores.addListener((ListChangeListener<? super Pair<String, Integer>>) e -> updateList()); } @@ -76,6 +79,9 @@ public class ScoresList extends VBox { } + /** + * Animates the reveal of the highscores + */ public void reveal() { logger.info("Playing reveal animations"); diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java index e7d93bd3fc0be724f3daeeb14860c32c0f882344..6b0733dbf336b7f1258abd030b6a08f195be94f8 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java @@ -14,11 +14,18 @@ public class Multimedia { private static MediaPlayer mediaPlayer; private static MediaPlayer backgroundPlayer; + /** + * Plays the background music specified and loops the music if required + * + * @param music Name of the music to play + * @param toLoop Boolean which specifies whether to loop the music or not + */ public static void playBackgroundMusic(String music, boolean toLoop) { String toPlay = Multimedia.class.getResource("/music/" + music).toExternalForm(); try { Media play = new Media(toPlay); backgroundPlayer = new MediaPlayer(play); + //Volume property is bound to the volume slider in the Options Scene backgroundPlayer.volumeProperty().bind(OptionsScene.getMusicLevel()); if (toLoop) { backgroundPlayer.setOnEndOfMedia(() -> backgroundPlayer.seek(Duration.ZERO)); @@ -31,12 +38,18 @@ public class Multimedia { } } + /** + * Plays the sound effect specified + * + * @param sound Name of the sound to play + */ public static void playSound(String sound) { String toPlay = Multimedia.class.getResource("/sounds/" + sound).toExternalForm(); try { Media play = new Media(toPlay); mediaPlayer = new MediaPlayer(play); + //Volume property is bound to the volume slider in the Options Scene mediaPlayer.volumeProperty().bind(OptionsScene.getSoundLevel()); mediaPlayer.play(); logger.info("Playing sound clip " + sound); @@ -46,6 +59,9 @@ public class Multimedia { } } + /** + * Stops all currently playing sounds and music + */ public static void stopSound() { logger.info("Stopping all current audio tracks"); if (mediaPlayer != null) { diff --git a/tetrecs/src/main/resources/music/menu.mp3 b/tetrecs/src/main/resources/music/menu.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0724b65ee300790b53ed7f4c79a800e934343feb Binary files /dev/null and b/tetrecs/src/main/resources/music/menu.mp3 differ diff --git a/tetrecs/src/main/resources/music/menuMusic.wav b/tetrecs/src/main/resources/music/menuMusic.wav deleted file mode 100644 index 2647529951c66f4f4ae01acd99863950421dbf0c..0000000000000000000000000000000000000000 Binary files a/tetrecs/src/main/resources/music/menuMusic.wav and /dev/null differ diff --git a/tetrecs/src/main/resources/style/game.css b/tetrecs/src/main/resources/style/game.css index 7eaa86d49f739d47afa78bf19dc24049935f01cc..cdc04b4c4fd6698e95913606defa32daa8213a30 100644 --- a/tetrecs/src/main/resources/style/game.css +++ b/tetrecs/src/main/resources/style/game.css @@ -31,6 +31,7 @@ } .menuItem:hover { + -fx-font-size: 40px; -fx-fill: yellow; } @@ -167,12 +168,19 @@ -fx-border-color: black; -fx-stroke: black; } + +.channelItem:hover { + -fx-fill: yellow; + -fx-font-size: 19px; +} + .channelItem.selected { -fx-fill: yellow; } .leaderboard { -fx-font-size: 16px; + -fx-font-family: 'Orbitron'; } .gameBox { diff --git a/tetrecs/target/classes/style/game.css b/tetrecs/target/classes/style/game.css index 7eaa86d49f739d47afa78bf19dc24049935f01cc..cdc04b4c4fd6698e95913606defa32daa8213a30 100644 --- a/tetrecs/target/classes/style/game.css +++ b/tetrecs/target/classes/style/game.css @@ -31,6 +31,7 @@ } .menuItem:hover { + -fx-font-size: 40px; -fx-fill: yellow; } @@ -167,12 +168,19 @@ -fx-border-color: black; -fx-stroke: black; } + +.channelItem:hover { + -fx-fill: yellow; + -fx-font-size: 19px; +} + .channelItem.selected { -fx-fill: yellow; } .leaderboard { -fx-font-size: 16px; + -fx-font-family: 'Orbitron'; } .gameBox { diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/App.class b/tetrecs/target/classes/uk/ac/soton/comp1206/App.class index 12974a99eeb4c3a50e2b5650cf110fc7ece1930d..98f4ae41e0fafb5288f74acf9b966e8ffa34091c 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/App.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/App.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/Launcher.class b/tetrecs/target/classes/uk/ac/soton/comp1206/Launcher.class index 08ed8e567f0f1a1eff2b0e281e15023c0acef18f..19de7470ce7a8b4c7c790d2701db7c24c212af38 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/Launcher.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/Launcher.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBlock.class b/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBlock.class index 5771b6ac8d29f0f260915e1c06460801f1c85e94..1afded4985df6fd0253eb7f48398768f79e44ec0 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBlock.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBlock.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class b/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class index d5094e13923c7a4fdb86af94e0052d0ce1b781f4..325cfc903bfec7a76d91ce58bc5f9c47ee9c9e79 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class differ 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 f0d85b6d2071a4fce55b16b5054c42edf2566ea3..49f28932822c89436d630a2b5e1af553554df8a2 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/game/GamePiece.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class index d26528c24acac20afdd51a29bf9c9d9407242059..aab24c40d0aec23893c553e54c2626906f975f47 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class index dda7f01ba1ac9ba52ef54d6cb3a478b7f95a46ae..2afe0bd3f309d1027929e6c255402b2acbbaf29f 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.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 ad0c9db41e584f034dae888bb8ee27054e6fcf6f..f2a7d845976c792e5c2329a5f0576b1b3aa6d75b 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 7453b0dfc02dd676f8cbeffd489f15aef967de33..df70c59d88bf5315c30e69cbb29c8066baf0e86c 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 diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class b/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class index 8521ce32017ef17e94b77b506033a31d869ed4c6..604a75a158347706aa1a858c0accaa53fa9b594b 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class differ