Updated for 2023
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.DS_Store
|
||||
.classpath
|
||||
.factorypath
|
||||
.idea
|
||||
.idea/
|
||||
.project
|
||||
.settings
|
||||
.settings/
|
||||
scores.txt
|
||||
target
|
||||
target/
|
||||
142
pom.xml
Normal file
@ -0,0 +1,142 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>uk.ac.soton.comp1206</groupId>
|
||||
<artifactId>tetrecs</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>19</maven.compiler.source>
|
||||
<maven.compiler.target>19</maven.compiler.target>
|
||||
<javafx.version>21-ea+5</javafx.version>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>shade</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<classifier>win</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<classifier>mac</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<classifier>linux</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<classifier>win</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<classifier>mac</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<classifier>linux</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>uk.ac.soton.comp1206.Launcher</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.neovisionaries</groupId>
|
||||
<artifactId>nv-websocket-client</artifactId>
|
||||
<version>2.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.20.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.20.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>19</source>
|
||||
<target>19</target>
|
||||
<release>19</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<mainClass>uk.ac.soton.comp1206/uk.ac.soton.comp1206.App</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
16
src/main/java/module-info.java
Normal file
@ -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;
|
||||
}
|
||||
81
src/main/java/uk/ac/soton/comp1206/App.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/java/uk/ac/soton/comp1206/Launcher.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
184
src/main/java/uk/ac/soton/comp1206/component/GameBlock.java
Normal file
@ -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<? extends Number> 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<? extends Number> input) {
|
||||
value.bind(input);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
175
src/main/java/uk/ac/soton/comp1206/component/GameBoard.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
103
src/main/java/uk/ac/soton/comp1206/game/Game.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
224
src/main/java/uk/ac/soton/comp1206/game/GamePiece.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
106
src/main/java/uk/ac/soton/comp1206/game/Grid.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
123
src/main/java/uk/ac/soton/comp1206/network/Communicator.java
Normal file
@ -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<CommunicationsListener> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
56
src/main/java/uk/ac/soton/comp1206/scene/BaseScene.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
83
src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
75
src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
94
src/main/java/uk/ac/soton/comp1206/ui/GamePane.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
163
src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/images/1.jpg
Normal file
|
After Width: | Height: | Size: 423 KiB |
BIN
src/main/resources/images/2.jpg
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
src/main/resources/images/3.jpg
Normal file
|
After Width: | Height: | Size: 426 KiB |
BIN
src/main/resources/images/4.jpg
Normal file
|
After Width: | Height: | Size: 485 KiB |
BIN
src/main/resources/images/5.jpg
Normal file
|
After Width: | Height: | Size: 444 KiB |
BIN
src/main/resources/images/6.jpg
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
src/main/resources/images/ECSGames.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src/main/resources/images/Instructions.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
src/main/resources/images/TetrECS.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
14
src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="INFO">
|
||||
<Appenders>
|
||||
<Console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout
|
||||
pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="debug" additivity="false">
|
||||
<AppenderRef ref="console" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
BIN
src/main/resources/music/end.wav
Normal file
BIN
src/main/resources/music/game.wav
Normal file
BIN
src/main/resources/music/game_start.wav
Normal file
BIN
src/main/resources/music/menu.mp3
Normal file
BIN
src/main/resources/sounds/clear.wav
Normal file
BIN
src/main/resources/sounds/explode.wav
Normal file
BIN
src/main/resources/sounds/fail.wav
Normal file
BIN
src/main/resources/sounds/intro.mp3
Normal file
BIN
src/main/resources/sounds/level.wav
Normal file
BIN
src/main/resources/sounds/lifegain.wav
Normal file
BIN
src/main/resources/sounds/lifelose.wav
Normal file
BIN
src/main/resources/sounds/message.wav
Normal file
BIN
src/main/resources/sounds/place.wav
Normal file
BIN
src/main/resources/sounds/pling.wav
Normal file
BIN
src/main/resources/sounds/rotate.wav
Executable file
BIN
src/main/resources/sounds/transition.wav
Normal file
BIN
src/main/resources/style/Orbitron-Black.ttf
Normal file
BIN
src/main/resources/style/Orbitron-Bold.ttf
Normal file
BIN
src/main/resources/style/Orbitron-ExtraBold.ttf
Normal file
BIN
src/main/resources/style/Orbitron-Medium.ttf
Normal file
BIN
src/main/resources/style/Orbitron-Regular.ttf
Normal file
BIN
src/main/resources/style/Orbitron-SemiBold.ttf
Normal file
209
src/main/resources/style/game.css
Normal file
@ -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;
|
||||
}
|
||||