From 4df15dc25477d4bee136a4c85dd80e894f4611bd Mon Sep 17 00:00:00 2001 From: Loosen-IT Date: Wed, 27 Nov 2024 22:46:25 +0100 Subject: [PATCH] finish big refactorings --- .../java/com/example/flappybird/Bird.java | 4 +- .../com/example/flappybird/FlappyBird.java | 5 - .../com/example/flappybird/GamePanel.java | 576 +----------------- .../com/example/flappybird/Highscore.java | 1 + .../example/flappybird/states/GameState.java | 169 +++++ .../example/flappybird/states/MenuState.java | 117 ++++ .../example/flappybird/states/PlayState.java | 318 ++++++++++ 7 files changed, 630 insertions(+), 560 deletions(-) create mode 100644 refactoring/flappy-bird/src/main/java/com/example/flappybird/states/GameState.java create mode 100644 refactoring/flappy-bird/src/main/java/com/example/flappybird/states/MenuState.java create mode 100644 refactoring/flappy-bird/src/main/java/com/example/flappybird/states/PlayState.java diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/Bird.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/Bird.java index acc6b6a..bcbce8f 100644 --- a/refactoring/flappy-bird/src/main/java/com/example/flappybird/Bird.java +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/Bird.java @@ -5,6 +5,8 @@ package com.example.flappybird; /** * @author Paul Krishnamurthy */ +import com.example.flappybird.states.GameState; + import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; @@ -127,7 +129,7 @@ public class Bird extends HasPosition { } else { // Play audio and set state to dead - GamePanel.audio.hit(); + GameState.audio.hit(); isAlive = false; } diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/FlappyBird.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/FlappyBird.java index 9f81fd3..264590d 100644 --- a/refactoring/flappy-bird/src/main/java/com/example/flappybird/FlappyBird.java +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/FlappyBird.java @@ -41,19 +41,14 @@ public class FlappyBird extends JFrame implements ActionListener { } public void actionPerformed (ActionEvent e) { - if (game != null && game.ready) { game.repaint(); } - } public static void main(String[] args) { - FlappyBird game = new FlappyBird(); - } - } diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/GamePanel.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/GamePanel.java index b88a775..9334669 100644 --- a/refactoring/flappy-bird/src/main/java/com/example/flappybird/GamePanel.java +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/GamePanel.java @@ -5,6 +5,9 @@ package com.example.flappybird; /** * @author Paul Krishnamurthy */ +import com.example.flappybird.states.GameState; +import com.example.flappybird.states.MenuState; + import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -19,77 +22,30 @@ import java.awt.image.BufferedImage; import java.util.Calendar; public class GamePanel extends JPanel implements KeyListener, MouseListener { - - private Random rand; - private Calendar cal; - - //////////////////// - // Game variables // - //////////////////// - - // Fonts - private Font flappyFontBase, - flappyFontReal, - flappyScoreFont, - flappyMiniFont = null; - // Textures - public static HashMap textures = new Sprites().getGameTextures(); - - // Moving base effect - private static int baseSpeed = 2; - private static int[] baseCoords = { 0, 435 }; - - // Game states - final static int MENU = 0; - final static int GAME = 1; - private int gameState = MENU; - - private int score; // Player score - private int pipeDistTracker; // Distance between pipes + private GameState gameState; public boolean ready = false; // If game has loaded - private boolean inStartGameState = false; // To show instructions scren - private Point clickedPoint = new Point(-1, -1); // Store point when player clicks - private boolean scoreWasGreater; // If score was greater than previous highscore - private boolean darkTheme; // Boolean to show dark or light screen - private String randomBird; // Random bird color - private String medal; // Medal to be awarded after each game - public ArrayList pipes; // Arraylist of Pipe objects - - private Bird gameBird; - private Highscore highscore = new Highscore(); - public static Audio audio = new Audio(); public GamePanel () { + setGameState(new MenuState(this)); - rand = new Random(); + // Make the panel focusable + setFocusable(true); + requestFocusInWindow(); - // Try to load ttf file - try { - InputStream is = new BufferedInputStream(this.getClass().getResourceAsStream("/res/fonts/flappy-font.ttf")); - flappyFontBase = Font.createFont(Font.TRUETYPE_FONT, is); - - // Header and sub-header fonts - flappyScoreFont = flappyFontBase.deriveFont(Font.PLAIN, 50); - flappyFontReal = flappyFontBase.deriveFont(Font.PLAIN, 20); - flappyMiniFont = flappyFontBase.deriveFont(Font.PLAIN, 15); - - } catch (Exception ex) { - - // Exit is font cannot be loaded - ex.printStackTrace(); - System.err.println("Could not load Flappy Font!"); - System.exit(-1); - } - - restart(); // Reset game variables + gameState.restart(); // Reset game variables // Input listeners addKeyListener(this); addMouseListener(this); + } + public void setGameState(GameState state) { + System.out.println("Switching to state: " + state.getClass().getName()); + this.gameState = state; + repaint(); } /** @@ -97,417 +53,14 @@ public class GamePanel extends JPanel implements KeyListener, MouseListener { */ public void addNotify() { super.addNotify(); - requestFocus(); + requestFocusInWindow(); // Ensures the panel gains focus ready = true; } - /** - * Restarts game by resetting game variables - */ - public void restart () { - - // Reset game statistics - score = 0; - pipeDistTracker = 0; - scoreWasGreater = false; - - // Get current hour with Calendar - // If it is past noon, use the dark theme - cal = Calendar.getInstance(); - int currentHour = cal.get(Calendar.HOUR_OF_DAY); - - // Array of bird colors - String[] birds = new String[] { - "yellow", - "blue", - "red" - }; - - // Set random scene assets - darkTheme = currentHour > 12; // If we should use the dark theme - randomBird = birds[rand.nextInt(3)]; // Random bird color - - // Game bird - gameBird = new Bird(randomBird, 172, 250, new BufferedImage[] { - textures.get(randomBird + "Bird1").getImage(), - textures.get(randomBird + "Bird2").getImage(), - textures.get(randomBird + "Bird3").getImage() - }); - - // Remove old pipes - pipes = new ArrayList(); - - } - - /** - * Checks if point is in rectangle - * - * @param r Rectangle - * @return Boolean if point collides with rectangle - */ - private boolean isTouching (Rectangle r) { - return r.contains(clickedPoint); - } - @Override public void paintComponent (Graphics g) { super.paintComponent(g); - - // Set font and color - g.setFont(flappyFontReal); - g.setColor(Color.white); - - // Only move screen if bird is alive - if (gameBird.isAlive()) { - - // Move base - baseCoords[0] = baseCoords[0] - baseSpeed < -435 ? 435 : baseCoords[0] - baseSpeed; - baseCoords[1] = baseCoords[1] - baseSpeed < -435 ? 435 : baseCoords[1] - baseSpeed; - - } - - // Background - g.drawImage(darkTheme ? textures.get("background2").getImage() : - textures.get("background1").getImage(), 0, 0, null); - - // Draw bird - gameBird.renderBird(g); - - switch (gameState) { - - case MENU: - - drawBase(g); - drawMenu(g); - - gameBird.menuFloat(); - - break; - - case GAME: - - if (gameBird.isAlive()) { - - // Start at instructions state - if (inStartGameState) { - startGameScreen(g); - - } else { - // Start game - pipeHandler(g); - gameBird.inGame(); - } - - drawBase(g); // Draw base over pipes - drawScore(g, score, false, 0, 0); // Draw player score - - } else { - - pipeHandler(g); - drawBase(g); - - // Draw game over assets - gameOver(g); - - } - - break; - } - - } - - ///////////////////////// - // All drawing methods // - ///////////////////////// - - /** - * Draws a string centered based on given restrictions - * - * @param s String to be drawn - * @param w Constraining width - * @param h Constraining height - * @param y Fixed y-coordiate - */ - public void drawCentered (String s, int w, int h, int y, Graphics g) { - FontMetrics fm = g.getFontMetrics(); - - // Calculate x-coordinate based on string length and width - int x = (w - fm.stringWidth(s)) / 2; - g.drawString(s, x, y); - } - - /** - * Needs to be called differently based on screen - */ - public void drawBase (Graphics g) { - - // Moving base effect - g.drawImage(textures.get("base").getImage(), baseCoords[0], textures.get("base").getY(), null); - g.drawImage(textures.get("base").getImage(), baseCoords[1], textures.get("base").getY(), null); - - } - - //////////////// - // Menuscreen // - //////////////// - - private void drawMenu (Graphics g) { - - // Title - g.drawImage(textures.get("titleText").getImage(), - textures.get("titleText").getX(), - textures.get("titleText").getY(), null); - - // Buttons - g.drawImage(textures.get("playButton").getImage(), - textures.get("playButton").getX(), - textures.get("playButton").getY(), null); - g.drawImage(textures.get("leaderboard").getImage(), - textures.get("leaderboard").getX(), - textures.get("leaderboard").getY(), null); - g.drawImage(textures.get("rateButton").getImage(), - textures.get("rateButton").getX(), - textures.get("rateButton").getY(), null); - - // Credits :p - drawCentered("Created by Paul Krishnamurthy", FlappyBird.WIDTH, FlappyBird.HEIGHT, 600, g); - g.setFont(flappyMiniFont); // Change font - drawCentered("www.PaulKr.com", FlappyBird.WIDTH, FlappyBird.HEIGHT, 630, g); - - } - - ///////////////// - // Game screen // - ///////////////// - - public void startGameScreen (Graphics g) { - - // Set bird's new position - gameBird.setGameStartPos(); - - // Get ready text - g.drawImage(textures.get("getReadyText").getImage(), - textures.get("getReadyText").getX(), - textures.get("getReadyText").getY(), null); - - // Instructions image - g.drawImage(textures.get("instructions").getImage(), - textures.get("instructions").getX(), - textures.get("instructions").getY(), null); - - } - - /** - * Aligns and draws score using image textures - * - * @param mini Boolean for drawing small or large numbers - * @param x X-coordinate to draw for mini numbers - */ - public void drawScore (Graphics g, int drawNum, boolean mini, int x, int y) { - - // Char array of digits - char[] digits = ("" + drawNum).toCharArray(); - - int digitCount = digits.length; - - // Calculate width for numeric textures - int takeUp = 0; - for (char digit : digits) { - - // Size to add varies based on texture - if (mini) { - takeUp += 18; - } else { - takeUp += digit == '1' ? 25 : 35; - } - } - - // Calculate x-coordinate - int drawScoreX = mini ? (x - takeUp) : (FlappyBird.WIDTH / 2 - takeUp / 2); - - // Draw every digit - for (int i = 0; i < digitCount; i++) { - g.drawImage(textures.get((mini ? "mini-score-" : "score-") + digits[i]).getImage(), drawScoreX, (mini ? y : 60), null); - - // Size to add varies based on texture - if (mini) { - drawScoreX += 18; - } else { - drawScoreX += digits[i] == '1' ? 25 : 35; - } - } - - } - - /** - * Moves and repositions pipes - */ - public void pipeHandler (Graphics g) { - - // Decrease distance between pipes - if (gameBird.isAlive()) { - pipeDistTracker --; - } - - // Initialize pipes as null - Pipe topPipe = null; - Pipe bottomPipe = null; - - // If there is no distance, - // a new pipe is needed - if (pipeDistTracker < 0) { - - // Reset distance - pipeDistTracker = Pipe.PIPE_DISTANCE; - - for (Pipe p : pipes) { - - // If pipe is out of screen - if (p.getX() < 0) { - if (topPipe == null) { - topPipe = p; - topPipe.canAwardPoint = true; - } - else if (bottomPipe == null) { - bottomPipe = p; - topPipe.canAwardPoint = true; - } - } - } - - Pipe currentPipe; // New pipe object for top and bottom pipes - - // Move and handle initial creation of top and bottom pipes - - if (topPipe == null) { - currentPipe = new Pipe("top"); - topPipe = currentPipe; - pipes.add(topPipe); - } else { - topPipe.reset(); - } - - if (bottomPipe == null) { - currentPipe = new Pipe("bottom"); - bottomPipe = currentPipe; - pipes.add(bottomPipe); - - // Avoid doubling points when passing initial pipes - bottomPipe.canAwardPoint = false; - } else { - bottomPipe.reset(); - } - - // Set y-coordinate of bottom pipe based on - // y-coordinate of top pipe - bottomPipe.setY(topPipe.getY() + Pipe.PIPE_SPACING); - - } - - // Move and draw each pipe - - for (Pipe p : pipes) { - - // Move the pipe - if (gameBird.isAlive()) { - p.move(); - } - - // Draw the top and bottom pipes - if (p.getY() <= 0) { - g.drawImage(textures.get("pipe-top").getImage(), p.getX(), p.getY(), null); - } else { - g.drawImage(textures.get("pipe-bottom").getImage(), p.getX(), p.getY(), null); - } - - // Check if bird hits pipes - if (gameBird.isAlive()) { - if (p.collide(gameBird)) { - // Kill bird and play sound - gameBird.kill(); - audio.hit(); - } else { - - // Checks if bird passes a pipe - if (gameBird.getX() >= p.getX() + p.WIDTH / 2) { - - // Increase score and play sound - if (p.canAwardPoint) { - audio.point(); - score ++; - p.canAwardPoint = false; - } - } - } - } - } - } - - public void gameOver (Graphics g) { - - // Game over text - g.drawImage(textures.get("gameOverText").getImage(), - textures.get("gameOverText").getX(), - textures.get("gameOverText").getY(), null); - - // Scorecard - g.drawImage(textures.get("scoreCard").getImage(), - textures.get("scoreCard").getX(), - textures.get("scoreCard").getY(), null); - - // New highscore image - if (scoreWasGreater) { - g.drawImage(textures.get("newHighscore").getImage(), - textures.get("newHighscore").getX(), - textures.get("newHighscore").getY(), null); - } - - // Draw mini fonts for current and best scores - drawScore(g, score, true, 303, 276); - drawScore(g, highscore.bestScore(), true, 303, 330); - - // Handle highscore - if (score > highscore.bestScore()) { - - // New best score - scoreWasGreater = true; - highscore.setNewBest(score); // Set in data file - } - - // Medal - if (score >= 10) { medal = "bronze"; } - if (score >= 20) { medal = "silver"; } - if (score >= 30) { medal = "gold"; } - if (score >= 40) { medal = "platinum"; } - - // Only award a medal if they deserve it - if (score > 9) { - g.drawImage(textures.get(medal).getImage(), - textures.get(medal).getX(), - textures.get(medal).getY(), null); - } - - // Buttons - g.drawImage(textures.get("playButton").getImage(), - textures.get("playButton").getX(), - textures.get("playButton").getY(), null); - g.drawImage(textures.get("leaderboard").getImage(), - textures.get("leaderboard").getX(), - textures.get("leaderboard").getY(), null); - - } - - public void openURL (String url) { - - try { - if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(new URI(url)); - } - } catch (Exception e) { - e.printStackTrace(); - System.out.println("Sorry could not open URL..."); - } - + gameState.renderGameState(g); } ////////////////////// @@ -517,32 +70,9 @@ public class GamePanel extends JPanel implements KeyListener, MouseListener { public void keyTyped (KeyEvent e) {} public void keyReleased (KeyEvent e) {} - public void keyPressed (KeyEvent e) { - - int keyCode = e.getKeyCode(); - - if (gameState == MENU) { - - // Start game on 'enter' key - if (keyCode == KeyEvent.VK_ENTER) { - gameState = GAME; - inStartGameState = true; - } - - } else if (gameState == GAME && gameBird.isAlive()) { - - if (keyCode == KeyEvent.VK_SPACE) { - - // Exit instructions state - if (inStartGameState) { - inStartGameState = false; - } - - // Jump and play audio even if in instructions state - gameBird.jump(); - audio.jump(); - } - } + public void keyPressed(KeyEvent e) { + System.out.println("Key pressed: " + e.getKeyCode()); + gameState.handleKeyboardEvent(e); } /////////////////// @@ -554,71 +84,9 @@ public class GamePanel extends JPanel implements KeyListener, MouseListener { public void mouseReleased (MouseEvent e) {} public void mouseClicked (MouseEvent e) {} - public void mousePressed (MouseEvent e) { - - // Save clicked point - clickedPoint = e.getPoint(); - - if (gameState == MENU) { - - if (isTouching(textures.get("playButton").getRect())) { - gameState = GAME; - inStartGameState = true; - - } else if (isTouching(textures.get("leaderboard").getRect())) { - - // Dummy message - JOptionPane.showMessageDialog(this, - "We can't access the leaderboard right now!", - "Oops!", - JOptionPane.ERROR_MESSAGE); - } - - if (gameBird.isAlive()) { - - if (isTouching(textures.get("rateButton").getRect())) { - openURL("http://paulkr.com"); // Open website - } - - } - - } else if (gameState == GAME) { - - if (gameBird.isAlive()) { - - // Allow jump with clicks - if (inStartGameState) { - inStartGameState = false; - } - - // Jump and play sound - gameBird.jump(); - audio.jump(); - - } else { - - // On game over screen, allow restart and leaderboard buttons - if (isTouching(textures.get("playButton").getRect())) { - inStartGameState = true; - gameState = GAME; - restart(); - gameBird.setGameStartPos(); - - } else if (isTouching(textures.get("leaderboard").getRect())) { - - // Dummy message - JOptionPane.showMessageDialog(this, - "We can't access the leaderboard right now!", - "Oops!", - JOptionPane.ERROR_MESSAGE); - } - - } - - } - + public void mousePressed(MouseEvent e) { + System.out.println("Mouse clicked at: " + e.getPoint()); + gameState.handleMouseEvent(e); } - - } diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/Highscore.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/Highscore.java index 7da3e52..c19b9c3 100644 --- a/refactoring/flappy-bird/src/main/java/com/example/flappybird/Highscore.java +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/Highscore.java @@ -22,6 +22,7 @@ public class Highscore { private static final String FILE_PATH = "/res/data/highscore.dat"; private static final URL dataURL = Highscore.class.getResource(FILE_PATH); + private static final File dataFile; static { diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/GameState.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/GameState.java new file mode 100644 index 0000000..db54bc8 --- /dev/null +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/GameState.java @@ -0,0 +1,169 @@ +package com.example.flappybird.states; /** + * GamePanel.java + * Main game panel + * + * @author Paul Krishnamurthy + */ + +import com.example.flappybird.*; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Random; + +public abstract class GameState { + private final Random rand; + + //////////////////// + // Game variables // + //////////////////// + + // Fonts + public static Font flappyFontBase; + public static Font flappyFontReal; + public static Font flappyScoreFont; + public static Font flappyMiniFont = null; + + public static Audio audio = new Audio(); + + // Textures + public static HashMap textures = new Sprites().getGameTextures(); + + protected static final int[] baseCoords = { 0, 435 }; + + protected int score; // Player score + protected int pipeDistTracker; // Distance between pipes + protected boolean inStartGameState = false; // To show instructions scren + protected Point clickedPoint = new Point(-1, -1); // Store point when player clicks + protected boolean scoreWasGreater; // If score was greater than previous highscore + protected String medal; // Medal to be awarded after each game + public static ArrayList pipes; // Arraylist of Pipe objects + + protected static boolean darkTheme; // Boolean to show dark or light screen + + protected static Bird gameBird; + protected final static Highscore highscore = new Highscore(); + + public GamePanel gamePanel; + + + public GameState(GamePanel panel) { + this.gamePanel = panel; + rand = new Random(); + + // Try to load ttf file + try { + InputStream is = new BufferedInputStream(this.getClass().getResourceAsStream("/res/fonts/flappy-font.ttf")); + flappyFontBase = Font.createFont(Font.TRUETYPE_FONT, is); + + // Header and sub-header fonts + flappyScoreFont = flappyFontBase.deriveFont(Font.PLAIN, 50); + flappyFontReal = flappyFontBase.deriveFont(Font.PLAIN, 20); + flappyMiniFont = flappyFontBase.deriveFont(Font.PLAIN, 15); + + } catch (Exception ex) { + + // Exit is font cannot be loaded + ex.printStackTrace(); + System.err.println("Could not load Flappy Font!"); + System.exit(-1); + } + } + + /** + * Restarts game by resetting game variables + */ + public void restart () { + + // Reset game statistics + score = 0; + pipeDistTracker = 0; + scoreWasGreater = false; + + // Get current hour with Calendar + // If it is past noon, use the dark theme + Calendar cal = Calendar.getInstance(); + int currentHour = cal.get(Calendar.HOUR_OF_DAY); + + // Array of bird colors + String[] birds = new String[] { + "yellow", + "blue", + "red" + }; + + // Set random scene assets + darkTheme = currentHour > 12; // If we should use the dark theme + // Random bird color + String randomBird = birds[rand.nextInt(3)]; // Random bird color + + // Game bird + gameBird = new Bird(randomBird, 172, 250, new BufferedImage[] { + textures.get(randomBird + "Bird1").getImage(), + textures.get(randomBird + "Bird2").getImage(), + textures.get(randomBird + "Bird3").getImage() + }); + + // Remove old pipes + pipes = new ArrayList(); + } + + /** + * Checks if point is in rectangle + * + * @param r Rectangle + * @return Boolean if point collides with rectangle + */ + protected boolean isTouching (Rectangle r) { + return r.contains(clickedPoint); + } + + public void prepareScreen(Graphics g) { + // Set font and color + g.setFont(flappyFontReal); + g.setColor(Color.white); + + // Only move screen if bird is alive + if (gameBird.isAlive()) { + + // Move base + // Moving base effect + int baseSpeed = 2; + baseCoords[0] = baseCoords[0] - baseSpeed < -435 ? 435 : baseCoords[0] - baseSpeed; + baseCoords[1] = baseCoords[1] - baseSpeed < -435 ? 435 : baseCoords[1] - baseSpeed; + + } + + // Background + g.drawImage(darkTheme ? textures.get("background2").getImage() : + textures.get("background1").getImage(), 0, 0, null); + + // Draw bird + gameBird.renderBird(g); + } + + public abstract void renderGameState(Graphics g); + + /** + * Needs to be called differently based on screen + */ + public void drawBase (Graphics g) { + + // Moving base effect + g.drawImage(textures.get("base").getImage(), baseCoords[0], textures.get("base").getY(), null); + g.drawImage(textures.get("base").getImage(), baseCoords[1], textures.get("base").getY(), null); + + } + + public abstract void handleKeyboardEvent(KeyEvent e); + public abstract void handleMouseEvent(MouseEvent e); +} + diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/MenuState.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/MenuState.java new file mode 100644 index 0000000..2341be6 --- /dev/null +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/MenuState.java @@ -0,0 +1,117 @@ +package com.example.flappybird.states; + +import com.example.flappybird.FlappyBird; +import com.example.flappybird.GamePanel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.net.URI; + +public class MenuState extends GameState { + public MenuState(GamePanel panel) { + super(panel); + } + + @Override + public void renderGameState(Graphics g) { + prepareScreen(g); + + drawBase(g); + drawMenu(g); + + gameBird.menuFloat(); + } + + /** + * Draws a string centered based on given restrictions + * + * @param s String to be drawn + * @param w Constraining width + * @param h Constraining height + * @param y Fixed y-coordiate + */ + public void drawCentered (String s, int w, int h, int y, Graphics g) { + FontMetrics fm = g.getFontMetrics(); + + // Calculate x-coordinate based on string length and width + int x = (w - fm.stringWidth(s)) / 2; + g.drawString(s, x, y); + } + + protected void drawMenu (Graphics g) { + // Title + g.drawImage(textures.get("titleText").getImage(), + textures.get("titleText").getX(), + textures.get("titleText").getY(), null); + + // Buttons + g.drawImage(textures.get("playButton").getImage(), + textures.get("playButton").getX(), + textures.get("playButton").getY(), null); + g.drawImage(textures.get("leaderboard").getImage(), + textures.get("leaderboard").getX(), + textures.get("leaderboard").getY(), null); + g.drawImage(textures.get("rateButton").getImage(), + textures.get("rateButton").getX(), + textures.get("rateButton").getY(), null); + + // Credits :p + drawCentered("Created by Paul Krishnamurthy", FlappyBird.WIDTH, FlappyBird.HEIGHT, 600, g); + g.setFont(flappyMiniFont); // Change font + drawCentered("www.PaulKr.com", FlappyBird.WIDTH, FlappyBird.HEIGHT, 630, g); + } + + /** + * Tries to open the review url in default web browser + */ + public void openReviewUrl() { + + try { + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().browse(new URI("http://paulkr.com")); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Sorry could not open URL..."); + } + + } + + @Override + public void handleKeyboardEvent(KeyEvent e) { + int keyCode = e.getKeyCode(); + // Start game on 'enter' key + if (keyCode == KeyEvent.VK_ENTER) { + gamePanel.setGameState(new PlayState(gamePanel)); + } + } + + @Override + public void handleMouseEvent(MouseEvent e) { + // Save clicked point + clickedPoint = e.getPoint(); + + if (isTouching(GameState.textures.get("playButton").getRect())) { + gamePanel.setGameState(new PlayState(gamePanel)); + + } else if (isTouching(GameState.textures.get("leaderboard").getRect())) { + // Dummy message + JOptionPane.showMessageDialog(gamePanel, + "We can't access the leaderboard right now!", + "Oops!", + JOptionPane.ERROR_MESSAGE); + } + + Rectangle rect = GameState.textures.get("playButton").getRect(); + System.out.println("Texture Rect: x=" + rect.getX() + ", y=" + rect.getY() + + ", width=" + rect.getWidth() + ", height=" + rect.getHeight()); + + if (gameBird.isAlive()) { + if (isTouching(textures.get("rateButton").getRect())) { + openReviewUrl(); // Open website + } + } + } +} diff --git a/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/PlayState.java b/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/PlayState.java new file mode 100644 index 0000000..2f84d26 --- /dev/null +++ b/refactoring/flappy-bird/src/main/java/com/example/flappybird/states/PlayState.java @@ -0,0 +1,318 @@ +package com.example.flappybird.states; + +import com.example.flappybird.FlappyBird; +import com.example.flappybird.GamePanel; +import com.example.flappybird.Pipe; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +public class PlayState extends GameState { + + public PlayState(GamePanel panel) { + super(panel); + } + + public void startGameScreen (Graphics g) { + + // Set bird's new position + gameBird.setGameStartPos(); + + // Get ready text + g.drawImage(textures.get("getReadyText").getImage(), + textures.get("getReadyText").getX(), + textures.get("getReadyText").getY(), null); + + // Instructions image + g.drawImage(textures.get("instructions").getImage(), + textures.get("instructions").getX(), + textures.get("instructions").getY(), null); + + } + + /** + * Aligns and draws score using image textures + * + * @param mini Boolean for drawing small or large numbers + * @param x X-coordinate to draw for mini numbers + */ + public void drawScore (Graphics g, int drawNum, boolean mini, int x, int y) { + + // Char array of digits + char[] digits = ("" + drawNum).toCharArray(); + + int digitCount = digits.length; + + // Calculate width for numeric textures + int takeUp = 0; + for (char digit : digits) { + + // Size to add varies based on texture + if (mini) { + takeUp += 18; + } else { + takeUp += digit == '1' ? 25 : 35; + } + } + + // Calculate x-coordinate + int drawScoreX = mini ? (x - takeUp) : (FlappyBird.WIDTH / 2 - takeUp / 2); + + // Draw every digit + for (int i = 0; i < digitCount; i++) { + g.drawImage(textures.get((mini ? "mini-score-" : "score-") + digits[i]).getImage(), drawScoreX, (mini ? y : 60), null); + + // Size to add varies based on texture + if (mini) { + drawScoreX += 18; + } else { + drawScoreX += digits[i] == '1' ? 25 : 35; + } + } + + } + + /** + * Moves and repositions pipes + */ + public void pipeHandler (Graphics g) { + + // Decrease distance between pipes + if (gameBird.isAlive()) { + pipeDistTracker --; + } + + // Initialize pipes as null + Pipe topPipe = null; + Pipe bottomPipe = null; + + // If there is no distance, + // a new pipe is needed + if (pipeDistTracker < 0) { + + // Reset distance + pipeDistTracker = Pipe.PIPE_DISTANCE; + + for (Pipe p : pipes) { + + // If pipe is out of screen + if (p.getX() < 0) { + if (topPipe == null) { + topPipe = p; + topPipe.canAwardPoint = true; + } + else if (bottomPipe == null) { + bottomPipe = p; + topPipe.canAwardPoint = true; + } + } + } + + Pipe currentPipe; // New pipe object for top and bottom pipes + + // Move and handle initial creation of top and bottom pipes + + if (topPipe == null) { + currentPipe = new Pipe("top"); + topPipe = currentPipe; + pipes.add(topPipe); + } else { + topPipe.reset(); + } + + if (bottomPipe == null) { + currentPipe = new Pipe("bottom"); + bottomPipe = currentPipe; + pipes.add(bottomPipe); + + // Avoid doubling points when passing initial pipes + bottomPipe.canAwardPoint = false; + } else { + bottomPipe.reset(); + } + + // Set y-coordinate of bottom pipe based on + // y-coordinate of top pipe + bottomPipe.setY(topPipe.getY() + Pipe.PIPE_SPACING); + + } + + // Move and draw each pipe + + for (Pipe p : pipes) { + + // Move the pipe + if (gameBird.isAlive()) { + p.move(); + } + + // Draw the top and bottom pipes + if (p.getY() <= 0) { + g.drawImage(textures.get("pipe-top").getImage(), p.getX(), p.getY(), null); + } else { + g.drawImage(textures.get("pipe-bottom").getImage(), p.getX(), p.getY(), null); + } + + // Check if bird hits pipes + if (gameBird.isAlive()) { + if (p.collide(gameBird)) { + // Kill bird and play sound + gameBird.kill(); + audio.hit(); + } else { + + // Checks if bird passes a pipe + if (gameBird.getX() >= p.getX() + p.WIDTH / 2) { + + // Increase score and play sound + if (p.canAwardPoint) { + audio.point(); + score ++; + p.canAwardPoint = false; + } + } + } + } + } + } + + public void gameOver (Graphics g) { + + // Game over text + g.drawImage(textures.get("gameOverText").getImage(), + textures.get("gameOverText").getX(), + textures.get("gameOverText").getY(), null); + + // Scorecard + g.drawImage(textures.get("scoreCard").getImage(), + textures.get("scoreCard").getX(), + textures.get("scoreCard").getY(), null); + + // New highscore image + if (scoreWasGreater) { + g.drawImage(textures.get("newHighscore").getImage(), + textures.get("newHighscore").getX(), + textures.get("newHighscore").getY(), null); + } + + // Draw mini fonts for current and best scores + drawScore(g, score, true, 303, 276); + drawScore(g, highscore.bestScore(), true, 303, 330); + + // Handle highscore + if (score > highscore.bestScore()) { + + // New best score + scoreWasGreater = true; + highscore.setNewBest(score); // Set in data file + } + + // Medal + if (score >= 10) { medal = "bronze"; } + if (score >= 20) { medal = "silver"; } + if (score >= 30) { medal = "gold"; } + if (score >= 40) { medal = "platinum"; } + + // Only award a medal if they deserve it + if (score > 9) { + g.drawImage(textures.get(medal).getImage(), + textures.get(medal).getX(), + textures.get(medal).getY(), null); + } + + // Buttons + g.drawImage(textures.get("playButton").getImage(), + textures.get("playButton").getX(), + textures.get("playButton").getY(), null); + g.drawImage(textures.get("leaderboard").getImage(), + textures.get("leaderboard").getX(), + textures.get("leaderboard").getY(), null); + + } + + @Override + public void renderGameState(Graphics g) { + prepareScreen(g); + + if (gameBird.isAlive()) { + + // Start at instructions state + if (inStartGameState) { + startGameScreen(g); + + } else { + // Start game + pipeHandler(g); + gameBird.inGame(); + } + + drawBase(g); // Draw base over pipes + drawScore(g, score, false, 0, 0); // Draw player score + + } else { + + pipeHandler(g); + drawBase(g); + + // Draw game over assets + gameOver(g); + } + } + + @Override + public void handleKeyboardEvent(KeyEvent e) { + int keyCode = e.getKeyCode(); + if (gameBird.isAlive()) { + if (keyCode == KeyEvent.VK_SPACE) { + + // Exit instructions state + if (inStartGameState) { + inStartGameState = false; + } + + // Jump and play audio even if in instructions state + gameBird.jump(); + audio.jump(); + } + } + } + + @Override + public void handleMouseEvent(MouseEvent e) { + // Save clicked point + clickedPoint = e.getPoint(); + + if (gameBird.isAlive()) { + + // Allow jump with clicks + if (inStartGameState) { + inStartGameState = false; + } + + // Jump and play sound + gameBird.jump(); + audio.jump(); + + } else { + + // On game over screen, allow restart and leaderboard buttons + if (isTouching(textures.get("playButton").getRect())) { + inStartGameState = true; + gamePanel.setGameState(new PlayState(gamePanel)); + restart(); + gameBird.setGameStartPos(); + + } else if (isTouching(textures.get("leaderboard").getRect())) { + + // Dummy message + JOptionPane.showMessageDialog(gamePanel, + "We can't access the leaderboard right now!", + "Oops!", + JOptionPane.ERROR_MESSAGE); + } + + } + } +}