commit 0cccefdb9ccad85f48dada9629888297fcccbdf5 Author: Oli Date: Fri Mar 10 10:58:11 2023 +0000 Updated for 2023 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4b3715 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.iml +.DS_Store +.classpath +.factorypath +.idea +.idea/ +.project +.settings +.settings/ +scores.txt +target +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0132729 --- /dev/null +++ b/pom.xml @@ -0,0 +1,142 @@ + + 4.0.0 + uk.ac.soton.comp1206 + tetrecs + 1.0-SNAPSHOT + + UTF-8 + 19 + 19 + 21-ea+5 + + + + + shade + + + org.openjfx + javafx-graphics + ${javafx.version} + win + + + org.openjfx + javafx-graphics + ${javafx.version} + mac + + + org.openjfx + javafx-graphics + ${javafx.version} + linux + + + org.openjfx + javafx-media + ${javafx.version} + win + + + org.openjfx + javafx-media + ${javafx.version} + mac + + + org.openjfx + javafx-media + ${javafx.version} + linux + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + true + + + uk.ac.soton.comp1206.Launcher + + + + + + + + + + + + + + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + + org.openjfx + javafx-media + ${javafx.version} + + + com.neovisionaries + nv-websocket-client + 2.14 + + + org.apache.logging.log4j + log4j-api + 2.20.0 + + + org.apache.logging.log4j + log4j-core + 2.20.0 + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 19 + 19 + 19 + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + uk.ac.soton.comp1206/uk.ac.soton.comp1206.App + + + + + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..5829f72 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,16 @@ +module uk.ac.soton.comp1206 { + requires java.scripting; + requires javafx.controls; + requires javafx.fxml; + requires javafx.media; + requires org.apache.logging.log4j; + requires nv.websocket.client; + opens uk.ac.soton.comp1206.ui to javafx.fxml; + exports uk.ac.soton.comp1206; + exports uk.ac.soton.comp1206.ui; + exports uk.ac.soton.comp1206.network; + exports uk.ac.soton.comp1206.scene; + exports uk.ac.soton.comp1206.event; + exports uk.ac.soton.comp1206.component; + exports uk.ac.soton.comp1206.game; +} \ No newline at end of file diff --git a/src/main/java/uk/ac/soton/comp1206/App.java b/src/main/java/uk/ac/soton/comp1206/App.java new file mode 100644 index 0000000..aa83486 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/App.java @@ -0,0 +1,81 @@ +package uk.ac.soton.comp1206; + +import javafx.application.Application; +import javafx.stage.Stage; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import uk.ac.soton.comp1206.ui.GameWindow; + +/** + * JavaFX Application class + */ +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; + } + +} \ No newline at end of file diff --git a/src/main/java/uk/ac/soton/comp1206/Launcher.java b/src/main/java/uk/ac/soton/comp1206/Launcher.java new file mode 100644 index 0000000..4fd4e21 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/Launcher.java @@ -0,0 +1,17 @@ +package uk.ac.soton.comp1206; + +/** + * This Launcher class is used to allow the game to be built into a shaded jar file which then loads JavaFX. This + * Launcher is used when running as a shaded jar file. + */ +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); + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java b/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java new file mode 100644 index 0000000..e6c5c78 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java @@ -0,0 +1,184 @@ +package uk.ac.soton.comp1206.component; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.canvas.Canvas; +import javafx.scene.paint.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The Visual User Interface component representing a single block in the grid. + * + * Extends Canvas and is responsible for drawing itself. + * + * Displays an empty square (when the value is 0) or a coloured square depending on value. + * + * The GameBlock value should be bound to a corresponding block in the Grid model. + */ +public class GameBlock extends Canvas { + + private static final Logger logger = LogManager.getLogger(GameBlock.class); + + /** + * The set of colours for different pieces + */ + public static final Color[] COLOURS = { + Color.TRANSPARENT, + Color.DEEPPINK, + Color.RED, + Color.ORANGE, + Color.YELLOW, + Color.YELLOWGREEN, + Color.LIME, + Color.GREEN, + Color.DARKGREEN, + Color.DARKTURQUOISE, + Color.DEEPSKYBLUE, + Color.AQUA, + Color.AQUAMARINE, + Color.BLUE, + Color.MEDIUMPURPLE, + Color.PURPLE + }; + + private final GameBoard gameBoard; + + private final double width; + private final double height; + + /** + * The column this block exists as in the grid + */ + private final int x; + + /** + * The row this block exists as in the grid + */ + private final int y; + + /** + * The value of this block (0 = empty, otherwise specifies the colour to render as) + */ + private final IntegerProperty value = new SimpleIntegerProperty(0); + + /** + * Create a new single Game Block + * @param gameBoard the board this block belongs to + * @param x the column the block exists in + * @param y the row the block exists in + * @param width the width of the canvas to render + * @param height the height of the canvas to render + */ + public GameBlock(GameBoard gameBoard, int x, int y, double width, double height) { + this.gameBoard = gameBoard; + this.width = width; + this.height = height; + this.x = x; + this.y = y; + + //A canvas needs a fixed width and height + setWidth(width); + setHeight(height); + + //Do an initial paint + paint(); + + //When the value property is updated, call the internal updateValue method + value.addListener(this::updateValue); + } + + /** + * When the value of this block is updated, + * @param observable what was updated + * @param oldValue the old value + * @param newValue the new value + */ + private void updateValue(ObservableValue observable, Number oldValue, Number newValue) { + paint(); + } + + /** + * Handle painting of the block canvas + */ + public void paint() { + //If the block is empty, paint as empty + if(value.get() == 0) { + paintEmpty(); + } else { + //If the block is not empty, paint with the colour represented by the value + paintColor(COLOURS[value.get()]); + } + } + + /** + * Paint this canvas empty + */ + private void paintEmpty() { + var gc = getGraphicsContext2D(); + + //Clear + gc.clearRect(0,0,width,height); + + //Fill + gc.setFill(Color.WHITE); + gc.fillRect(0,0, width, height); + + //Border + gc.setStroke(Color.BLACK); + gc.strokeRect(0,0,width,height); + } + + /** + * Paint this canvas with the given colour + * @param colour the colour to paint + */ + private void paintColor(Paint colour) { + var gc = getGraphicsContext2D(); + + //Clear + gc.clearRect(0,0,width,height); + + //Colour fill + gc.setFill(colour); + gc.fillRect(0,0, width, height); + + //Border + gc.setStroke(Color.BLACK); + gc.strokeRect(0,0,width,height); + } + + /** + * Get the column of this block + * @return column number + */ + public int getX() { + return x; + } + + /** + * Get the row of this block + * @return row number + */ + public int getY() { + return y; + } + + /** + * Get the current value held by this block, representing it's colour + * @return value + */ + public int getValue() { + return this.value.get(); + } + + /** + * Bind the value of this block to another property. Used to link the visual block to a corresponding block in the Grid. + * @param input property to bind the value to + */ + public void bind(ObservableValue input) { + value.bind(input); + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/component/GameBlockCoordinate.java b/src/main/java/uk/ac/soton/comp1206/component/GameBlockCoordinate.java new file mode 100644 index 0000000..62d52b6 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/component/GameBlockCoordinate.java @@ -0,0 +1,130 @@ +package uk.ac.soton.comp1206.component; + +import javafx.beans.NamedArg; + +/** + * Represents a row and column representation of a block in the grid. Holds the x (column) and y (row). + * + * Useful for use in a set or list or other form of collection. + */ +public class GameBlockCoordinate { + + /** + * Represents the column + */ + private final int x; + + /** + * Represents the row + */ + private final int y; + + /** + * A hash is computed to enable comparisons between this and other GameBlockCoordinates. + */ + private int hash = 0; + + + /** + * Create a new GameBlockCoordinate which stores a row and column reference to a block + * @param x column + * @param y row + */ + public GameBlockCoordinate(@NamedArg("x") int x, @NamedArg("y") int y) { + this.x = x; + this.y = y; + } + + /** + * Return the column (x) + * @return column number + */ + public int getX() { + return x; + } + + /** + * Return the row (y) + * @return the row number + */ + public int getY() { + return y; + } + + /** + * Add a row and column reference to this one and return a new GameBlockCoordinate + * @param x additional columns + * @param y additional rows + * @return a new GameBlockCoordinate with the result of the addition + */ + public GameBlockCoordinate add(int x, int y) { + return new GameBlockCoordinate( + getX() + x, + getY() + y); + } + + /** + * Add another GameBlockCoordinate to this one, returning a new GameBlockCoordinate + * @param point point to add + * @return a new GameBlockCoordinate with the result of the addition + */ + public GameBlockCoordinate add(GameBlockCoordinate point) { + return add(point.getX(), point.getY()); + } + + /** Subtract a row and column reference to this one and return a new GameBlockCoordinate + * @param x columns to remove + * @param y rows to remove + * @return a new GameBlockCoordinate with the result of the subtraction + */ + public GameBlockCoordinate subtract(int x, int y) { + return new GameBlockCoordinate( + getX() - x, + getY() - y); + } + + /** + * Subtract another GameBlockCoordinate to this one, returning a new GameBlockCoordinate + * @param point point to subtract + * @return a new GameBlockCoordinate with the result of the subtraction + */ + public GameBlockCoordinate subtract(GameBlockCoordinate point) { + return subtract(point.getX(), point.getY()); + } + + /** + * Compare this GameBlockCoordinate to another GameBlockCoordinate + * @param obj other object to compare to + * @return true if equal, otherwise false + */ + @Override public boolean equals(Object obj) { + if (obj == this) return true; + if (obj instanceof GameBlockCoordinate) { + GameBlockCoordinate other = (GameBlockCoordinate) obj; + return getX() == other.getX() && getY() == other.getY(); + } else return false; + } + + /** + * Calculate a hash code of this GameBlockCoordinate, used for comparisons + * @return hash code + */ + @Override public int hashCode() { + if (hash == 0) { + long bits = 7L; + bits = 31L * bits + Double.doubleToLongBits(getX()); + bits = 31L * bits + Double.doubleToLongBits(getY()); + hash = (int) (bits ^ (bits >> 32)); + } + return hash; + } + + /** + * Return a string representation of this GameBlockCoordinate + * @return string representation + */ + @Override public String toString() { + return "GameBlockCoordinate [x = " + getX() + ", y = " + getY() + "]"; + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java b/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java new file mode 100644 index 0000000..b587148 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java @@ -0,0 +1,175 @@ +package uk.ac.soton.comp1206.component; + +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.game.Grid; + +/** + * A GameBoard is a visual component to represent the visual GameBoard. + * It extends a GridPane to hold a grid of GameBlocks. + * + * The GameBoard can hold an internal grid of it's own, for example, for displaying an upcoming block. It also be + * linked to an external grid, for the main game board. + * + * The GameBoard is only a visual representation and should not contain game logic or model logic in it, which should + * take place in the Grid. + */ +public class GameBoard extends GridPane { + + private static final Logger logger = LogManager.getLogger(GameBoard.class); + + /** + * Number of columns in the board + */ + private final int cols; + + /** + * Number of rows in the board + */ + private final int rows; + + /** + * The visual width of the board - has to be specified due to being a Canvas + */ + private final double width; + + /** + * The visual height of the board - has to be specified due to being a Canvas + */ + private final double height; + + /** + * The grid this GameBoard represents + */ + final Grid grid; + + /** + * The blocks inside the grid + */ + GameBlock[][] blocks; + + /** + * The listener to call when a specific block is clicked + */ + private BlockClickedListener blockClickedListener; + + + /** + * Create a new GameBoard, based off a given grid, with a visual width and height. + * @param grid linked grid + * @param width the visual width + * @param height the visual height + */ + public GameBoard(Grid grid, double width, double height) { + this.cols = grid.getCols(); + this.rows = grid.getRows(); + this.width = width; + this.height = height; + this.grid = grid; + + //Build the GameBoard + build(); + } + + /** + * Create a new GameBoard with it's own internal grid, specifying the number of columns and rows, along with the + * visual width and height. + * + * @param cols number of columns for internal grid + * @param rows number of rows for internal grid + * @param width the visual width + * @param height the visual height + */ + public GameBoard(int cols, int rows, double width, double height) { + this.cols = cols; + this.rows = rows; + this.width = width; + this.height = height; + this.grid = new Grid(cols,rows); + + //Build the GameBoard + build(); + } + + /** + * Get a specific block from the GameBoard, specified by it's row and column + * @param x column + * @param y row + * @return game block at the given column and row + */ + public GameBlock getBlock(int x, int y) { + return blocks[x][y]; + } + + /** + * Build the GameBoard by creating a block at every x and y column and row + */ + protected void build() { + logger.info("Building grid: {} x {}",cols,rows); + + setMaxWidth(width); + setMaxHeight(height); + + setGridLinesVisible(true); + + blocks = new GameBlock[cols][rows]; + + for(var y = 0; y < rows; y++) { + for (var x = 0; x < cols; x++) { + createBlock(x,y); + } + } + } + + /** + * Create a block at the given x and y position in the GameBoard + * @param x column + * @param y row + */ + protected GameBlock createBlock(int x, int y) { + var blockWidth = width / cols; + var blockHeight = height / rows; + + //Create a new GameBlock UI component + GameBlock block = new GameBlock(this, x, y, blockWidth, blockHeight); + + //Add to the GridPane + add(block,x,y); + + //Add to our block directory + blocks[x][y] = block; + + //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)); + + return block; + } + + /** + * Set the listener to handle an event when a block is clicked + * @param listener listener to add + */ + public void setOnBlockClick(BlockClickedListener listener) { + this.blockClickedListener = listener; + } + + /** + * Triggered when a block is clicked. Call the attached listener. + * @param event mouse event + * @param block block clicked on + */ + private void blockClicked(MouseEvent event, GameBlock block) { + logger.info("Block clicked: {}", block); + + if(blockClickedListener != null) { + blockClickedListener.blockClicked(block); + } + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/event/BlockClickedListener.java b/src/main/java/uk/ac/soton/comp1206/event/BlockClickedListener.java new file mode 100644 index 0000000..0e1001a --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/event/BlockClickedListener.java @@ -0,0 +1,16 @@ +package uk.ac.soton.comp1206.event; + +import uk.ac.soton.comp1206.component.GameBlock; + +/** + * The Block Clicked listener is used to handle the event when a block in a GameBoard is clicked. It passes the + * GameBlock that was clicked in the message + */ +public interface BlockClickedListener { + + /** + * Handle a block clicked event + * @param block the block that was clicked + */ + public void blockClicked(GameBlock block); +} diff --git a/src/main/java/uk/ac/soton/comp1206/event/CommunicationsListener.java b/src/main/java/uk/ac/soton/comp1206/event/CommunicationsListener.java new file mode 100644 index 0000000..89c532e --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/event/CommunicationsListener.java @@ -0,0 +1,13 @@ +package uk.ac.soton.comp1206.event; + +/** + * The Communications Listener is used for listening to messages received by the communicator. + */ +public interface CommunicationsListener { + + /** + * Handle an incoming message received by the Communicator + * @param communication the message that was received + */ + public void receiveCommunication(String communication); +} diff --git a/src/main/java/uk/ac/soton/comp1206/game/Game.java b/src/main/java/uk/ac/soton/comp1206/game/Game.java new file mode 100644 index 0000000..a379daf --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/game/Game.java @@ -0,0 +1,103 @@ +package uk.ac.soton.comp1206.game; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.component.GameBlock; + +/** + * The Game class handles the main logic, state and properties of the TetrECS game. Methods to manipulate the game state + * and to handle actions made by the player should take place inside this class. + */ +public class Game { + + private static final Logger logger = LogManager.getLogger(Game.class); + + /** + * Number of rows + */ + protected final int rows; + + /** + * Number of columns + */ + protected final int cols; + + /** + * The grid model linked to the game + */ + protected final Grid grid; + + /** + * Create a new game with the specified rows and columns. Creates a corresponding grid model. + * @param cols number of columns + * @param rows number of rows + */ + public Game(int cols, int rows) { + this.cols = cols; + this.rows = rows; + + //Create a new grid model to represent the game state + this.grid = new Grid(cols,rows); + } + + /** + * Start the game + */ + public void start() { + logger.info("Starting game"); + initialiseGame(); + } + + /** + * Initialise a new game and set up anything that needs to be done at the start + */ + public void initialiseGame() { + logger.info("Initialising game"); + } + + /** + * Handle what should happen when a particular block is clicked + * @param gameBlock the block that was clicked + */ + public void blockClicked(GameBlock gameBlock) { + //Get the position of this block + int x = gameBlock.getX(); + int y = gameBlock.getY(); + + //Get the new value for this block + int previousValue = grid.get(x,y); + int newValue = previousValue + 1; + if (newValue > GamePiece.PIECES) { + newValue = 0; + } + + //Update the grid with the new value + grid.set(x,y,newValue); + } + + /** + * Get the grid model inside this game representing the game state of the board + * @return game grid model + */ + public Grid getGrid() { + return grid; + } + + /** + * Get the number of columns in this game + * @return number of columns + */ + public int getCols() { + return cols; + } + + /** + * Get the number of rows in this game + * @return number of rows + */ + public int getRows() { + return rows; + } + + +} diff --git a/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java b/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java new file mode 100644 index 0000000..763897a --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java @@ -0,0 +1,224 @@ +package uk.ac.soton.comp1206.game; + +/** + * Instances of GamePiece Represents the model of a specific Game Piece with it's block makeup. + * + * The GamePiece class also contains a factory for producing a GamePiece of a particular shape, as specified by it's + * number. + */ +public class GamePiece { + + /** + * The total number of pieces in this game + */ + public static final int PIECES = 15; + + /** + * The 2D grid representation of the shape of this piece + */ + private int[][] blocks; + + /** + * The value of this piece + */ + private final int value; + + /** + * The name of this piece + */ + private final String name; + + /** + * Create a new GamePiece of the specified piece number + * @param piece piece number + * @return the created GamePiece + */ + public static GamePiece createPiece(int piece) { + switch (piece) { + //Line + case 0 -> { + int[][] blocks = {{0, 0, 0}, {1, 1, 1}, {0, 0, 0}}; + return new GamePiece("Line", blocks, 1); + } + + //C + case 1 -> { + int[][] blocks = {{0, 0, 0}, {1, 1, 1}, {1, 0, 1}}; + return new GamePiece("C", blocks, 2); + } + + //Plus + case 2 -> { + int[][] blocks = {{0, 1, 0}, {1, 1, 1}, {0, 1, 0}}; + return new GamePiece("Plus", blocks, 3); + } + + //Dot + case 3 -> { + int[][] blocks = {{0, 0, 0}, {0, 1, 0}, {0, 0, 0}}; + return new GamePiece("Dot", blocks, 4); + } + + //Square + case 4 -> { + int[][] blocks = {{1, 1, 0}, {1, 1, 0}, {0, 0, 0}}; + return new GamePiece("Square", blocks, 5); + } + + //L + case 5 -> { + int[][] blocks = {{0, 0, 0}, {1, 1, 1}, {0, 0, 1}}; + return new GamePiece("L", blocks, 6); + } + + //J + case 6 -> { + int[][] blocks = {{0, 0, 1}, {1, 1, 1}, {0, 0, 0}}; + return new GamePiece("J", blocks, 7); + } + + //S + case 7 -> { + int[][] blocks = {{0, 0, 0}, {0, 1, 1}, {1, 1, 0}}; + return new GamePiece("S", blocks, 8); + } + + //Z + case 8 -> { + int[][] blocks = {{1, 1, 0}, {0, 1, 1}, {0, 0, 0}}; + return new GamePiece("Z", blocks, 9); + } + + //T + case 9 -> { + int[][] blocks = {{1, 0, 0}, {1, 1, 0}, {1, 0, 0}}; + return new GamePiece("T", blocks, 10); + } + + //X + case 10 -> { + int[][] blocks = {{1, 0, 1}, {0, 1, 0}, {1, 0, 1}}; + return new GamePiece("X", blocks, 11); + } + + //Corner + case 11 -> { + int[][] blocks = {{0, 0, 0}, {1, 1, 0}, {1, 0, 0}}; + return new GamePiece("Corner", blocks, 12); + } + + //Inverse Corner + case 12 -> { + int[][] blocks = {{1, 0, 0}, {1, 1, 0}, {0, 0, 0}}; + return new GamePiece("Inverse Corner", blocks, 13); + } + + //Diagonal + case 13 -> { + int[][] blocks = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + return new GamePiece("Diagonal", blocks, 14); + } + + //Double + case 14 -> { + int[][] blocks = {{0, 1, 0}, {0, 1, 0}, {0, 0, 0}}; + return new GamePiece("Double", blocks, 15); + } + } + + //Not a valid piece number + throw new IndexOutOfBoundsException("No such piece: " + piece); + } + + /** + * Create a new GamePiece of the specified piece number and rotation + * @param piece piece number + * @param rotation number of times to rotate + * @return the created GamePiece + */ + public static GamePiece createPiece(int piece, int rotation) { + var newPiece = createPiece(piece); + + newPiece.rotate(rotation); + return newPiece; + } + + /** + * Create a new GamePiece with the given name, block makeup and value. Should not be called directly, only via the + * factory. + * @param name name of the piece + * @param blocks block makeup of the piece + * @param value the value of this piece + */ + private GamePiece(String name, int[][] blocks, int value) { + this.name = name; + this.blocks = blocks; + this.value = value; + + //Use the shape of the block to create a grid with either 0 (empty) or the value of this shape for each block. + for(int x = 0; x < blocks.length; x++) { + for (int y = 0; y < blocks[x].length; y++) { + if(blocks[x][y] == 0) continue; + blocks[x][y] = value; + } + } + } + + /** + * Get the value of this piece + * @return piece value + */ + public int getValue() { + return value; + } + + /** + * Get the block makeup of this piece + * @return 2D grid of the blocks representing the piece shape + */ + public int[][] getBlocks() { + return blocks; + } + + /** + * Rotate this piece the given number of rotations + * @param rotations number of rotations + */ + public void rotate(int rotations) { + for(int rotated = 0; rotated < rotations; rotated ++) { + rotate(); + } + } + + /** + * Rotate this piece exactly once by rotating it's 3x3 grid + */ + public void rotate() { + int[][] rotated = new int[blocks.length][blocks[0].length]; + rotated[2][0] = blocks[0][0]; + rotated[1][0] = blocks[0][1]; + rotated[0][0] = blocks[0][2]; + + rotated[2][1] = blocks[1][0]; + rotated[1][1] = blocks[1][1]; + rotated[0][1] = blocks[1][2]; + + rotated[2][2] = blocks[2][0]; + rotated[1][2] = blocks[2][1]; + rotated[0][2] = blocks[2][2]; + + blocks = rotated; + } + + + /** + * Return the string representation of this piece + * @return the name of this piece + */ + public String toString() { + return this.name; + } + + + +} diff --git a/src/main/java/uk/ac/soton/comp1206/game/Grid.java b/src/main/java/uk/ac/soton/comp1206/game/Grid.java new file mode 100644 index 0000000..4924c99 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/game/Grid.java @@ -0,0 +1,106 @@ +package uk.ac.soton.comp1206.game; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; + +/** + * The Grid is a model which holds the state of a game board. It is made up of a set of Integer values arranged in a 2D + * arrow, with rows and columns. + * + * Each value inside the Grid is an IntegerProperty can be bound to enable modification and display of the contents of + * the grid. + * + * The Grid contains functions related to modifying the model, for example, placing a piece inside the grid. + * + * The Grid should be linked to a GameBoard for it's display. + */ +public class Grid { + + /** + * The number of columns in this grid + */ + private final int cols; + + /** + * The number of rows in this grid + */ + private final int rows; + + /** + * The grid is a 2D arrow with rows and columns of SimpleIntegerProperties. + */ + private final SimpleIntegerProperty[][] grid; + + /** + * Create a new Grid with the specified number of columns and rows and initialise them + * @param cols number of columns + * @param rows number of rows + */ + public Grid(int cols, int rows) { + this.cols = cols; + this.rows = rows; + + //Create the grid itself + grid = new SimpleIntegerProperty[cols][rows]; + + //Add a SimpleIntegerProperty to every block in the grid + for(var y = 0; y < rows; y++) { + for(var x = 0; x < cols; x++) { + grid[x][y] = new SimpleIntegerProperty(0); + } + } + } + + /** + * Get the Integer property contained inside the grid at a given row and column index. Can be used for binding. + * @param x column + * @param y row + * @return the IntegerProperty at the given x and y in this grid + */ + public IntegerProperty getGridProperty(int x, int y) { + return grid[x][y]; + } + + /** + * Update the value at the given x and y index within the grid + * @param x column + * @param y row + * @param value the new value + */ + public void set(int x, int y, int value) { + grid[x][y].set(value); + } + + /** + * Get the value represented at the given x and y index within the grid + * @param x column + * @param y row + * @return the value + */ + public int get(int x, int y) { + try { + //Get the value held in the property at the x and y index provided + return grid[x][y].get(); + } catch (ArrayIndexOutOfBoundsException e) { + //No such index + return -1; + } + } + + /** + * Get the number of columns in this game + * @return number of columns + */ + public int getCols() { + return cols; + } + + /** + * Get the number of rows in this game + * @return number of rows + */ + public int getRows() { + return rows; + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/network/Communicator.java b/src/main/java/uk/ac/soton/comp1206/network/Communicator.java new file mode 100644 index 0000000..5af9474 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/network/Communicator.java @@ -0,0 +1,123 @@ +package uk.ac.soton.comp1206.network; + +import com.neovisionaries.ws.client.*; +import javafx.scene.control.Alert; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.event.CommunicationsListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * Uses web sockets to talk to a web socket server and relays communication to attached listeners + * + * YOU DO NOT NEED TO WORRY ABOUT THIS CLASS! Leave it be :-) + */ +public class Communicator { + + private static final Logger logger = LogManager.getLogger(Communicator.class); + + /** + * Attached communication listeners listening to messages on this Communicator. Each will be sent any messages. + */ + private final List handlers = new ArrayList<>(); + + private WebSocket ws = null; + + /** + * Create a new communicator to the given web socket server + * + * @param server server to connect to + */ + public Communicator(String server) { + + try { + var socketFactory = new WebSocketFactory(); + + //Connect to the server + ws = socketFactory.createSocket(server); + ws.connect(); + logger.info("Connected to " + server); + + //When a message is received, call the receive method + ws.addListener(new WebSocketAdapter() { + @Override + public void onTextMessage(WebSocket websocket, String message) throws Exception { + Communicator.this.receive(websocket, message); + } + @Override + public void onPingFrame(WebSocket webSocket, WebSocketFrame webSocketFrame) throws Exception { + logger.info("Ping? Pong!"); + } + }); + + //Error handling + ws.addListener(new WebSocketAdapter() { + @Override + public void onTextMessage(WebSocket websocket, String message) throws Exception { + if(message.startsWith("ERROR")) { + logger.error(message); + } + } + @Override + public void handleCallbackError(WebSocket webSocket, Throwable throwable) throws Exception { + logger.error("Callback Error:" + throwable.getMessage()); + throwable.printStackTrace(); + } + @Override + public void onError(WebSocket webSocket, WebSocketException e) throws Exception { + logger.error("Error:" + e.getMessage()); + e.printStackTrace(); + } + }); + + } catch (Exception e){ + logger.error("Socket error: " + e.getMessage()); + e.printStackTrace(); + + Alert error = new Alert(Alert.AlertType.ERROR,"Unable to communicate with the TetrECS server\n\n" + e.getMessage() + "\n\nPlease ensure you are connected to the VPN"); + error.showAndWait(); + System.exit(1); + } + } + + /** Send a message to the server + * + * @param message Message to send + */ + public void send(String message) { + logger.info("Sending message: " + message); + + ws.sendText(message); + } + + /** + * Add a new listener to receive messages from the server + * @param listener the listener to add + */ + public void addListener(CommunicationsListener listener) { + this.handlers.add(listener); + } + + /** + * Clear all current listeners + */ + public void clearListeners() { + this.handlers.clear(); + } + + /** Receive a message from the server. Relay to any attached listeners + * + * @param websocket the socket + * @param message the message that was received + */ + private void receive(WebSocket websocket, String message) { + logger.info("Received: " + message); + + for(CommunicationsListener handler : handlers) { + handler.receiveCommunication(message); + } + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/scene/BaseScene.java b/src/main/java/uk/ac/soton/comp1206/scene/BaseScene.java new file mode 100644 index 0000000..3c3156f --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/scene/BaseScene.java @@ -0,0 +1,56 @@ +package uk.ac.soton.comp1206.scene; + +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import uk.ac.soton.comp1206.ui.GamePane; +import uk.ac.soton.comp1206.ui.GameWindow; + +/** + * A Base Scene used in the game. Handles common functionality between all scenes. + */ +public abstract class BaseScene { + + protected final GameWindow gameWindow; + + protected GamePane root; + protected Scene scene; + + /** + * Create a new scene, passing in the GameWindow the scene will be displayed in + * @param gameWindow the game window + */ + public BaseScene(GameWindow gameWindow) { + this.gameWindow = gameWindow; + } + + /** + * Initialise this scene. Called after creation + */ + public abstract void initialise(); + + /** + * Build the layout of the scene + */ + public abstract void build(); + + /** + * Create a new JavaFX scene using the root contained within this scene + * @return JavaFX scene + */ + public Scene setScene() { + var previous = gameWindow.getScene(); + Scene scene = new Scene(root, previous.getWidth(), previous.getHeight(), Color.BLACK); + scene.getStylesheets().add(getClass().getResource("/style/game.css").toExternalForm()); + this.scene = scene; + return scene; + } + + /** + * Get the JavaFX scene contained inside + * @return JavaFX scene + */ + public Scene getScene() { + return this.scene; + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java b/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java new file mode 100644 index 0000000..fd6c21b --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java @@ -0,0 +1,83 @@ +package uk.ac.soton.comp1206.scene; + +import javafx.scene.layout.*; +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.game.Game; +import uk.ac.soton.comp1206.ui.GamePane; +import uk.ac.soton.comp1206.ui.GameWindow; + +/** + * The Single Player challenge scene. Holds the UI for the single player challenge mode in the game. + */ +public class ChallengeScene extends BaseScene { + + private static final Logger logger = LogManager.getLogger(MenuScene.class); + protected Game game; + + /** + * Create a new Single Player challenge scene + * @param gameWindow the Game Window + */ + public ChallengeScene(GameWindow gameWindow) { + super(gameWindow); + logger.info("Creating Challenge Scene"); + } + + /** + * Build the Challenge window + */ + @Override + public void build() { + logger.info("Building " + this.getClass().getName()); + + setupGame(); + + root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight()); + + var challengePane = new StackPane(); + challengePane.setMaxWidth(gameWindow.getWidth()); + challengePane.setMaxHeight(gameWindow.getHeight()); + challengePane.getStyleClass().add("menu-background"); + root.getChildren().add(challengePane); + + var mainPane = new BorderPane(); + challengePane.getChildren().add(mainPane); + + var board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2); + mainPane.setCenter(board); + + //Handle block on gameboard grid being clicked + board.setOnBlockClick(this::blockClicked); + } + + /** + * Handle when a block is clicked + * @param gameBlock the Game Block that was clocked + */ + private void blockClicked(GameBlock gameBlock) { + game.blockClicked(gameBlock); + } + + /** + * Setup the game object and model + */ + public void setupGame() { + logger.info("Starting a new challenge"); + + //Start new game + game = new Game(5, 5); + } + + /** + * Initialise the scene and start the game + */ + @Override + public void initialise() { + logger.info("Initialising Challenge"); + game.start(); + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java b/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java new file mode 100644 index 0000000..4bcc9d8 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java @@ -0,0 +1,75 @@ +package uk.ac.soton.comp1206.scene; + +import javafx.event.ActionEvent; +import javafx.scene.control.Button; +import javafx.scene.layout.*; +import javafx.scene.text.Text; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.ui.GamePane; +import uk.ac.soton.comp1206.ui.GameWindow; + +/** + * The main menu of the game. Provides a gateway to the rest of the game. + */ +public class MenuScene extends BaseScene { + + private static final Logger logger = LogManager.getLogger(MenuScene.class); + + /** + * Create a new menu scene + * @param gameWindow the Game Window this will be displayed in + */ + public MenuScene(GameWindow gameWindow) { + super(gameWindow); + logger.info("Creating Menu Scene"); + } + + /** + * Build the menu layout + */ + @Override + public void build() { + logger.info("Building " + this.getClass().getName()); + + root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight()); + + var menuPane = new StackPane(); + menuPane.setMaxWidth(gameWindow.getWidth()); + menuPane.setMaxHeight(gameWindow.getHeight()); + menuPane.getStyleClass().add("menu-background"); + root.getChildren().add(menuPane); + + var mainPane = new BorderPane(); + menuPane.getChildren().add(mainPane); + + //Awful title + var title = new Text("TetrECS"); + title.getStyleClass().add("title"); + mainPane.setTop(title); + + //For now, let us just add a button that starts the game. I'm sure you'll do something way better. + var button = new Button("Play"); + mainPane.setCenter(button); + + //Bind the button action to the startGame method in the menu + button.setOnAction(this::startGame); + } + + /** + * Initialise the menu + */ + @Override + public void initialise() { + + } + + /** + * Handle when the Start Game button is pressed + * @param event event + */ + private void startGame(ActionEvent event) { + gameWindow.startChallenge(); + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/ui/GamePane.java b/src/main/java/uk/ac/soton/comp1206/ui/GamePane.java new file mode 100644 index 0000000..ef42b4f --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/ui/GamePane.java @@ -0,0 +1,94 @@ +package uk.ac.soton.comp1206.ui; + +import javafx.geometry.Pos; +import javafx.scene.layout.*; +import javafx.scene.transform.Scale; +import javafx.scene.transform.Translate; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The Game Pane is a special pane which will scale anything inside it to the screen and maintain the aspect ratio. + * + * Drawing will be scaled appropriately. + * + * This takes the worry about the layout out and will allow the game to scale to any resolution easily. + * + * It uses the width and height given which should match the main window size. This will be the base drawing resolution, + * but will be scaled up or down as the window is resized. + * + * You should not need to modify this class + */ +public class GamePane extends StackPane { + + private static final Logger logger = LogManager.getLogger(GamePane.class); + + private final int width; + private final int height; + private double scalar = 1; + private final boolean autoScale = true; + + /** + * Create a new scalable GamePane with the given drawing width and height. + * @param width width + * @param height height + */ + public GamePane(int width, int height) { + super(); + this.width = width; + this.height = height; + + getStyleClass().add("gamepane"); + setAlignment(Pos.TOP_LEFT); + } + + /** + * Update the scalar being used by this draw pane + * @param scalar scalar + */ + protected void setScalar(double scalar) { + this.scalar = scalar; + } + + /** + * Use a Graphics Transformation to scale everything inside this pane. Padding is added to the edges to maintain + * the correct aspect ratio and keep the display centred. + */ + @Override + public void layoutChildren() { + super.layoutChildren(); + + if(!autoScale) { + return; + } + + //Work out the scale factor height and width + var scaleFactorHeight = getHeight() / height; + var scaleFactorWidth = getWidth() / width; + + //Work out whether to scale by width or height + if (scaleFactorHeight > scaleFactorWidth) { + setScalar(scaleFactorWidth); + } else { + setScalar(scaleFactorHeight); + } + + //Set up the scale + Scale scale = new Scale(scalar,scalar); + + //Get the parent width and height + var parentWidth = getWidth(); + var parentHeight = getHeight(); + + //Get the padding needed on the top and left + var paddingLeft = (parentWidth - (width * scalar)) / 2.0; + var paddingTop = (parentHeight - (height * scalar)) / 2.0; + + //Perform the transformation + Translate translate = new Translate(paddingLeft, paddingTop); + scale.setPivotX(0); + scale.setPivotY(0); + getTransforms().setAll(translate, scale); + } + +} diff --git a/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java b/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java new file mode 100644 index 0000000..772a244 --- /dev/null +++ b/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java @@ -0,0 +1,163 @@ +package uk.ac.soton.comp1206.ui; + +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.stage.Stage; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import uk.ac.soton.comp1206.App; +import uk.ac.soton.comp1206.network.Communicator; +import uk.ac.soton.comp1206.scene.*; + +/** + * 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. + * + * The GameWindow has methods to launch each of the different parts of the game by switching scenes. You can add more + * methods here to add more screens to the game. + */ +public class GameWindow { + + private static final Logger logger = LogManager.getLogger(GameWindow.class); + + private final int width; + private final int height; + + private final Stage stage; + + private BaseScene currentScene; + private Scene scene; + + final Communicator communicator; + + /** + * Create a new GameWindow attached to the given stage with the specified width and height + * @param stage stage + * @param width width + * @param height height + */ + public GameWindow(Stage stage, int width, int height) { + this.width = width; + this.height = height; + + this.stage = stage; + + //Setup window + setupStage(); + + //Setup resources + setupResources(); + + //Setup default scene + setupDefaultScene(); + + //Setup communicator + communicator = new Communicator("ws://ofb-labs.soton.ac.uk:9700"); + + //Go to menu + startMenu(); + } + + /** + * Setup the font and any other resources we need + */ + private void setupResources() { + logger.info("Loading resources"); + + //We need to load fonts here due to the Font loader bug with spaces in URLs in the CSS files + Font.loadFont(getClass().getResourceAsStream("/style/Orbitron-Regular.ttf"),32); + Font.loadFont(getClass().getResourceAsStream("/style/Orbitron-Bold.ttf"),32); + Font.loadFont(getClass().getResourceAsStream("/style/Orbitron-ExtraBold.ttf"),32); + } + + /** + * Display the main menu + */ + public void startMenu() { + loadScene(new MenuScene(this)); + } + + /** + * Display the single player challenge + */ + public void startChallenge() { loadScene(new ChallengeScene(this)); } + + /** + * Setup the default settings for the stage itself (the window), such as the title and minimum width and height. + */ + public void setupStage() { + stage.setTitle("TetrECS"); + stage.setMinWidth(width); + stage.setMinHeight(height + 20); + stage.setOnCloseRequest(ev -> App.getInstance().shutdown()); + } + + /** + * Load a given scene which extends BaseScene and switch over. + * @param newScene new scene to load + */ + public void loadScene(BaseScene newScene) { + //Cleanup remains of the previous scene + cleanup(); + + //Create the new scene and set it up + newScene.build(); + currentScene = newScene; + scene = newScene.setScene(); + stage.setScene(scene); + + //Initialise the scene when ready + Platform.runLater(() -> currentScene.initialise()); + } + + /** + * Setup the default scene (an empty black scene) when no scene is loaded + */ + public void setupDefaultScene() { + this.scene = new Scene(new Pane(),width,height, Color.BLACK); + stage.setScene(this.scene); + } + + /** + * When switching scenes, perform any cleanup needed, such as removing previous listeners + */ + public void cleanup() { + logger.info("Clearing up previous scene"); + communicator.clearListeners(); + } + + /** + * Get the current scene being displayed + * @return scene + */ + public Scene getScene() { + return scene; + } + + /** + * Get the width of the Game Window + * @return width + */ + public int getWidth() { + return this.width; + } + + /** + * Get the height of the Game Window + * @return height + */ + public int getHeight() { + return this.height; + } + + /** + * Get the communicator + * @return communicator + */ + public Communicator getCommunicator() { + return communicator; + } +} diff --git a/src/main/resources/images/1.jpg b/src/main/resources/images/1.jpg new file mode 100644 index 0000000..c9fe593 Binary files /dev/null and b/src/main/resources/images/1.jpg differ diff --git a/src/main/resources/images/2.jpg b/src/main/resources/images/2.jpg new file mode 100644 index 0000000..4a4b8ca Binary files /dev/null and b/src/main/resources/images/2.jpg differ diff --git a/src/main/resources/images/3.jpg b/src/main/resources/images/3.jpg new file mode 100644 index 0000000..65a50c5 Binary files /dev/null and b/src/main/resources/images/3.jpg differ diff --git a/src/main/resources/images/4.jpg b/src/main/resources/images/4.jpg new file mode 100644 index 0000000..7e03278 Binary files /dev/null and b/src/main/resources/images/4.jpg differ diff --git a/src/main/resources/images/5.jpg b/src/main/resources/images/5.jpg new file mode 100644 index 0000000..26464dd Binary files /dev/null and b/src/main/resources/images/5.jpg differ diff --git a/src/main/resources/images/6.jpg b/src/main/resources/images/6.jpg new file mode 100644 index 0000000..756e2eb Binary files /dev/null and b/src/main/resources/images/6.jpg differ diff --git a/src/main/resources/images/ECSGames.png b/src/main/resources/images/ECSGames.png new file mode 100644 index 0000000..c0af4a1 Binary files /dev/null and b/src/main/resources/images/ECSGames.png differ diff --git a/src/main/resources/images/Instructions.png b/src/main/resources/images/Instructions.png new file mode 100644 index 0000000..d920888 Binary files /dev/null and b/src/main/resources/images/Instructions.png differ diff --git a/src/main/resources/images/TetrECS.png b/src/main/resources/images/TetrECS.png new file mode 100644 index 0000000..99468eb Binary files /dev/null and b/src/main/resources/images/TetrECS.png differ diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..f0c0b06 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/end.wav b/src/main/resources/music/end.wav new file mode 100644 index 0000000..722f64b Binary files /dev/null and b/src/main/resources/music/end.wav differ diff --git a/src/main/resources/music/game.wav b/src/main/resources/music/game.wav new file mode 100644 index 0000000..152d55c Binary files /dev/null and b/src/main/resources/music/game.wav differ diff --git a/src/main/resources/music/game_start.wav b/src/main/resources/music/game_start.wav new file mode 100644 index 0000000..a263551 Binary files /dev/null and b/src/main/resources/music/game_start.wav differ diff --git a/src/main/resources/music/menu.mp3 b/src/main/resources/music/menu.mp3 new file mode 100644 index 0000000..0724b65 Binary files /dev/null and b/src/main/resources/music/menu.mp3 differ diff --git a/src/main/resources/sounds/clear.wav b/src/main/resources/sounds/clear.wav new file mode 100644 index 0000000..dc2f867 Binary files /dev/null and b/src/main/resources/sounds/clear.wav differ diff --git a/src/main/resources/sounds/explode.wav b/src/main/resources/sounds/explode.wav new file mode 100644 index 0000000..4087204 Binary files /dev/null and b/src/main/resources/sounds/explode.wav differ diff --git a/src/main/resources/sounds/fail.wav b/src/main/resources/sounds/fail.wav new file mode 100644 index 0000000..3b89af9 Binary files /dev/null and b/src/main/resources/sounds/fail.wav differ diff --git a/src/main/resources/sounds/intro.mp3 b/src/main/resources/sounds/intro.mp3 new file mode 100644 index 0000000..bef60c8 Binary files /dev/null and b/src/main/resources/sounds/intro.mp3 differ diff --git a/src/main/resources/sounds/level.wav b/src/main/resources/sounds/level.wav new file mode 100644 index 0000000..57326ea Binary files /dev/null and b/src/main/resources/sounds/level.wav differ diff --git a/src/main/resources/sounds/lifegain.wav b/src/main/resources/sounds/lifegain.wav new file mode 100644 index 0000000..e51aeda Binary files /dev/null and b/src/main/resources/sounds/lifegain.wav differ diff --git a/src/main/resources/sounds/lifelose.wav b/src/main/resources/sounds/lifelose.wav new file mode 100644 index 0000000..7659408 Binary files /dev/null and b/src/main/resources/sounds/lifelose.wav differ diff --git a/src/main/resources/sounds/message.wav b/src/main/resources/sounds/message.wav new file mode 100644 index 0000000..5debe50 Binary files /dev/null and b/src/main/resources/sounds/message.wav differ diff --git a/src/main/resources/sounds/place.wav b/src/main/resources/sounds/place.wav new file mode 100644 index 0000000..e784399 Binary files /dev/null and b/src/main/resources/sounds/place.wav differ diff --git a/src/main/resources/sounds/pling.wav b/src/main/resources/sounds/pling.wav new file mode 100644 index 0000000..cf09ba1 Binary files /dev/null and b/src/main/resources/sounds/pling.wav differ diff --git a/src/main/resources/sounds/rotate.wav b/src/main/resources/sounds/rotate.wav new file mode 100755 index 0000000..37f4e5e Binary files /dev/null and b/src/main/resources/sounds/rotate.wav differ diff --git a/src/main/resources/sounds/transition.wav b/src/main/resources/sounds/transition.wav new file mode 100644 index 0000000..12d91fd Binary files /dev/null and b/src/main/resources/sounds/transition.wav differ diff --git a/src/main/resources/style/Orbitron-Black.ttf b/src/main/resources/style/Orbitron-Black.ttf new file mode 100644 index 0000000..b02201b Binary files /dev/null and b/src/main/resources/style/Orbitron-Black.ttf differ diff --git a/src/main/resources/style/Orbitron-Bold.ttf b/src/main/resources/style/Orbitron-Bold.ttf new file mode 100644 index 0000000..214b73e Binary files /dev/null and b/src/main/resources/style/Orbitron-Bold.ttf differ diff --git a/src/main/resources/style/Orbitron-ExtraBold.ttf b/src/main/resources/style/Orbitron-ExtraBold.ttf new file mode 100644 index 0000000..f9fc3b5 Binary files /dev/null and b/src/main/resources/style/Orbitron-ExtraBold.ttf differ diff --git a/src/main/resources/style/Orbitron-Medium.ttf b/src/main/resources/style/Orbitron-Medium.ttf new file mode 100644 index 0000000..7367780 Binary files /dev/null and b/src/main/resources/style/Orbitron-Medium.ttf differ diff --git a/src/main/resources/style/Orbitron-Regular.ttf b/src/main/resources/style/Orbitron-Regular.ttf new file mode 100644 index 0000000..dcfab7f Binary files /dev/null and b/src/main/resources/style/Orbitron-Regular.ttf differ diff --git a/src/main/resources/style/Orbitron-SemiBold.ttf b/src/main/resources/style/Orbitron-SemiBold.ttf new file mode 100644 index 0000000..3fa5364 Binary files /dev/null and b/src/main/resources/style/Orbitron-SemiBold.ttf differ diff --git a/src/main/resources/style/game.css b/src/main/resources/style/game.css new file mode 100644 index 0000000..2b77c42 --- /dev/null +++ b/src/main/resources/style/game.css @@ -0,0 +1,209 @@ +.gamepane { + -fx-background-color: black; +} + +.intro { + -fx-background-color: black; +} + +.menu-background { + -fx-background-image: url("../images/1.jpg"); + -fx-background-size: cover; +} + +.challenge-background { + -fx-background-image: url("../images/2.jpg"); + -fx-background-size: cover; +} + +.menu { + -fx-padding: 10; +} + +.menuItem { + -fx-fill: white; + -fx-font-family: 'Orbitron'; + -fx-font-size: 32px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.menuItem:hover { + -fx-fill: yellow; +} + +.menuItem.selected { + -fx-fill: yellow; +} + +.bigtitle { + -fx-fill: yellow; + -fx-font-family: 'Orbitron'; + -fx-font-size: 64px; + -fx-font-weight: 900; + -fx-border-color: black; + -fx-stroke: black; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.title { + -fx-fill: white; + -fx-font-family: 'Orbitron'; + -fx-font-size: 32px; + -fx-font-weight: 900; + -fx-border-color: black; + -fx-stroke: black; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.heading { + -fx-fill: white; + -fx-font-family: 'Orbitron'; + -fx-font-size: 20px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.score { + -fx-fill: yellow; + -fx-font-family: 'Orbitron'; + -fx-font-size: 36px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; + -fx-text-alignment: center; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.level { + -fx-fill: #ff6600; + -fx-font-family: 'Orbitron'; + -fx-font-size: 24px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; + -fx-text-alignment: center; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.hiscore { + -fx-fill: orange; + -fx-font-family: 'Orbitron'; + -fx-font-size: 24px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; + -fx-text-alignment: center; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.lives { + -fx-fill: yellow; + -fx-font-family: 'Orbitron'; + -fx-font-size: 36px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; + -fx-text-alignment: center; + -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1); +} + +.scorelist { + -fx-font-size: 20px; + -fx-font-family: 'Orbitron'; +} + +.scoreitem { + +} + +.scorer { + +} + +.myscore { + -fx-font-weight: 700; +} + +.deadscore { + -fx-strikethrough: true; +} + +.points { +} + +.channelList { + +} + +.channelItem { + -fx-fill: white; + -fx-font-family: 'Orbitron'; + -fx-font-size: 16px; + -fx-font-weight: 700; + -fx-border-color: black; + -fx-stroke: black; +} +.channelItem.selected { + -fx-fill: yellow; +} + +.leaderboard { + -fx-font-size: 16px; +} + +.gameBox { + -fx-padding: 10; + -fx-background-color: rgba(0, 0, 0, 0.5); + -fx-border-color: white; + -fx-border-width: 1; +} + +.scroller { + -fx-background-color: transparent; +} + +.scroller .viewport { + -fx-background-color: transparent; +} + +.messages { + -fx-background-color: transparent; + -fx-font-size: 12px; + -fx-font-family: 'Orbitron'; +} + +.messages Text { + -fx-fill: white; +} + +TextField { + -fx-border-color: white; + -fx-border-width: 1px; + -fx-background-color: rgba(0,0,0,0.5); + -fx-text-fill: white; + -fx-prompt-text-fill: grey; +} + +.playerBox { + -fx-font-size: 14px; + -fx-font-family: 'Orbitron'; +} + +.playerBox Text { + -fx-fill: white; +} + +.myname { + -fx-font-weight: 700; +} + +.instructions { + -fx-font-size: 10px; + -fx-font-family: 'Orbitron'; + -fx-fill: white; +} \ No newline at end of file