[FEAT] Add basic game logic and audio
This commit is contained in:
@@ -8,8 +8,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,4 +181,12 @@ public class GameBlock extends Canvas {
|
||||
value.bind(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GameBlock{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
", value=" + value.get() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
package uk.mgrove.ac.soton.comp1206.game;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.component.GameBlock;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(Game.class);
|
||||
|
||||
/**
|
||||
* Random number generator
|
||||
*/
|
||||
private final Random random = new Random();
|
||||
|
||||
/**
|
||||
* Number of rows
|
||||
*/
|
||||
@@ -27,6 +39,28 @@ public class Game {
|
||||
*/
|
||||
protected final Grid grid;
|
||||
|
||||
/**
|
||||
* Current game piece player is using
|
||||
*/
|
||||
private GamePiece currentPiece;
|
||||
|
||||
/**
|
||||
* Player's current score
|
||||
*/
|
||||
protected IntegerProperty score = new SimpleIntegerProperty(0);
|
||||
/**
|
||||
* Player's current level
|
||||
*/
|
||||
protected IntegerProperty level = new SimpleIntegerProperty(0);
|
||||
/**
|
||||
* Player's number of remaining lives
|
||||
*/
|
||||
protected IntegerProperty lives = new SimpleIntegerProperty(3);
|
||||
/**
|
||||
* Player's current multiplier
|
||||
*/
|
||||
protected IntegerProperty multiplier = new SimpleIntegerProperty(1);
|
||||
|
||||
/**
|
||||
* Create a new game with the specified rows and columns. Creates a corresponding grid model.
|
||||
* @param cols number of columns
|
||||
@@ -48,11 +82,33 @@ public class Game {
|
||||
initialiseGame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next piece for the player to use in the game
|
||||
* @return the next piece
|
||||
*/
|
||||
public GamePiece nextPiece() {
|
||||
currentPiece = spawnPiece();
|
||||
logger.info("Next piece is: {}", currentPiece);
|
||||
return currentPiece;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new game piece
|
||||
* @return newly created piece
|
||||
*/
|
||||
public GamePiece spawnPiece() {
|
||||
var maxPieces = GamePiece.PIECES;
|
||||
var randomPieceNumber = random.nextInt(maxPieces);
|
||||
logger.info("Picking random piece: {}", randomPieceNumber);
|
||||
return GamePiece.createPiece(randomPieceNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise a new game and set up anything that needs to be done at the start
|
||||
*/
|
||||
public void initialiseGame() {
|
||||
logger.info("Initialising game");
|
||||
nextPiece();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,15 +120,80 @@ public class Game {
|
||||
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;
|
||||
if (grid.canPlayPiece(currentPiece,x,y)) {
|
||||
grid.playPiece(currentPiece,x,y);
|
||||
afterPiece();
|
||||
nextPiece();
|
||||
} else {
|
||||
// can't play the piece
|
||||
}
|
||||
}
|
||||
|
||||
//Update the grid with the new value
|
||||
grid.set(x,y,newValue);
|
||||
/**
|
||||
* Handle additional processing after piece has been played - clear lines of blocks
|
||||
*/
|
||||
public void afterPiece() {
|
||||
Set<IntegerProperty> blocksToRemove = new HashSet<>();
|
||||
int linesToRemove = 0;
|
||||
|
||||
for (var x=0; x < grid.getCols(); x++) {
|
||||
List<IntegerProperty> columnBlocksToRemove = new ArrayList<>();
|
||||
for (var y=0; y < grid.getRows(); y++) {
|
||||
// if column isn't full then move to next column
|
||||
if (grid.get(x,y) <= 0) break;
|
||||
|
||||
columnBlocksToRemove.add(grid.getGridProperty(x,y));
|
||||
}
|
||||
// if column is full then store blocks to reset
|
||||
if (columnBlocksToRemove.size() == grid.getRows()) {
|
||||
for (var block : columnBlocksToRemove) {
|
||||
blocksToRemove.add(block);
|
||||
linesToRemove++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do the same for rows
|
||||
for (var y=0; y < grid.getRows(); y++) {
|
||||
List<IntegerProperty> rowBlocksToRemove = new ArrayList<>();
|
||||
for (var x=0; x < grid.getCols(); x++) {
|
||||
// if row isn't full then move to next row
|
||||
if (grid.get(x,y) <= 0) break;
|
||||
|
||||
rowBlocksToRemove.add(grid.getGridProperty(x,y));
|
||||
}
|
||||
// if row is full then store blocks to reset
|
||||
if (rowBlocksToRemove.size() == grid.getCols()) {
|
||||
for (var block : rowBlocksToRemove) {
|
||||
blocksToRemove.add(block);
|
||||
linesToRemove++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update score and multiplier
|
||||
score(linesToRemove,blocksToRemove.size());
|
||||
|
||||
// reset blocks that need resetting
|
||||
for (var block : blocksToRemove) {
|
||||
block.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the score, multiplier, and level depending on the number of lines and blocks that have been cleared
|
||||
* @param lines number of lines cleared
|
||||
* @param blocks number of blocks cleared
|
||||
*/
|
||||
private void score(int lines, int blocks) {
|
||||
var addScore = lines * blocks * 10 * multiplier.get();
|
||||
score.set(score.get() + addScore);
|
||||
|
||||
if (lines > 0) multiplier.set(multiplier.get() + 1);
|
||||
else multiplier.set(1);
|
||||
|
||||
// set level
|
||||
level.set((int) Math.floor((double) score.get() / 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,5 +220,95 @@ public class Game {
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the player's current level
|
||||
* @return current level
|
||||
*/
|
||||
public int getLevel() {
|
||||
return level.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player's current level
|
||||
*/
|
||||
public void setLevel(int level) {
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's current level property
|
||||
* @return player's current level property
|
||||
*/
|
||||
public IntegerProperty levelProperty() {
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's remaining lives
|
||||
* @return number of remaining lives
|
||||
*/
|
||||
public int getLives() {
|
||||
return lives.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player's remaining lives
|
||||
*/
|
||||
public void setLives(int lives) {
|
||||
this.lives.set(lives);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's remaining lives property
|
||||
* @return player's remaining lives property
|
||||
*/
|
||||
public IntegerProperty livesProperty() {
|
||||
return lives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's current multiplier
|
||||
* @return current multiplier
|
||||
*/
|
||||
public int getMultiplier() {
|
||||
return multiplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player's current multiplier
|
||||
*/
|
||||
public void setMultiplier(int multiplier) {
|
||||
this.multiplier.set(multiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's current multiplier property
|
||||
* @return player's current multiplier property
|
||||
*/
|
||||
public IntegerProperty multiplierProperty() {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's current score
|
||||
* @return current score
|
||||
*/
|
||||
public int getScore() {
|
||||
return score.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player's current score
|
||||
*/
|
||||
public void setScore(int score) {
|
||||
this.score.set(score);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player's current score property
|
||||
* @return player's current score property
|
||||
*/
|
||||
public IntegerProperty scoreProperty() {
|
||||
return score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package uk.mgrove.ac.soton.comp1206.game;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -16,6 +19,11 @@ import javafx.beans.property.SimpleIntegerProperty;
|
||||
*/
|
||||
public class Grid {
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static final Logger logger = LogManager.getLogger(Grid.class);
|
||||
|
||||
/**
|
||||
* The number of columns in this grid
|
||||
*/
|
||||
@@ -87,6 +95,58 @@ public class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether piece can be placed in grid at given location
|
||||
* @param piece piece to check
|
||||
* @param placeX x-coordinate to check
|
||||
* @param placeY y-coordinate to check
|
||||
* @return whether piece can be placed in this location
|
||||
*/
|
||||
public boolean canPlayPiece(GamePiece piece, int placeX, int placeY) {
|
||||
logger.info("Checking if piece {} can be played at {},{}", piece, placeX, placeY);
|
||||
|
||||
int[][] blocks = piece.getBlocks();
|
||||
|
||||
for (var blockX = 0; blockX < blocks.length; blockX++) {
|
||||
for (var blockY = 0; blockY < blocks.length; blockY++) {
|
||||
// check if this block can be placed on grid
|
||||
var blockValue = blocks[blockX][blockY];
|
||||
var x = placeX + blockX - 1;
|
||||
var y = placeY + blockY - 1;
|
||||
var gridValue = get(x,y);
|
||||
if (blockValue != 0 && gridValue != 0) {
|
||||
logger.info("Unable to place block due to conflict at {},{}", x, y);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a piece at given location in the grid by updating the grid with the piece blocks
|
||||
* @param piece piece to place
|
||||
* @param placeX x-coordinate to check
|
||||
* @param placeY y-coordinate to check
|
||||
*/
|
||||
public void playPiece(GamePiece piece, int placeX, int placeY) {
|
||||
logger.info("Playing piece {} at {},{}", piece, placeX, placeY);
|
||||
int value = piece.getValue();
|
||||
int[][] blocks = piece.getBlocks();
|
||||
|
||||
// return if piece can't be played
|
||||
if (!canPlayPiece(piece, placeX, placeY)) return;
|
||||
|
||||
for (var blockX = 0; blockX < blocks.length; blockX++) {
|
||||
for (var blockY = 0; blockY < blocks.length; blockY++) {
|
||||
if (blocks[blockX][blockY] > 0) {
|
||||
set(placeX + blockX - 1, placeY + blockY - 1, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of columns in this game
|
||||
* @return number of columns
|
||||
|
||||
@@ -8,6 +8,8 @@ import uk.mgrove.ac.soton.comp1206.component.GameBoard;
|
||||
import uk.mgrove.ac.soton.comp1206.game.Game;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GamePane;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.StatsMenu;
|
||||
import uk.mgrove.ac.soton.comp1206.util.Multimedia;
|
||||
|
||||
/**
|
||||
* The Single Player challenge scene. Holds the UI for the single player challenge mode in the game.
|
||||
@@ -49,6 +51,11 @@ public class ChallengeScene extends BaseScene {
|
||||
var board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2);
|
||||
mainPane.setCenter(board);
|
||||
|
||||
var statsMenu = new StatsMenu(game.scoreProperty(),game.levelProperty(),game.livesProperty(),game.multiplierProperty());
|
||||
mainPane.setRight(statsMenu);
|
||||
|
||||
Multimedia.playMusic("music/game.wav");
|
||||
|
||||
//Handle block on game board grid being clicked
|
||||
board.setOnBlockClick(this::blockClicked);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GamePane;
|
||||
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;
|
||||
import uk.mgrove.ac.soton.comp1206.util.Multimedia;
|
||||
|
||||
/**
|
||||
* The main menu of the game. Provides a gateway to the rest of the game.
|
||||
@@ -52,6 +53,8 @@ public class MenuScene extends BaseScene {
|
||||
var button = new Button("Play");
|
||||
mainPane.setCenter(button);
|
||||
|
||||
Multimedia.playMusic("music/menu.mp3");
|
||||
|
||||
//Bind the button action to the startGame method in the menu
|
||||
button.setOnAction(this::startGame);
|
||||
}
|
||||
|
||||
49
src/main/java/uk/mgrove/ac/soton/comp1206/ui/StatsMenu.java
Normal file
49
src/main/java/uk/mgrove/ac/soton/comp1206/ui/StatsMenu.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
/**
|
||||
* Stats menu class to show basic stats about game status
|
||||
*/
|
||||
public class StatsMenu extends VBox {
|
||||
|
||||
/**
|
||||
* Player's current score
|
||||
*/
|
||||
private final Text score = new Text("0");
|
||||
/**
|
||||
* Player's current level
|
||||
*/
|
||||
private final Text level = new Text("0");
|
||||
/**
|
||||
* Player's remaining lives
|
||||
*/
|
||||
private final Text lives = new Text("3");
|
||||
/**
|
||||
* Player's current multiplier
|
||||
*/
|
||||
private final Text multiplier = new Text("1");
|
||||
|
||||
/**
|
||||
* Initialise the menu by adding basic stats and binding them to properties
|
||||
* @param score score property
|
||||
* @param level level property
|
||||
* @param lives lives property
|
||||
* @param multiplier multiplier property
|
||||
*/
|
||||
public StatsMenu(IntegerProperty score, IntegerProperty level, IntegerProperty lives, IntegerProperty multiplier) {
|
||||
this.score.textProperty().bind(score.asString());
|
||||
this.level.textProperty().bind(level.asString());
|
||||
this.lives.textProperty().bind(lives.asString());
|
||||
this.multiplier.textProperty().bind(multiplier.asString());
|
||||
|
||||
var scoreHeader = new Text("Score");
|
||||
var levelHeader = new Text("Level");
|
||||
var livesHeader = new Text("Lives");
|
||||
var multiplierHeader = new Text("Multiplier");
|
||||
getChildren().addAll(scoreHeader,this.score,levelHeader,this.level,livesHeader,this.lives,multiplierHeader,this.multiplier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package uk.mgrove.ac.soton.comp1206.util;
|
||||
|
||||
import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
|
||||
public class Multimedia {
|
||||
|
||||
/**
|
||||
* Media player for game music
|
||||
*/
|
||||
private static MediaPlayer musicPlayer;
|
||||
/**
|
||||
* Media player for sound effects
|
||||
*/
|
||||
private static MediaPlayer audioPlayer;
|
||||
|
||||
/**
|
||||
* Play sound effect from file
|
||||
* @param filePath file path of audio file
|
||||
*/
|
||||
public static void playAudio(String filePath) {
|
||||
if (audioPlayer != null) audioPlayer.stop();
|
||||
var media = new Media(Multimedia.class.getResource("/" + filePath).toExternalForm());
|
||||
audioPlayer = new MediaPlayer(media);
|
||||
audioPlayer.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Play background music from file
|
||||
* @param filePath file path of audio file
|
||||
*/
|
||||
public static void playMusic(String filePath) {
|
||||
if (musicPlayer != null) musicPlayer.stop();
|
||||
var media = new Media(Multimedia.class.getResource("/" + filePath).toExternalForm());
|
||||
musicPlayer = new MediaPlayer(media);
|
||||
musicPlayer.setAutoPlay(true);
|
||||
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
musicPlayer.play();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user