From a906bc53810a32992069379fa73d467d40d3417c Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Sat, 28 Jun 2025 21:30:12 +0200 Subject: [PATCH] feat: display score & game over, code cleanup --- .../java/eu/midnightdust/yaytris/Tetris.java | 40 ++++++++- .../eu/midnightdust/yaytris/game/Space.java | 4 + .../midnightdust/yaytris/game/Tetromino.java | 51 ++++++----- .../midnightdust/yaytris/ui/GameCanvas.java | 8 +- .../eu/midnightdust/yaytris/ui/ScoreMenu.java | 34 +++++++ .../eu/midnightdust/yaytris/ui/TetrisUI.java | 89 +++++++++---------- 6 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 src/main/java/eu/midnightdust/yaytris/ui/ScoreMenu.java diff --git a/src/main/java/eu/midnightdust/yaytris/Tetris.java b/src/main/java/eu/midnightdust/yaytris/Tetris.java index 3d58ba7..6139f01 100644 --- a/src/main/java/eu/midnightdust/yaytris/Tetris.java +++ b/src/main/java/eu/midnightdust/yaytris/Tetris.java @@ -1,17 +1,20 @@ package eu.midnightdust.yaytris; import eu.midnightdust.yaytris.game.Space; +import eu.midnightdust.yaytris.ui.ScoreMenu; import eu.midnightdust.yaytris.ui.TetrisUI; import javax.swing.*; import java.util.Random; import java.util.Timer; +import java.util.TimerTask; public class Tetris { public static final Random random = new Random(); - public static Space space; - public static Timer timer; - public static TetrisUI ui; + private static Space space; + private static Timer timer; + private static TetrisUI ui; + private static TimerTask gravityTask; public static void main(String[] args) { try { @@ -23,4 +26,35 @@ public class Tetris { space = new Space(); ui = new TetrisUI(); } + + public static TetrisUI getUi() { + return ui; + } + + public static Space getSpace() { + return space; + } + + public static void resetSpace() { + space = new Space(); + } + + public static void startGame() { + space.spawnTetromino(); + gravityTask = new TimerTask() { + @Override + public void run() { + if (space.getCurrentTetromino() != null) { + space.getCurrentTetromino().fall(1); + ui.getGamePanel().repaint(); + } + } + }; + timer.scheduleAtFixedRate(gravityTask, 1, Settings.difficulty.getTimerPeriod()); + } + public static void stopGame() { + gravityTask.cancel(); + if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) Tetris.ui.getMenuPanel()).gameOver(); + ui.transferFocus(); + } } diff --git a/src/main/java/eu/midnightdust/yaytris/game/Space.java b/src/main/java/eu/midnightdust/yaytris/game/Space.java index f582ecd..3c629a7 100644 --- a/src/main/java/eu/midnightdust/yaytris/game/Space.java +++ b/src/main/java/eu/midnightdust/yaytris/game/Space.java @@ -1,5 +1,8 @@ package eu.midnightdust.yaytris.game; +import eu.midnightdust.yaytris.Tetris; +import eu.midnightdust.yaytris.ui.ScoreMenu; + import java.awt.Color; import java.util.*; @@ -78,6 +81,7 @@ public class Space { } } this.score += combo; + if (Tetris.getUi().getMenuPanel() instanceof ScoreMenu) ((ScoreMenu)Tetris.getUi().getMenuPanel()).updateScore(this.score); //System.out.println(score); } diff --git a/src/main/java/eu/midnightdust/yaytris/game/Tetromino.java b/src/main/java/eu/midnightdust/yaytris/game/Tetromino.java index 6ed2aa4..510e24d 100644 --- a/src/main/java/eu/midnightdust/yaytris/game/Tetromino.java +++ b/src/main/java/eu/midnightdust/yaytris/game/Tetromino.java @@ -20,34 +20,20 @@ public class Tetromino { public void fall(int length) { Vec2i newPos = centerPos.offset(Vec2i.of(0, length)); if (collidesVertically(newPos)) { - if (fallLength < 1) System.out.println("Game over!"); int[] affectedLines = new int[this.collision.length]; int line = centerPos.getY(); for (int i = 0; i < this.collision.length; i++) { affectedLines[i] = line; line++; } - Tetris.space.onLinesChanged(this, affectedLines); - Tetris.space.spawnTetromino(); + Tetris.getSpace().onLinesChanged(this, affectedLines); + if (fallLength >= 1) Tetris.getSpace().spawnTetromino(); + else Tetris.stopGame(); } fallLength += 1; centerPos = newPos; } - private boolean collidesVertically(Vec2i newPos) { - if (Tetris.space == null) return false; - boolean collides = newPos.getY() + this.collision.length > Tetris.space.getMapHeight(); // Bottom check - if (!collides) { - for (int i = 0; i < collision[0].length; i++) { - int maxCollisionY = collision.length - 1; - while (collision[maxCollisionY][i] == 0) maxCollisionY--; - if (newPos.getY()+maxCollisionY >= Tetris.space.getGameMap().length) continue; - collides |= Tetris.space.getGameMap()[newPos.getY() + maxCollisionY][newPos.getX() + i] != null; // Check for other tetrominos - } - } - return collides; - } - public void move(int xOffset) { Vec2i newPos = centerPos.offset(Vec2i.of(xOffset, 0)); if (collidesHorizontally(newPos, xOffset)) { @@ -56,15 +42,34 @@ public class Tetromino { centerPos = newPos; } + private boolean collidesVertically(Vec2i newPos) { + if (Tetris.getSpace() == null) return false; + + boolean collides = newPos.getY() + this.collision.length > Tetris.getSpace().getMapHeight(); // Bottom check + + if (!collides) { // Check for other tetrominos + for (int i = 0; i < collision[0].length; i++) { + int maxCollisionY = collision.length - 1; + while (collision[maxCollisionY][i] == 0) maxCollisionY--; // Figure out the collision box's bounding + + if (newPos.getY()+maxCollisionY >= Tetris.getSpace().getMapHeight() || newPos.getX() + i >= Tetris.getSpace().getMapWidth()) continue; + collides |= Tetris.getSpace().getGameMap()[newPos.getY() + maxCollisionY][newPos.getX() + i] != null; + } + } + return collides; + } + private boolean collidesHorizontally(Vec2i newPos, int xOffset) { - if (Tetris.space == null) return false; - boolean collides = newPos.getX() < 0 || newPos.getX() + collision[0].length > Tetris.space.getGameMap()[0].length; - if (!collides) { + if (Tetris.getSpace() == null) return false; + + boolean collides = newPos.getX() < 0 || newPos.getX() + collision[0].length > Tetris.getSpace().getMapWidth(); // Side check + if (!collides) { // Check for other tetrominos for (int i = 0; i < collision.length; i++) { int maxCollisionX = xOffset > 0 ? collision[i].length - 1 : 0; - while (collision[i][maxCollisionX] == 0) maxCollisionX += xOffset > 0 ? -1 : 1; - if (newPos.getY()+maxCollisionX >= Tetris.space.getGameMap().length) continue; - collides |= Tetris.space.getGameMap()[newPos.getY() + i][newPos.getX() + maxCollisionX] != null; // Check for other tetrominos + while (collision[i][maxCollisionX] == 0) maxCollisionX += xOffset > 0 ? -1 : 1; // Figure out the collision box's bounding + + if (newPos.getY()+maxCollisionX >= Tetris.getSpace().getMapHeight()) continue; + collides |= Tetris.getSpace().getGameMap()[newPos.getY() + i][newPos.getX() + maxCollisionX] != null; } } return collides; diff --git a/src/main/java/eu/midnightdust/yaytris/ui/GameCanvas.java b/src/main/java/eu/midnightdust/yaytris/ui/GameCanvas.java index 89b6d34..81c0da8 100644 --- a/src/main/java/eu/midnightdust/yaytris/ui/GameCanvas.java +++ b/src/main/java/eu/midnightdust/yaytris/ui/GameCanvas.java @@ -25,11 +25,11 @@ public class GameCanvas extends JPanel { super.paintComponent(graphics); if (graphics == null) return; - for (int y = 0; y < Tetris.space.getGameMapWithTetromino().length; y++) { - for (int x = 0; x < Tetris.space.getGameMapWithTetromino()[y].length; x++) { - Color color = Tetris.space.getGameMapWithTetromino()[y][x]; + for (int y = 0; y < Tetris.getSpace().getMapHeight(); y++) { + for (int x = 0; x < Tetris.getSpace().getMapWidth(); x++) { + Color color = Tetris.getSpace().getGameMapWithTetromino()[y][x]; if (color == null) continue; - int blockSize = (int) Math.ceil((float) (this.getWidth() - this.getInsets().left - this.getInsets().right) / Tetris.space.getGameMapWithTetromino()[0].length); + int blockSize = (int) Math.ceil((float) (this.getWidth() - this.getInsets().left - this.getInsets().right) / Tetris.getSpace().getMapWidth()); //graphics.setXORMode(withAlpha(color,0)); graphics.drawImage(texture, x*blockSize +getInsets().left, y*blockSize + getInsets().top, blockSize, blockSize, color, this); graphics.setColor(withAlpha(color, 120)); diff --git a/src/main/java/eu/midnightdust/yaytris/ui/ScoreMenu.java b/src/main/java/eu/midnightdust/yaytris/ui/ScoreMenu.java new file mode 100644 index 0000000..f69b4cc --- /dev/null +++ b/src/main/java/eu/midnightdust/yaytris/ui/ScoreMenu.java @@ -0,0 +1,34 @@ +package eu.midnightdust.yaytris.ui; + +import javax.swing.*; + +public class ScoreMenu extends AbstractMenu { + final TetrisUI ui; + final JLabel gameOverLabel; + final JLabel currentScoreLabel; + + ScoreMenu(int x, int y, int width, int height, TetrisUI ui) { + this.ui = ui; + this.setBounds(x, y, width, height); + this.setLayout(null); + + this.gameOverLabel = new JLabel(); + this.add(gameOverLabel); + + this.currentScoreLabel = new JLabel("Score: 0"); + this.add(currentScoreLabel); + + JButton backButton = new JButton("Zurück"); + backButton.addActionListener(ui::openMainMenu); + this.add(backButton); + } + + public void updateScore(int score) { + this.currentScoreLabel.setText("Score: %s".formatted(score)); + this.repaint(); + } + + public void gameOver() { + this.gameOverLabel.setText("Game over :("); + } +} diff --git a/src/main/java/eu/midnightdust/yaytris/ui/TetrisUI.java b/src/main/java/eu/midnightdust/yaytris/ui/TetrisUI.java index d6073c3..49e36cc 100644 --- a/src/main/java/eu/midnightdust/yaytris/ui/TetrisUI.java +++ b/src/main/java/eu/midnightdust/yaytris/ui/TetrisUI.java @@ -1,6 +1,5 @@ package eu.midnightdust.yaytris.ui; -import eu.midnightdust.yaytris.Settings; import eu.midnightdust.yaytris.Tetris; import eu.midnightdust.yaytris.game.Space; @@ -12,10 +11,8 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.IOException; -import java.util.TimerTask; import static eu.midnightdust.yaytris.Settings.guiScale; -import static eu.midnightdust.yaytris.Tetris.timer; public class TetrisUI extends JFrame implements KeyListener { JLabel titleLabel; @@ -76,25 +73,22 @@ public class TetrisUI extends JFrame implements KeyListener { return gamePanel; } + public JPanel getMenuPanel() { + return menuPanel; + } + public void startGame(ActionEvent actionEvent) { - Tetris.space.spawnTetromino(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (Tetris.space.getCurrentTetromino() != null) { - Tetris.space.getCurrentTetromino().fall(1); - Tetris.ui.getGamePanel().repaint(); - } - } - }, 1, Settings.difficulty.getTimerPeriod()); + Tetris.startGame(); //this.remove(menuPanel); //menuPanel = null; + this.openScoreMenu(actionEvent); this.requestFocus(); this.repaint(); } public void openMainMenu(ActionEvent actionEvent) { if (this.menuPanel != null) this.remove(menuPanel); + Tetris.resetSpace(); rescale(); menuPanel = new MainMenu(scale(170), scale(40), scale(220), scale(226), this); menuPanel.setBackground(Color.DARK_GRAY); @@ -112,6 +106,15 @@ public class TetrisUI extends JFrame implements KeyListener { this.repaint(); } + public void openScoreMenu(ActionEvent actionEvent) { + if (this.menuPanel != null) this.remove(menuPanel); + menuPanel = new ScoreMenu(scale(170), scale(40), scale(220), scale(226), this); + menuPanel.setBackground(Color.DARK_GRAY); + menuPanel.setBorder(new LineBorder(Color.GRAY, scale(2))); + this.add(menuPanel); + this.repaint(); + } + // Source: https://stackoverflow.com/a/19746437 private void setWindowPosition(JFrame window, int screen) { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); @@ -139,44 +142,36 @@ public class TetrisUI extends JFrame implements KeyListener { window.setLocation(windowPosX, windowPosY); } - @Override - public void keyTyped(KeyEvent e) { - //System.out.println("Typed"); - } - @Override public void keyPressed(KeyEvent e) { - //System.out.println("Pressed"); - if (e.getKeyCode() == KeyEvent.VK_E) { - Tetris.space.spawnTetromino(); - //Tetris.space.onLinesChanged(Tetris.space.getCurrentTetromino(), 0, 1, 2, 3); - } - else if (e.getKeyCode() == KeyEvent.VK_W) { - Tetris.space.getCurrentTetromino().rotate(); - //Tetris.space.onLinesChanged(Tetris.space.getCurrentTetromino(), 0, 1, 2, 3); - } - else if (e.getKeyCode() == KeyEvent.VK_D) { - Tetris.space.getCurrentTetromino().move(1); - //Tetris.space.onLinesChanged(Tetris.space.getCurrentTetromino(), 0, 1, 2, 3); - } - else if (e.getKeyCode() == KeyEvent.VK_A) { - Tetris.space.getCurrentTetromino().move(-1); - //Tetris.space.onLinesChanged(Tetris.space.getCurrentTetromino(), 0, 1, 2, 3); - } - else if (e.getKeyCode() == KeyEvent.VK_S) { - Tetris.space.getCurrentTetromino().fall(1); - //Tetris.space.onLinesChanged(Tetris.space.getCurrentTetromino(), 0, 1, 2, 3); - } - else if (e.getKeyCode() == KeyEvent.VK_SPACE) { - Tetris.space.getCurrentTetromino().fall(12); - //Tetris.space.onLinesChanged(Tetris.space.getCurrentTetromino(), 0, 1, 2, 3); + switch (e.getKeyCode()) { + case KeyEvent.VK_E: + Tetris.getSpace().spawnTetromino(); + break; + case KeyEvent.VK_UP: + case KeyEvent.VK_W: + Tetris.getSpace().getCurrentTetromino().rotate(); + break; + case KeyEvent.VK_DOWN: + case KeyEvent.VK_D: + Tetris.getSpace().getCurrentTetromino().move(1); + break; + case KeyEvent.VK_LEFT: + case KeyEvent.VK_A: + Tetris.getSpace().getCurrentTetromino().move(-1); + break; + case KeyEvent.VK_RIGHT: + case KeyEvent.VK_S: + Tetris.getSpace().getCurrentTetromino().fall(1); + break; + case KeyEvent.VK_SPACE: + Tetris.getSpace().getCurrentTetromino().fall(12); + break; } gamePanel.repaint(); } - @Override - public void keyReleased(KeyEvent e) { - //System.out.println("Released"); - - } + // Unused (But required overrides) + @Override public void keyReleased(KeyEvent e) {} + @Override public void keyTyped(KeyEvent e) {} }