├── .gitignore ├── README.md ├── bin └── TetrisJFX.jar ├── pom.xml ├── sonar-project.properties └── src ├── main ├── java │ └── com │ │ └── quirko │ │ ├── app │ │ └── GameController.java │ │ ├── gui │ │ ├── GameOverPanel.java │ │ ├── GuiController.java │ │ ├── Main.java │ │ └── NotificationPanel.java │ │ └── logic │ │ ├── Board.java │ │ ├── ClearRow.java │ │ ├── DownData.java │ │ ├── MatrixOperations.java │ │ ├── Score.java │ │ ├── SimpleBoard.java │ │ ├── ViewData.java │ │ ├── bricks │ │ ├── Brick.java │ │ ├── BrickGenerator.java │ │ ├── IBrick.java │ │ ├── JBrick.java │ │ ├── LBrick.java │ │ ├── OBrick.java │ │ ├── RandomBrickGenerator.java │ │ ├── SBrick.java │ │ ├── TBrick.java │ │ └── ZBrick.java │ │ ├── events │ │ ├── EventSource.java │ │ ├── EventType.java │ │ ├── InputEventListener.java │ │ └── MoveEvent.java │ │ └── rotator │ │ ├── BrickRotator.java │ │ └── NextShapeInfo.java └── resources │ ├── background_image.png │ ├── digital.ttf │ ├── gameLayout.fxml │ └── window_style.css └── test └── java └── com └── quirko └── logic ├── MatrixOperationsTest.java ├── bricks └── IBrickTest.java └── rotator └── NextShapeInfoTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .sonar 3 | target 4 | jar_builder.bat -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JavaFX Tetris Clone Game 2 | ============= 3 | 4 | JavaFX Tetris Clone Game 5 | 6 | ## Screenshots 7 |

8 | TetrisJFX Screen Shoot 9 | TetrisJFX Screen Shoot 1 10 |

11 | 12 | ## Gameplay 13 | See Youtube: https://www.youtube.com/watch?v=uzyOOlCvQN0 14 | -------------------------------------------------------------------------------- /bin/TetrisJFX.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javafx-dev/JavaFX-Tetris-Clone/9078cea250826c3d01ffe37033dc26e136229b5a/bin/TetrisJFX.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.quirko 8 | tetris 9 | 1.0-SNAPSHOT 10 | 11 | UTF-8 12 | 1.8 13 | 1.8 14 | 15 | 16 | 17 | 18 | com.zenjava 19 | javafx-maven-plugin 20 | 2.0 21 | 22 | com.quirko.gui.Main 23 | 24 | 25 | 26 | 27 | 28 | 29 | junit 30 | junit 31 | 4.11 32 | 33 | 34 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Required metadata 2 | sonar.projectKey=tetris 3 | sonar.projectName=Tetris FX 4 | sonar.projectVersion=1.0 5 | 6 | # Comma-separated paths to directories with sources (required) 7 | sonar.sources=src/main/java 8 | 9 | #sonar.binaries=target\classes 10 | 11 | # Language 12 | sonar.language=java 13 | 14 | # Encoding of the source files 15 | sonar.sourceEncoding=UTF-8 16 | 17 | sonar.junit.reportsPath=target/surefire-reports 18 | sonar.jacoco.reportPath=target/jacoco.exec 19 | sonar.dynamicAnalysis=reuseReports 20 | sonar.java.coveragePlugin=jacoco -------------------------------------------------------------------------------- /src/main/java/com/quirko/app/GameController.java: -------------------------------------------------------------------------------- 1 | package com.quirko.app; 2 | 3 | import com.quirko.gui.GuiController; 4 | import com.quirko.logic.*; 5 | import com.quirko.logic.events.EventSource; 6 | import com.quirko.logic.events.InputEventListener; 7 | import com.quirko.logic.events.MoveEvent; 8 | 9 | public class GameController implements InputEventListener { 10 | 11 | private Board board = new SimpleBoard(25, 10); 12 | 13 | private final GuiController viewGuiController; 14 | 15 | public GameController(GuiController c) { 16 | viewGuiController = c; 17 | board.createNewBrick(); 18 | viewGuiController.setEventListener(this); 19 | viewGuiController.initGameView(board.getBoardMatrix(), board.getViewData()); 20 | viewGuiController.bindScore(board.getScore().scoreProperty()); 21 | } 22 | 23 | @Override 24 | public DownData onDownEvent(MoveEvent event) { 25 | boolean canMove = board.moveBrickDown(); 26 | ClearRow clearRow = null; 27 | if (!canMove) { 28 | board.mergeBrickToBackground(); 29 | clearRow = board.clearRows(); 30 | if (clearRow.getLinesRemoved() > 0) { 31 | board.getScore().add(clearRow.getScoreBonus()); 32 | } 33 | if (board.createNewBrick()) { 34 | viewGuiController.gameOver(); 35 | } 36 | 37 | viewGuiController.refreshGameBackground(board.getBoardMatrix()); 38 | 39 | } else { 40 | if (event.getEventSource() == EventSource.USER) { 41 | board.getScore().add(1); 42 | } 43 | } 44 | return new DownData(clearRow, board.getViewData()); 45 | } 46 | 47 | @Override 48 | public ViewData onLeftEvent(MoveEvent event) { 49 | board.moveBrickLeft(); 50 | return board.getViewData(); 51 | } 52 | 53 | @Override 54 | public ViewData onRightEvent(MoveEvent event) { 55 | board.moveBrickRight(); 56 | return board.getViewData(); 57 | } 58 | 59 | @Override 60 | public ViewData onRotateEvent(MoveEvent event) { 61 | board.rotateLeftBrick(); 62 | return board.getViewData(); 63 | } 64 | 65 | 66 | @Override 67 | public void createNewGame() { 68 | board.newGame(); 69 | viewGuiController.refreshGameBackground(board.getBoardMatrix()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/gui/GameOverPanel.java: -------------------------------------------------------------------------------- 1 | package com.quirko.gui; 2 | 3 | import javafx.scene.control.Label; 4 | import javafx.scene.layout.BorderPane; 5 | 6 | 7 | public class GameOverPanel extends BorderPane { 8 | 9 | public GameOverPanel() { 10 | final Label gameOverLabel = new Label("GAME OVER"); 11 | gameOverLabel.getStyleClass().add("gameOverStyle"); 12 | setCenter(gameOverLabel); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/gui/GuiController.java: -------------------------------------------------------------------------------- 1 | package com.quirko.gui; 2 | 3 | import com.quirko.logic.DownData; 4 | import com.quirko.logic.ViewData; 5 | import com.quirko.logic.events.*; 6 | import javafx.animation.KeyFrame; 7 | import javafx.animation.Timeline; 8 | import javafx.beans.property.BooleanProperty; 9 | import javafx.beans.property.IntegerProperty; 10 | import javafx.beans.property.SimpleBooleanProperty; 11 | import javafx.beans.value.ChangeListener; 12 | import javafx.beans.value.ObservableValue; 13 | import javafx.event.ActionEvent; 14 | import javafx.event.EventHandler; 15 | import javafx.fxml.FXML; 16 | import javafx.fxml.Initializable; 17 | import javafx.scene.Group; 18 | import javafx.scene.control.ToggleButton; 19 | import javafx.scene.effect.Reflection; 20 | import javafx.scene.input.KeyCode; 21 | import javafx.scene.input.KeyEvent; 22 | import javafx.scene.layout.GridPane; 23 | import javafx.scene.paint.Color; 24 | import javafx.scene.paint.Paint; 25 | import javafx.scene.shape.Rectangle; 26 | import javafx.scene.text.Font; 27 | import javafx.scene.text.Text; 28 | import javafx.util.Duration; 29 | 30 | import java.net.URL; 31 | import java.util.ResourceBundle; 32 | 33 | public class GuiController implements Initializable { 34 | 35 | private static final int BRICK_SIZE = 20; 36 | 37 | @FXML 38 | private GridPane gamePanel; 39 | 40 | @FXML 41 | private Text scoreValue; 42 | 43 | @FXML 44 | private Group groupNotification; 45 | 46 | @FXML 47 | private GridPane nextBrick; 48 | 49 | @FXML 50 | private GridPane brickPanel; 51 | 52 | @FXML 53 | private ToggleButton pauseButton; 54 | 55 | @FXML 56 | private GameOverPanel gameOverPanel; 57 | 58 | private Rectangle[][] displayMatrix; 59 | 60 | private InputEventListener eventListener; 61 | 62 | private Rectangle[][] rectangles; 63 | 64 | private Timeline timeLine; 65 | 66 | private final BooleanProperty isPause = new SimpleBooleanProperty(); 67 | 68 | private final BooleanProperty isGameOver = new SimpleBooleanProperty(); 69 | 70 | @Override 71 | public void initialize(URL location, ResourceBundle resources) { 72 | Font.loadFont(getClass().getClassLoader().getResource("digital.ttf").toExternalForm(), 38); 73 | gamePanel.setFocusTraversable(true); 74 | gamePanel.requestFocus(); 75 | gamePanel.setOnKeyPressed(new EventHandler() { 76 | @Override 77 | public void handle(KeyEvent keyEvent) { 78 | if (isPause.getValue() == Boolean.FALSE && isGameOver.getValue() == Boolean.FALSE) { 79 | if (keyEvent.getCode() == KeyCode.LEFT || keyEvent.getCode() == KeyCode.A) { 80 | refreshBrick(eventListener.onLeftEvent(new MoveEvent(EventType.LEFT, EventSource.USER))); 81 | keyEvent.consume(); 82 | } 83 | if (keyEvent.getCode() == KeyCode.RIGHT || keyEvent.getCode() == KeyCode.D) { 84 | refreshBrick(eventListener.onRightEvent(new MoveEvent(EventType.RIGHT, EventSource.USER))); 85 | keyEvent.consume(); 86 | } 87 | if (keyEvent.getCode() == KeyCode.UP || keyEvent.getCode() == KeyCode.W) { 88 | refreshBrick(eventListener.onRotateEvent(new MoveEvent(EventType.ROTATE, EventSource.USER))); 89 | keyEvent.consume(); 90 | } 91 | if (keyEvent.getCode() == KeyCode.DOWN || keyEvent.getCode() == KeyCode.S) { 92 | moveDown(new MoveEvent(EventType.DOWN, EventSource.USER)); 93 | keyEvent.consume(); 94 | } 95 | } 96 | if (keyEvent.getCode() == KeyCode.N) { 97 | newGame(null); 98 | } 99 | if (keyEvent.getCode() == KeyCode.P) { 100 | pauseButton.selectedProperty().setValue(!pauseButton.selectedProperty().getValue()); 101 | } 102 | 103 | } 104 | }); 105 | gameOverPanel.setVisible(false); 106 | pauseButton.selectedProperty().bindBidirectional(isPause); 107 | pauseButton.selectedProperty().addListener(new ChangeListener() { 108 | @Override 109 | public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { 110 | if (newValue) { 111 | timeLine.pause(); 112 | pauseButton.setText("Resume"); 113 | } else { 114 | timeLine.play(); 115 | pauseButton.setText("Pause"); 116 | } 117 | } 118 | }); 119 | final Reflection reflection = new Reflection(); 120 | reflection.setFraction(0.8); 121 | reflection.setTopOpacity(0.9); 122 | reflection.setTopOffset(-12); 123 | scoreValue.setEffect(reflection); 124 | } 125 | 126 | public void initGameView(int[][] boardMatrix, ViewData brick) { 127 | displayMatrix = new Rectangle[boardMatrix.length][boardMatrix[0].length]; 128 | for (int i = 2; i < boardMatrix.length; i++) { 129 | for (int j = 0; j < boardMatrix[i].length; j++) { 130 | Rectangle rectangle = new Rectangle(BRICK_SIZE, BRICK_SIZE); 131 | rectangle.setFill(Color.TRANSPARENT); 132 | displayMatrix[i][j] = rectangle; 133 | gamePanel.add(rectangle, j, i - 2); 134 | } 135 | } 136 | 137 | rectangles = new Rectangle[brick.getBrickData().length][brick.getBrickData()[0].length]; 138 | for (int i = 0; i < brick.getBrickData().length; i++) { 139 | for (int j = 0; j < brick.getBrickData()[i].length; j++) { 140 | Rectangle rectangle = new Rectangle(BRICK_SIZE, BRICK_SIZE); 141 | rectangle.setFill(getFillColor(brick.getBrickData()[i][j])); 142 | rectangles[i][j] = rectangle; 143 | brickPanel.add(rectangle, j, i); 144 | } 145 | } 146 | brickPanel.setLayoutX(gamePanel.getLayoutX() + brick.getxPosition() * brickPanel.getVgap() + brick.getxPosition() * BRICK_SIZE); 147 | brickPanel.setLayoutY(-42 + gamePanel.getLayoutY() + brick.getyPosition() * brickPanel.getHgap() + brick.getyPosition() * BRICK_SIZE); 148 | 149 | generatePreviewPanel(brick.getNextBrickData()); 150 | 151 | 152 | timeLine = new Timeline(new KeyFrame( 153 | Duration.millis(400), 154 | ae -> moveDown(new MoveEvent(EventType.DOWN, EventSource.THREAD)) 155 | )); 156 | timeLine.setCycleCount(Timeline.INDEFINITE); 157 | timeLine.play(); 158 | } 159 | 160 | private Paint getFillColor(int i) { 161 | Paint returnPaint; 162 | switch (i) { 163 | case 0: 164 | returnPaint = Color.TRANSPARENT; 165 | break; 166 | case 1: 167 | returnPaint = Color.AQUA; 168 | break; 169 | case 2: 170 | returnPaint = Color.BLUEVIOLET; 171 | break; 172 | case 3: 173 | returnPaint = Color.DARKGREEN; 174 | break; 175 | case 4: 176 | returnPaint = Color.YELLOW; 177 | break; 178 | case 5: 179 | returnPaint = Color.RED; 180 | break; 181 | case 6: 182 | returnPaint = Color.BEIGE; 183 | break; 184 | case 7: 185 | returnPaint = Color.BURLYWOOD; 186 | break; 187 | default: 188 | returnPaint = Color.WHITE; 189 | break; 190 | } 191 | return returnPaint; 192 | } 193 | 194 | private void generatePreviewPanel(int[][] nextBrickData) { 195 | nextBrick.getChildren().clear(); 196 | for (int i = 0; i < nextBrickData.length; i++) { 197 | for (int j = 0; j < nextBrickData[i].length; j++) { 198 | Rectangle rectangle = new Rectangle(BRICK_SIZE, BRICK_SIZE); 199 | setRectangleData(nextBrickData[i][j], rectangle); 200 | if (nextBrickData[i][j] != 0) { 201 | nextBrick.add(rectangle, j, i); 202 | } 203 | } 204 | } 205 | } 206 | 207 | private void refreshBrick(ViewData brick) { 208 | if (isPause.getValue() == Boolean.FALSE) { 209 | brickPanel.setLayoutX(gamePanel.getLayoutX() + brick.getxPosition() * brickPanel.getVgap() + brick.getxPosition() * BRICK_SIZE); 210 | brickPanel.setLayoutY(-42 + gamePanel.getLayoutY() + brick.getyPosition() * brickPanel.getHgap() + brick.getyPosition() * BRICK_SIZE); 211 | for (int i = 0; i < brick.getBrickData().length; i++) { 212 | for (int j = 0; j < brick.getBrickData()[i].length; j++) { 213 | setRectangleData(brick.getBrickData()[i][j], rectangles[i][j]); 214 | } 215 | } 216 | generatePreviewPanel(brick.getNextBrickData()); 217 | } 218 | } 219 | 220 | public void refreshGameBackground(int[][] board) { 221 | for (int i = 2; i < board.length; i++) { 222 | for (int j = 0; j < board[i].length; j++) { 223 | setRectangleData(board[i][j], displayMatrix[i][j]); 224 | } 225 | } 226 | } 227 | 228 | private void setRectangleData(int color, Rectangle rectangle) { 229 | rectangle.setFill(getFillColor(color)); 230 | rectangle.setArcHeight(9); 231 | rectangle.setArcWidth(9); 232 | } 233 | 234 | private void moveDown(MoveEvent event) { 235 | if (isPause.getValue() == Boolean.FALSE) { 236 | DownData downData = eventListener.onDownEvent(event); 237 | if (downData.getClearRow() != null && downData.getClearRow().getLinesRemoved() > 0) { 238 | NotificationPanel notificationPanel = new NotificationPanel("+" + downData.getClearRow().getScoreBonus()); 239 | groupNotification.getChildren().add(notificationPanel); 240 | notificationPanel.showScore(groupNotification.getChildren()); 241 | } 242 | refreshBrick(downData.getViewData()); 243 | } 244 | gamePanel.requestFocus(); 245 | } 246 | 247 | public void setEventListener(InputEventListener eventListener) { 248 | this.eventListener = eventListener; 249 | } 250 | 251 | public void bindScore(IntegerProperty integerProperty) { 252 | scoreValue.textProperty().bind(integerProperty.asString()); 253 | } 254 | 255 | public void gameOver() { 256 | timeLine.stop(); 257 | gameOverPanel.setVisible(true); 258 | isGameOver.setValue(Boolean.TRUE); 259 | 260 | } 261 | 262 | public void newGame(ActionEvent actionEvent) { 263 | timeLine.stop(); 264 | gameOverPanel.setVisible(false); 265 | eventListener.createNewGame(); 266 | gamePanel.requestFocus(); 267 | timeLine.play(); 268 | isPause.setValue(Boolean.FALSE); 269 | isGameOver.setValue(Boolean.FALSE); 270 | } 271 | 272 | public void pauseGame(ActionEvent actionEvent) { 273 | gamePanel.requestFocus(); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/gui/Main.java: -------------------------------------------------------------------------------- 1 | package com.quirko.gui; 2 | 3 | import com.quirko.app.GameController; 4 | import javafx.application.Application; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.scene.Parent; 7 | import javafx.scene.Scene; 8 | import javafx.stage.Stage; 9 | 10 | import java.net.URL; 11 | import java.util.ResourceBundle; 12 | 13 | public class Main extends Application { 14 | 15 | @Override 16 | public void start(Stage primaryStage) throws Exception { 17 | 18 | URL location = getClass().getClassLoader().getResource("gameLayout.fxml"); 19 | ResourceBundle resources = null; 20 | FXMLLoader fxmlLoader = new FXMLLoader(location, resources); 21 | Parent root = fxmlLoader.load(); 22 | GuiController c = fxmlLoader.getController(); 23 | 24 | primaryStage.setTitle("TetrisJFX"); 25 | Scene scene = new Scene(root, 400, 510); 26 | primaryStage.setScene(scene); 27 | primaryStage.show(); 28 | new GameController(c); 29 | } 30 | 31 | 32 | public static void main(String[] args) { 33 | launch(args); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/gui/NotificationPanel.java: -------------------------------------------------------------------------------- 1 | package com.quirko.gui; 2 | 3 | import javafx.animation.FadeTransition; 4 | import javafx.animation.ParallelTransition; 5 | import javafx.animation.TranslateTransition; 6 | import javafx.collections.ObservableList; 7 | import javafx.event.ActionEvent; 8 | import javafx.event.EventHandler; 9 | import javafx.scene.Node; 10 | import javafx.scene.control.Label; 11 | import javafx.scene.effect.Effect; 12 | import javafx.scene.effect.Glow; 13 | import javafx.scene.layout.BorderPane; 14 | import javafx.scene.paint.Color; 15 | import javafx.util.Duration; 16 | 17 | public class NotificationPanel extends BorderPane { 18 | 19 | public NotificationPanel(String text) { 20 | setMinHeight(200); 21 | setMinWidth(220); 22 | final Label score = new Label(text); 23 | score.getStyleClass().add("bonusStyle"); 24 | final Effect glow = new Glow(0.6); 25 | score.setEffect(glow); 26 | score.setTextFill(Color.WHITE); 27 | setCenter(score); 28 | 29 | } 30 | 31 | public void showScore(ObservableList list) { 32 | FadeTransition ft = new FadeTransition(Duration.millis(2000), this); 33 | TranslateTransition tt = new TranslateTransition(Duration.millis(2500), this); 34 | tt.setToY(this.getLayoutY() - 40); 35 | ft.setFromValue(1); 36 | ft.setToValue(0); 37 | ParallelTransition transition = new ParallelTransition(tt, ft); 38 | transition.setOnFinished(new EventHandler() { 39 | @Override 40 | public void handle(ActionEvent event) { 41 | list.remove(NotificationPanel.this); 42 | } 43 | }); 44 | transition.play(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/Board.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | public interface Board { 4 | 5 | boolean moveBrickDown(); 6 | 7 | boolean moveBrickLeft(); 8 | 9 | boolean moveBrickRight(); 10 | 11 | boolean rotateLeftBrick(); 12 | 13 | boolean createNewBrick(); 14 | 15 | int[][] getBoardMatrix(); 16 | 17 | ViewData getViewData(); 18 | 19 | void mergeBrickToBackground(); 20 | 21 | ClearRow clearRows(); 22 | 23 | Score getScore(); 24 | 25 | void newGame(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/ClearRow.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | public final class ClearRow { 4 | 5 | private final int linesRemoved; 6 | private final int[][] newMatrix; 7 | private final int scoreBonus; 8 | 9 | public ClearRow(int linesRemoved, int[][] newMatrix, int scoreBonus) { 10 | this.linesRemoved = linesRemoved; 11 | this.newMatrix = newMatrix; 12 | this.scoreBonus = scoreBonus; 13 | } 14 | 15 | public int getLinesRemoved() { 16 | return linesRemoved; 17 | } 18 | 19 | public int[][] getNewMatrix() { 20 | return MatrixOperations.copy(newMatrix); 21 | } 22 | 23 | public int getScoreBonus() { 24 | return scoreBonus; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/DownData.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | public final class DownData { 4 | private final ClearRow clearRow; 5 | private final ViewData viewData; 6 | 7 | public DownData(ClearRow clearRow, ViewData viewData) { 8 | this.clearRow = clearRow; 9 | this.viewData = viewData; 10 | } 11 | 12 | public ClearRow getClearRow() { 13 | return clearRow; 14 | } 15 | 16 | public ViewData getViewData() { 17 | return viewData; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/MatrixOperations.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.ArrayList; 5 | import java.util.Deque; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class MatrixOperations { 10 | 11 | 12 | //We don't want to instantiate this utility class 13 | private MatrixOperations(){ 14 | 15 | } 16 | 17 | public static boolean intersect(final int[][] matrix, final int[][] brick, int x, int y) { 18 | for (int i = 0; i < brick.length; i++) { 19 | for (int j = 0; j < brick[i].length; j++) { 20 | int targetX = x + i; 21 | int targetY = y + j; 22 | if (brick[j][i] != 0 && (checkOutOfBound(matrix, targetX, targetY) || matrix[targetY][targetX] != 0)) { 23 | return true; 24 | } 25 | } 26 | } 27 | return false; 28 | } 29 | 30 | private static boolean checkOutOfBound(int[][] matrix, int targetX, int targetY) { 31 | boolean returnValue = true; 32 | if (targetX >= 0 && targetY < matrix.length && targetX < matrix[targetY].length) { 33 | returnValue = false; 34 | } 35 | return returnValue; 36 | } 37 | 38 | public static int[][] copy(int[][] original) { 39 | int[][] myInt = new int[original.length][]; 40 | for (int i = 0; i < original.length; i++) { 41 | int[] aMatrix = original[i]; 42 | int aLength = aMatrix.length; 43 | myInt[i] = new int[aLength]; 44 | System.arraycopy(aMatrix, 0, myInt[i], 0, aLength); 45 | } 46 | return myInt; 47 | } 48 | 49 | public static int[][] merge(int[][] filledFields, int[][] brick, int x, int y) { 50 | int[][] copy = copy(filledFields); 51 | for (int i = 0; i < brick.length; i++) { 52 | for (int j = 0; j < brick[i].length; j++) { 53 | int targetX = x + i; 54 | int targetY = y + j; 55 | if (brick[j][i] != 0) { 56 | copy[targetY][targetX] = brick[j][i]; 57 | } 58 | } 59 | } 60 | return copy; 61 | } 62 | 63 | public static ClearRow checkRemoving(final int[][] matrix) { 64 | int[][] tmp = new int[matrix.length][matrix[0].length]; 65 | Deque newRows = new ArrayDeque<>(); 66 | List clearedRows = new ArrayList<>(); 67 | 68 | for (int i = 0; i < matrix.length; i++) { 69 | int[] tmpRow = new int[matrix[i].length]; 70 | boolean rowToClear = true; 71 | for (int j = 0; j < matrix[0].length; j++) { 72 | if (matrix[i][j] == 0) { 73 | rowToClear = false; 74 | } 75 | tmpRow[j] = matrix[i][j]; 76 | } 77 | if (rowToClear) { 78 | clearedRows.add(i); 79 | } else { 80 | newRows.add(tmpRow); 81 | } 82 | } 83 | for (int i = matrix.length - 1; i >= 0; i--) { 84 | int[] row = newRows.pollLast(); 85 | if (row != null) { 86 | tmp[i] = row; 87 | } else { 88 | break; 89 | } 90 | } 91 | int scoreBonus = 50 * clearedRows.size() * clearedRows.size(); 92 | return new ClearRow(clearedRows.size(), tmp, scoreBonus); 93 | } 94 | 95 | public static List deepCopyList(List list){ 96 | return list.stream().map(MatrixOperations::copy).collect(Collectors.toList()); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/Score.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | import javafx.beans.property.IntegerProperty; 4 | import javafx.beans.property.SimpleIntegerProperty; 5 | 6 | public final class Score { 7 | 8 | private final IntegerProperty score = new SimpleIntegerProperty(0); 9 | 10 | public IntegerProperty scoreProperty() { 11 | return score; 12 | } 13 | 14 | public void add(int i){ 15 | score.setValue(score.getValue() + i); 16 | } 17 | 18 | public void reset() { 19 | score.setValue(0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/SimpleBoard.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | import com.quirko.logic.bricks.Brick; 4 | import com.quirko.logic.bricks.BrickGenerator; 5 | import com.quirko.logic.bricks.RandomBrickGenerator; 6 | import com.quirko.logic.rotator.BrickRotator; 7 | import com.quirko.logic.rotator.NextShapeInfo; 8 | 9 | import java.awt.*; 10 | 11 | public class SimpleBoard implements Board { 12 | 13 | private final int width; 14 | private final int height; 15 | private final BrickGenerator brickGenerator; 16 | private final BrickRotator brickRotator; 17 | private int[][] currentGameMatrix; 18 | private Point currentOffset; 19 | private final Score score; 20 | 21 | public SimpleBoard(int width, int height) { 22 | this.width = width; 23 | this.height = height; 24 | currentGameMatrix = new int[width][height]; 25 | brickGenerator = new RandomBrickGenerator(); 26 | brickRotator = new BrickRotator(); 27 | score = new Score(); 28 | } 29 | 30 | @Override 31 | public boolean moveBrickDown() { 32 | int[][] currentMatrix = MatrixOperations.copy(currentGameMatrix); 33 | Point p = new Point(currentOffset); 34 | p.translate(0, 1); 35 | boolean conflict = MatrixOperations.intersect(currentMatrix, brickRotator.getCurrentShape(), (int) p.getX(), (int) p.getY()); 36 | if (conflict) { 37 | return false; 38 | } else { 39 | currentOffset = p; 40 | return true; 41 | } 42 | } 43 | 44 | 45 | @Override 46 | public boolean moveBrickLeft() { 47 | int[][] currentMatrix = MatrixOperations.copy(currentGameMatrix); 48 | Point p = new Point(currentOffset); 49 | p.translate(-1, 0); 50 | boolean conflict = MatrixOperations.intersect(currentMatrix, brickRotator.getCurrentShape(), (int) p.getX(), (int) p.getY()); 51 | if (conflict) { 52 | return false; 53 | } else { 54 | currentOffset = p; 55 | return true; 56 | } 57 | } 58 | 59 | @Override 60 | public boolean moveBrickRight() { 61 | int[][] currentMatrix = MatrixOperations.copy(currentGameMatrix); 62 | Point p = new Point(currentOffset); 63 | p.translate(1, 0); 64 | boolean conflict = MatrixOperations.intersect(currentMatrix, brickRotator.getCurrentShape(), (int) p.getX(), (int) p.getY()); 65 | if (conflict) { 66 | return false; 67 | } else { 68 | currentOffset = p; 69 | return true; 70 | } 71 | } 72 | 73 | @Override 74 | public boolean rotateLeftBrick() { 75 | int[][] currentMatrix = MatrixOperations.copy(currentGameMatrix); 76 | NextShapeInfo nextShape = brickRotator.getNextShape(); 77 | boolean conflict = MatrixOperations.intersect(currentMatrix, nextShape.getShape(), (int) currentOffset.getX(), (int) currentOffset.getY()); 78 | if (conflict) { 79 | return false; 80 | } else { 81 | brickRotator.setCurrentShape(nextShape.getPosition()); 82 | return true; 83 | } 84 | } 85 | 86 | @Override 87 | public boolean createNewBrick() { 88 | Brick currentBrick = brickGenerator.getBrick(); 89 | brickRotator.setBrick(currentBrick); 90 | currentOffset = new Point(4, 0); 91 | return MatrixOperations.intersect(currentGameMatrix, brickRotator.getCurrentShape(), (int) currentOffset.getX(), (int) currentOffset.getY()); 92 | } 93 | 94 | @Override 95 | public int[][] getBoardMatrix() { 96 | return currentGameMatrix; 97 | } 98 | 99 | @Override 100 | public ViewData getViewData() { 101 | return new ViewData(brickRotator.getCurrentShape(), (int) currentOffset.getX(), (int) currentOffset.getY(), brickGenerator.getNextBrick().getShapeMatrix().get(0)); 102 | } 103 | 104 | @Override 105 | public void mergeBrickToBackground() { 106 | currentGameMatrix = MatrixOperations.merge(currentGameMatrix, brickRotator.getCurrentShape(), (int) currentOffset.getX(), (int) currentOffset.getY()); 107 | } 108 | 109 | @Override 110 | public ClearRow clearRows() { 111 | ClearRow clearRow = MatrixOperations.checkRemoving(currentGameMatrix); 112 | currentGameMatrix = clearRow.getNewMatrix(); 113 | return clearRow; 114 | 115 | } 116 | 117 | @Override 118 | public Score getScore() { 119 | return score; 120 | } 121 | 122 | 123 | @Override 124 | public void newGame() { 125 | currentGameMatrix = new int[width][height]; 126 | score.reset(); 127 | createNewBrick(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/ViewData.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic; 2 | 3 | public final class ViewData { 4 | 5 | private final int[][] brickData; 6 | private final int xPosition; 7 | private final int yPosition; 8 | private final int[][] nextBrickData; 9 | 10 | public ViewData(int[][] brickData, int xPosition, int yPosition, int[][] nextBrickData) { 11 | this.brickData = brickData; 12 | this.xPosition = xPosition; 13 | this.yPosition = yPosition; 14 | this.nextBrickData = nextBrickData; 15 | } 16 | 17 | public int[][] getBrickData() { 18 | return MatrixOperations.copy(brickData); 19 | } 20 | 21 | public int getxPosition() { 22 | return xPosition; 23 | } 24 | 25 | public int getyPosition() { 26 | return yPosition; 27 | } 28 | 29 | public int[][] getNextBrickData() { 30 | return MatrixOperations.copy(nextBrickData); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/Brick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import java.util.List; 4 | 5 | public interface Brick { 6 | 7 | List getShapeMatrix(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/BrickGenerator.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | public interface BrickGenerator { 4 | 5 | Brick getBrick(); 6 | 7 | Brick getNextBrick(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/IBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class IBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public IBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {1, 1, 1, 1}, 16 | {0, 0, 0, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | brickMatrix.add(new int[][]{ 20 | {0, 1, 0, 0}, 21 | {0, 1, 0, 0}, 22 | {0, 1, 0, 0}, 23 | {0, 1, 0, 0} 24 | }); 25 | } 26 | 27 | @Override 28 | public List getShapeMatrix() { 29 | return MatrixOperations.deepCopyList(brickMatrix); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/JBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class JBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public JBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {2, 2, 2, 0}, 16 | {0, 0, 2, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | brickMatrix.add(new int[][]{ 20 | {0, 0, 0, 0}, 21 | {0, 2, 2, 0}, 22 | {0, 2, 0, 0}, 23 | {0, 2, 0, 0} 24 | }); 25 | brickMatrix.add(new int[][]{ 26 | {0, 0, 0, 0}, 27 | {0, 2, 0, 0}, 28 | {0, 2, 2, 2}, 29 | {0, 0, 0, 0} 30 | }); 31 | brickMatrix.add(new int[][]{ 32 | {0, 0, 2, 0}, 33 | {0, 0, 2, 0}, 34 | {0, 2, 2, 0}, 35 | {0, 0, 0, 0} 36 | }); 37 | } 38 | 39 | @Override 40 | public List getShapeMatrix() { 41 | return MatrixOperations.deepCopyList(brickMatrix); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/LBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class LBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public LBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {0, 3, 3, 3}, 16 | {0, 3, 0, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | brickMatrix.add(new int[][]{ 20 | {0, 0, 0, 0}, 21 | {0, 3, 3, 0}, 22 | {0, 0, 3, 0}, 23 | {0, 0, 3, 0} 24 | }); 25 | brickMatrix.add(new int[][]{ 26 | {0, 0, 0, 0}, 27 | {0, 0, 3, 0}, 28 | {3, 3, 3, 0}, 29 | {0, 0, 0, 0} 30 | }); 31 | brickMatrix.add(new int[][]{ 32 | {0, 3, 0, 0}, 33 | {0, 3, 0, 0}, 34 | {0, 3, 3, 0}, 35 | {0, 0, 0, 0} 36 | }); 37 | } 38 | 39 | @Override 40 | public List getShapeMatrix() { 41 | return MatrixOperations.deepCopyList(brickMatrix); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/OBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class OBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public OBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {0, 4, 4, 0}, 16 | {0, 4, 4, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | } 20 | 21 | @Override 22 | public List getShapeMatrix() { 23 | return MatrixOperations.deepCopyList(brickMatrix); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/RandomBrickGenerator.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.ArrayList; 5 | import java.util.Deque; 6 | import java.util.List; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | 9 | public class RandomBrickGenerator implements BrickGenerator { 10 | 11 | private final List brickList; 12 | 13 | private final Deque nextBricks = new ArrayDeque<>(); 14 | 15 | public RandomBrickGenerator() { 16 | brickList = new ArrayList<>(); 17 | brickList.add(new IBrick()); 18 | brickList.add(new JBrick()); 19 | brickList.add(new LBrick()); 20 | brickList.add(new OBrick()); 21 | brickList.add(new SBrick()); 22 | brickList.add(new TBrick()); 23 | brickList.add(new ZBrick()); 24 | nextBricks.add(brickList.get(ThreadLocalRandom.current().nextInt(brickList.size()))); 25 | nextBricks.add(brickList.get(ThreadLocalRandom.current().nextInt(brickList.size()))); 26 | } 27 | 28 | @Override 29 | public Brick getBrick() { 30 | if (nextBricks.size() <= 1) { 31 | nextBricks.add(brickList.get(ThreadLocalRandom.current().nextInt(brickList.size()))); 32 | } 33 | return nextBricks.poll(); 34 | } 35 | 36 | @Override 37 | public Brick getNextBrick() { 38 | return nextBricks.peek(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/SBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class SBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public SBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {0, 5, 5, 0}, 16 | {5, 5, 0, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | brickMatrix.add(new int[][]{ 20 | {5, 0, 0, 0}, 21 | {5, 5, 0, 0}, 22 | {0, 5, 0, 0}, 23 | {0, 0, 0, 0} 24 | }); 25 | } 26 | 27 | @Override 28 | public List getShapeMatrix() { 29 | return MatrixOperations.deepCopyList(brickMatrix); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/TBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class TBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public TBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {6, 6, 6, 0}, 16 | {0, 6, 0, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | brickMatrix.add(new int[][]{ 20 | {0, 6, 0, 0}, 21 | {0, 6, 6, 0}, 22 | {0, 6, 0, 0}, 23 | {0, 0, 0, 0} 24 | }); 25 | brickMatrix.add(new int[][]{ 26 | {0, 6, 0, 0}, 27 | {6, 6, 6, 0}, 28 | {0, 0, 0, 0}, 29 | {0, 0, 0, 0} 30 | }); 31 | brickMatrix.add(new int[][]{ 32 | {0, 6, 0, 0}, 33 | {6, 6, 0, 0}, 34 | {0, 6, 0, 0}, 35 | {0, 0, 0, 0} 36 | }); 37 | } 38 | 39 | @Override 40 | public List getShapeMatrix() { 41 | return MatrixOperations.deepCopyList(brickMatrix); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/bricks/ZBrick.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.bricks; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final class ZBrick implements Brick { 9 | 10 | private final List brickMatrix = new ArrayList<>(); 11 | 12 | public ZBrick() { 13 | brickMatrix.add(new int[][]{ 14 | {0, 0, 0, 0}, 15 | {7, 7, 0, 0}, 16 | {0, 7, 7, 0}, 17 | {0, 0, 0, 0} 18 | }); 19 | brickMatrix.add(new int[][]{ 20 | {0, 7, 0, 0}, 21 | {7, 7, 0, 0}, 22 | {7, 0, 0, 0}, 23 | {0, 0, 0, 0} 24 | }); 25 | } 26 | 27 | @Override 28 | public List getShapeMatrix() { 29 | return MatrixOperations.deepCopyList(brickMatrix); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/events/EventSource.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.events; 2 | 3 | public enum EventSource { 4 | USER, THREAD 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/events/EventType.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.events; 2 | 3 | public enum EventType { 4 | DOWN, LEFT, RIGHT, ROTATE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/events/InputEventListener.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.events; 2 | 3 | import com.quirko.logic.ViewData; 4 | import com.quirko.logic.DownData; 5 | 6 | public interface InputEventListener { 7 | 8 | DownData onDownEvent(MoveEvent event); 9 | 10 | ViewData onLeftEvent(MoveEvent event); 11 | 12 | ViewData onRightEvent(MoveEvent event); 13 | 14 | ViewData onRotateEvent(MoveEvent event); 15 | 16 | void createNewGame(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/events/MoveEvent.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.events; 2 | 3 | public final class MoveEvent { 4 | private final EventType eventType; 5 | private final EventSource eventSource; 6 | 7 | public MoveEvent(EventType eventType, EventSource eventSource) { 8 | this.eventType = eventType; 9 | this.eventSource = eventSource; 10 | } 11 | 12 | public EventType getEventType() { 13 | return eventType; 14 | } 15 | 16 | public EventSource getEventSource() { 17 | return eventSource; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/rotator/BrickRotator.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.rotator; 2 | 3 | import com.quirko.logic.bricks.Brick; 4 | 5 | public class BrickRotator { 6 | 7 | private Brick brick; 8 | private int currentShape = 0; 9 | 10 | public NextShapeInfo getNextShape() { 11 | int nextShape = currentShape; 12 | nextShape = (++nextShape) % brick.getShapeMatrix().size(); 13 | return new NextShapeInfo(brick.getShapeMatrix().get(nextShape), nextShape); 14 | } 15 | 16 | public int[][] getCurrentShape() { 17 | return brick.getShapeMatrix().get(currentShape); 18 | } 19 | 20 | public void setCurrentShape(int currentShape) { 21 | this.currentShape = currentShape; 22 | } 23 | 24 | public void setBrick(Brick brick) { 25 | this.brick = brick; 26 | currentShape = 0; 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/quirko/logic/rotator/NextShapeInfo.java: -------------------------------------------------------------------------------- 1 | package com.quirko.logic.rotator; 2 | 3 | import com.quirko.logic.MatrixOperations; 4 | 5 | public final class NextShapeInfo { 6 | 7 | private final int[][] shape; 8 | private final int position; 9 | 10 | public NextShapeInfo(final int[][] shape, final int position) { 11 | this.shape = shape; 12 | this.position = position; 13 | } 14 | 15 | public int[][] getShape() { 16 | return MatrixOperations.copy(shape); 17 | } 18 | 19 | public int getPosition() { 20 | return position; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/background_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javafx-dev/JavaFX-Tetris-Clone/9078cea250826c3d01ffe37033dc26e136229b5a/src/main/resources/background_image.png -------------------------------------------------------------------------------- /src/main/resources/digital.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javafx-dev/JavaFX-Tetris-Clone/9078cea250826c3d01ffe37033dc26e136229b5a/src/main/resources/digital.ttf -------------------------------------------------------------------------------- /src/main/resources/gameLayout.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 |
14 | 15 |
16 |
17 | 18 | 19 | 20 | 23 | 24 | 31 |