diff --git a/.idea/comp1206-cw.iml b/.idea/comp1206-cw.iml new file mode 100644 index 0000000000000000000000000000000000000000..d6ebd4805981b8400db3e3291c74a743fef9a824 --- /dev/null +++ b/.idea/comp1206-cw.iml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$" /> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/tetrecs/.idea/uiDesigner.xml b/tetrecs/.idea/uiDesigner.xml new file mode 100644 index 0000000000000000000000000000000000000000..e96534fb27b68192f27f985d3879e173ec77adb8 --- /dev/null +++ b/tetrecs/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> + <preferred-size width="-1" height="20" /> + </default-constraints> + </item> + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> + </item> + </group> + </component> +</project> \ No newline at end of file diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java index 272c52512ac6e503de2371e21119a018c7cd5bd6..50106edfd52b16ad225fe70cba9d56721809e5ca 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBlock.java @@ -11,179 +11,186 @@ import org.apache.logging.log4j.Logger; /** * The Visual User Interface component representing a single block in the grid. - * + * <p> * Extends Canvas and is responsible for drawing itself. - * + * <p> * Displays an empty square (when the value is 0) or a coloured square depending on value. - * + * <p> * 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.0D, 0.0D, this.width, this.height); - - //Fill - Color gradStart = Color.color(0.0D, 0.0D, 0.6D, 0.3D); - Color gradEnd = Color.color(0.0D, 0.0D, 0.2D, 0.5D); - gc.setFill(new LinearGradient(0.0D, 0.0D, 1.0D, 1.0D, true, CycleMethod.REFLECT, new Stop(0.0D, gradStart), new Stop(1.0D, gradEnd))); - gc.fillRect(0.0D, 0.0D, this.width, this.height); - - //Border - gc.setStroke(Color.WHITE); - gc.setGlobalAlpha(0.7); - 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.setGlobalAlpha(1); - 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); + 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.0D, 0.0D, this.width, this.height); + + //Fill + Color gradStart = Color.color(0.0D, 0.0D, 0.6D, 0.3D); + Color gradEnd = Color.color(0.0D, 0.0D, 0.2D, 0.5D); + gc.setFill(new LinearGradient(0.0D, 0.0D, 1.0D, 1.0D, true, CycleMethod.REFLECT, new Stop(0.0D, gradStart), new Stop(1.0D, gradEnd))); + gc.fillRect(0.0D, 0.0D, this.width, this.height); + + //Border + gc.setStroke(Color.WHITE); + gc.setGlobalAlpha(0.7); + 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.setGlobalAlpha(1); + 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); + } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java index b587148c66b16f7940a4ec76a0a9e762c44016b5..1131d32556301904654d3e80be903938882a54a5 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/GameBoard.java @@ -1,175 +1,199 @@ package uk.ac.soton.comp1206.component; +import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.GridPane; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import uk.ac.soton.comp1206.event.BlockClickedListener; +import uk.ac.soton.comp1206.event.RightClickListener; 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. - * + * <p> * 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. - * + * <p> * 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(); + 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 + */ + protected BlockClickedListener blockClickedListener; + + protected RightClickListener rightClickListener; + + /** + * 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 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; + } + + /** + * 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)); + + block.setOnMouseClicked(e -> { + if (e.getButton() == MouseButton.PRIMARY) { + blockClicked(block); + } else { + blockRightClicked(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; + } + + public void setOnRightClick(RightClickListener listener) { + this.rightClickListener = listener; + } + + /** + * Triggered when a block is clicked. Call the attached listener. + * + * @param block block clicked on + */ + private void blockClicked(GameBlock block) { + logger.info("Block clicked: {}", block); + + if (blockClickedListener != null) { + blockClickedListener.blockClicked(block); } + } - /** - * 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); - } + private void blockRightClicked(GameBlock block) { + if (this.rightClickListener != null) { + this.rightClickListener.rightClicked(); } + } } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java index c2d35fd34beeccee411ac277c9a55fc9069c180a..5abd200729cf1db9f272d2e508fd3cfa532cf91e 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/PieceBoard.java @@ -1,16 +1,29 @@ package uk.ac.soton.comp1206.component; +import uk.ac.soton.comp1206.event.BlockClickedListener; import uk.ac.soton.comp1206.game.GamePiece; public class PieceBoard extends GameBoard { - private GamePiece pieceToDisplay; - public PieceBoard(double width, double height) { super(3, 3, width, height); } + public void displayPiece(GamePiece piece, int placeX, int placeY) { + clear(); + this.grid.playPiece(piece, placeX + 1, placeY + 1); + } + public void displayPiece(GamePiece piece) { - this.pieceToDisplay = piece; + displayPiece(piece, 0, 0); + } + + public void clear() { + this.grid.clear(); + } + + public void setOnClick(BlockClickedListener handler) { + this.blockClickedListener = handler; } + } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java new file mode 100644 index 0000000000000000000000000000000000000000..09971bd2dae205469f3af8bfafb4915169e935f0 --- /dev/null +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/NextPieceListener.java @@ -0,0 +1,7 @@ +package uk.ac.soton.comp1206.event; + +import uk.ac.soton.comp1206.game.GamePiece; + +public interface NextPieceListener { + void nextPiece(GamePiece paramGamePiece); +} diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java new file mode 100644 index 0000000000000000000000000000000000000000..9b2c5abb20bc053d570ece6d50f1230353e4f2a8 --- /dev/null +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/RightClickListener.java @@ -0,0 +1,5 @@ +package uk.ac.soton.comp1206.event; + +public interface RightClickListener { + void rightClicked(); +} diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java index af904f43d19766988f4cea34288d1b0582a9e4a7..fb971dd7fc20711414c1b9c6b9b8767c450dbc2d 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java @@ -5,6 +5,7 @@ import javafx.beans.property.SimpleIntegerProperty; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import uk.ac.soton.comp1206.component.GameBlock; +import uk.ac.soton.comp1206.event.NextPieceListener; import uk.ac.soton.comp1206.utility.Multimedia; import java.util.ArrayList; @@ -43,6 +44,8 @@ public class Game { private int remainder = 0; + protected NextPieceListener nextPieceHandler = null; + /** * Create a new game with the specified rows and columns. Creates a corresponding grid model. * @param cols number of columns @@ -77,7 +80,6 @@ public class Game { this.nextPiece = spawnPiece(); nextPiece(); - this.currentPiece = spawnPiece(); } /** @@ -93,14 +95,19 @@ public class Game { logger.info("Block clicked at x = {} y = {}",x,y); boolean placed = grid.playPiece(currentPiece, x, y); if (placed) { + logger.info("Placing block at x:{} y:{}",x,y); + Multimedia.playSound("place.wav"); this.afterPiece(); this.nextPiece(); + } else { + logger.info("Cannot place piece here"); + Multimedia.playSound("fail.wav"); } } public GamePiece spawnPiece() { Random rand = new Random(); - GamePiece piece = GamePiece.createPiece(3, rand.nextInt(3)); + GamePiece piece = GamePiece.createPiece(rand.nextInt(15), rand.nextInt(3)); logger.info("Spawning piece: {}" , piece); return piece; } @@ -109,6 +116,10 @@ public class Game { this.currentPiece = this.nextPiece; this.nextPiece = spawnPiece(); + if (this.nextPieceHandler != null) { + this.nextPieceHandler.nextPiece(this.currentPiece); + } + logger.info("Current piece is : {}", this.currentPiece); logger.info("Next piece is: {}", this.nextPiece); @@ -195,6 +206,12 @@ public class Game { } } + public void swapPiece() { + GamePiece tempPiece = this.currentPiece; + this.currentPiece = this.nextPiece; + this.nextPiece = tempPiece; + } + /** * Get the grid model inside this game representing the game state of the board * @return game grid model @@ -219,6 +236,22 @@ public class Game { return rows; } + public GamePiece getCurrentPiece() { + return currentPiece; + } + + public GamePiece getNextPiece() { + return nextPiece; + } + + public void setOnNextPiece(NextPieceListener handler) { + this.nextPieceHandler = handler; + } + + public void rotatePiece(int rotations) { + this.currentPiece.rotate(rotations); + } + public IntegerProperty getLevel() { return level; } diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java index 7905236966906c866d7b7a80091eaa2baa898609..1e66ea3ae457426cbc9587f4ee9f0885da94d37f 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Grid.java @@ -101,8 +101,6 @@ public class Grid { int value = blocks[x][y]; if (value == 0) continue; if (get(x + placeX, y + placeY) != 0) { - logger.info("Cannot place piece here"); - Multimedia.playSound("fail.wav"); return false; } } @@ -113,8 +111,6 @@ public class Grid { public boolean playPiece(GamePiece gamePiece, int placeX, int placeY) { if (!this.canPlayPiece(gamePiece, placeX, placeY)) return false; - logger.info("Placing block at x:{} y:{}",placeX,placeY); - Multimedia.playSound("place.wav"); int[][] blocks = gamePiece.getBlocks(); placeX = placeX - 1; placeY = placeY - 1; @@ -129,6 +125,14 @@ public class Grid { return true; } + public void clear() { + for (int y = 0; y < this.rows; y++) { + for (int x = 0; x < this.cols; x++) { + this.grid[x][y].set(0); + } + } + } + /** * Get the number of columns in this game * @return number of columns diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java index b5fbf9609d5a9633d074bac1042d3971476f5bf4..d2461ba4f84ec4c067861b96091b054b833ac55a 100644 --- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java +++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java @@ -15,6 +15,7 @@ import uk.ac.soton.comp1206.component.GameBlock; import uk.ac.soton.comp1206.component.GameBoard; import uk.ac.soton.comp1206.component.PieceBoard; import uk.ac.soton.comp1206.game.Game; +import uk.ac.soton.comp1206.game.GamePiece; import uk.ac.soton.comp1206.ui.GamePane; import uk.ac.soton.comp1206.ui.GameWindow; import uk.ac.soton.comp1206.utility.Multimedia; @@ -29,6 +30,10 @@ public class ChallengeScene extends BaseScene { private static final Logger logger = LogManager.getLogger(MenuScene.class); protected Game game; + protected GameBoard board; + protected PieceBoard currentPieceBoard; + protected PieceBoard nextPieceBoard; + /** * Create a new Single Player challenge scene * @param gameWindow the Game Window @@ -50,7 +55,6 @@ public class ChallengeScene extends BaseScene { root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight()); StackPane challengePane = new StackPane(); - challengePane.setBackground(new Background(new BackgroundFill[] { new BackgroundFill((Paint) Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY) })); challengePane.setMaxWidth(gameWindow.getWidth()); challengePane.setMaxHeight(gameWindow.getHeight()); challengePane.getStyleClass().add("menu-background"); @@ -114,11 +118,12 @@ public class ChallengeScene extends BaseScene { public HBox buildBoards() { HBox boards = new HBox(); - var board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2); + this.board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2,gameWindow.getWidth()/2); //Handle block on gameboard grid being clicked - board.setOnBlockClick(this::blockClicked); + this.board.setOnBlockClick(this::blockClicked); + this.board.setOnRightClick(this::rotateBlock); - boards.getChildren().add(board); + boards.getChildren().add(this.board); VBox gameInfo = new VBox(); @@ -143,11 +148,13 @@ public class ChallengeScene extends BaseScene { incomingTitle.getStyleClass().add("heading"); gameInfo.getChildren().add(incomingTitle); - PieceBoard currentPieceBoard = new PieceBoard(gameWindow.getWidth()/6, gameWindow.getWidth()/6); - gameInfo.getChildren().add(currentPieceBoard); + this.currentPieceBoard = new PieceBoard(gameWindow.getWidth()/6, gameWindow.getWidth()/6); + this.currentPieceBoard.setOnClick(this::rotateBlock); + gameInfo.getChildren().add(this.currentPieceBoard); - PieceBoard nextPieceBoard = new PieceBoard(gameWindow.getWidth()/10, gameWindow.getWidth()/10); - gameInfo.getChildren().add(nextPieceBoard); + this.nextPieceBoard = new PieceBoard(gameWindow.getWidth()/10, gameWindow.getWidth()/10); + this.nextPieceBoard.setOnClick(this::swapPiece); + gameInfo.getChildren().add(this.nextPieceBoard); gameInfo.setAlignment(Pos.CENTER); gameInfo.setSpacing(10); @@ -160,6 +167,13 @@ public class ChallengeScene extends BaseScene { return boards; } + private void swapPiece(GameBlock gameBlock) { + logger.info("Swapping current and next piece"); + this.currentPieceBoard.displayPiece(this.game.getNextPiece()); + this.nextPieceBoard.displayPiece(this.game.getCurrentPiece()); + this.game.swapPiece(); + } + /** * Handle when a block is clicked * @param gameBlock the Game Block that was clocked @@ -168,14 +182,27 @@ public class ChallengeScene extends BaseScene { game.blockClicked(gameBlock); } + private void rotateBlock(int rotations) { + logger.info("Rotating block"); + Multimedia.playSound("rotate.wav"); + this.game.rotatePiece(rotations); + this.currentPieceBoard.displayPiece(this.game.getCurrentPiece()); + } + + private void rotateBlock(GameBlock gameBlock) { + rotateBlock(1); + } + + private void rotateBlock() { + rotateBlock(1); + } /** * Setup the game object and model */ public void setupGame() { logger.info("Starting a new challenge"); - Multimedia.playBackgroundMusic("game_start.wav", false); - Multimedia.getBackgroundPlayer().setOnEndOfMedia(() -> Multimedia.playBackgroundMusic("game.wav", true)); + //Start new game game = new Game(5, 5); } @@ -186,7 +213,16 @@ public class ChallengeScene extends BaseScene { @Override public void initialise() { logger.info("Initialising Challenge"); + Multimedia.playBackgroundMusic("game_start.wav", false); + Multimedia.getBackgroundPlayer().setOnEndOfMedia(() -> Multimedia.playBackgroundMusic("game.wav", true)); + + this.game.setOnNextPiece(this::nextPiece); game.start(); } + protected void nextPiece(GamePiece nextPiece) { + logger.info("Next piece to place: " + nextPiece); + this.currentPieceBoard.displayPiece(nextPiece); + this.nextPieceBoard.displayPiece(this.game.getNextPiece()); + } } diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class b/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class index 5f8a073ae3bae068119a84b6ff70d71bff47c4bc..7d7f7ec15155e1956eee50ce3814870034b662b0 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/component/GameBoard.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class index 54142e5420e92cd5cec8ffadb11a9b80935bf8b5..9bee46cc02c85f4d236ae529187f1b0a6ca79b2f 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class index 002e059f2affcc6dc3522671f50858fd1a9f5865..915e5a5f1411e7a79631ab290a29233d57d65412 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Grid.class differ diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class index db3f11b372bf64a21aa8eddfd74d0f4317da0970..307d07de2a84933ea7a5dc80b9504e5c98c000a8 100644 Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class differ diff --git a/tetrecs/tetrecs.iml b/tetrecs/tetrecs.iml new file mode 100644 index 0000000000000000000000000000000000000000..34834c254facdd44a9b4d1422658a5542f952166 --- /dev/null +++ b/tetrecs/tetrecs.iml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> + <component name="CheckStyle-IDEA-Module"> + <option name="configuration"> + <map /> + </option> + </component> + <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_17"> + <output url="file://$MODULE_DIR$/target/classes" /> + <output-test url="file://$MODULE_DIR$/target/test-classes" /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <excludeFolder url="file://$MODULE_DIR$/${project.build.directory}/classes" /> + <excludeFolder url="file://$MODULE_DIR$/${project.build.directory}/test-classes" /> + <excludeFolder url="file://$MODULE_DIR$/target" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-controls:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-controls:win:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-graphics:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-graphics:win:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-base:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-base:win:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-fxml:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-fxml:win:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-media:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: org.openjfx:javafx-media:win:17.0.2" level="project" /> + <orderEntry type="library" name="Maven: com.neovisionaries:nv-websocket-client:2.14" level="project" /> + <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.17.1" level="project" /> + <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-core:2.17.1" level="project" /> + </component> +</module> \ No newline at end of file