diff --git a/build.gradle b/build.gradle index 933f025..a99c25e 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ test { jar { manifest { attributes( - 'Main-Class':'Main' + 'Main-Class':'main.Main' ) } } diff --git a/src/main/java/main/Main.java b/src/main/java/main/Main.java index 17e490c..8c3fb69 100644 --- a/src/main/java/main/Main.java +++ b/src/main/java/main/Main.java @@ -1,6 +1,6 @@ package main; -import animus.ProjectAnimusGame; +import pong.PongGame; import sugaEngine.input.GameKeyListener; import sugaEngine.input.GameMouseListener; import sugaEngine.graphics.Graphics2d; @@ -17,6 +17,11 @@ */ public class Main { + /** + * The frame that this is being run in. + */ + public static JFrame frame; + /** * Runs the testing program for the graphics engine. * @@ -25,14 +30,15 @@ public class Main { public static void main (String[] args) { Graphics2d panel = new Graphics2d(); panel.setBackground(Color.BLACK); - JFrame frame = new JFrame("SugaEngine - PONG"); - frame.setSize(1920, 1080); + frame = new JFrame("SugaEngine - PONG"); + Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setSize(size.width, size.height); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(panel, BorderLayout.CENTER); frame.setExtendedState(JFrame.MAXIMIZED_BOTH); frame.setUndecorated(true); frame.setVisible(true); new GraphicsThread(panel, 60).start(); - new GameLogicThread(new ProjectAnimusGame(panel, new GameKeyListener(frame), new GameMouseListener(frame)), 60).start(); + new GameLogicThread(new PongGame(panel, new GameKeyListener(frame), new GameMouseListener(frame)), 60).start(); } } diff --git a/src/main/java/pong/PongGame.java b/src/main/java/pong/PongGame.java index 7494e63..3115ef1 100644 --- a/src/main/java/pong/PongGame.java +++ b/src/main/java/pong/PongGame.java @@ -1,16 +1,16 @@ package pong; +import pong.scenes.MainGame; +import pong.scenes.MainMenu; +import sugaEngine.AIAgent; import sugaEngine.Game; +import sugaEngine.GameObject; import sugaEngine.input.GameKeyListener; import sugaEngine.input.GameMouseListener; import sugaEngine.graphics.GraphicsPanel; -import sugaEngine.threads.GameLogicThread; -import sugaEngine.threads.GraphicsThread; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; /** * Main game class for the Pong game. @@ -20,10 +20,19 @@ public class PongGame extends Game { /** - * These are keys that are currently being held. That can be useful information in it of itself but this is used to - * ignore future key pressed messages. + * The player score counter. */ - List pressedKeys = new ArrayList<>(); + private AtomicInteger playerScore = new AtomicInteger(0); + + /** + * The AI score counter. + */ + private AtomicInteger aiScore = new AtomicInteger(0); + + /** + * Whether the game pong is currently in dev mode or not. + */ + private static boolean devMode = false; /** * Creates a new game with the given panel used to register GameObjects as draw listeners to. @@ -34,7 +43,27 @@ public class PongGame extends Game { */ public PongGame (GraphicsPanel panel, GameKeyListener listener, GameMouseListener mouseListener) { super(panel, listener, mouseListener); - addDrawingListener(new DividingLine()); + scenes.put("Main Game", new MainGame()); + scenes.put("Main Menu", new MainMenu()); + loadScene("Main Menu"); + } + + /** + * Accessor method for the atomic int used for the player's score. + * + * @return The atomic integer used for player scoring. + */ + public AtomicInteger getPlayerScorer () { + return playerScore; + } + + /** + * Accessor method for the atomic int used for the AI's score. + * + * @return The atomic integer used for AI scoring. + */ + public AtomicInteger getAiScorer () { + return aiScore; } /** @@ -42,30 +71,63 @@ public PongGame (GraphicsPanel panel, GameKeyListener listener, GameMouseListene */ @Override public void loop () { - super.loop(); + physics.checkCollisions(); + for (AIAgent a : agents) a.logic(); + for (GameObject gO : objects.values()) gO.runLogic(); } /** - * Processes inputs given by players. Is run during pause. + * Clears all AIAgents, physics managers, GameObjects, and PanelListeners. */ @Override - public void processInput () { - Stack mice = mouseListener.getEvents(); - while (mice.size() > 0) mice.pop(); - Stack keys = keyListener.getKeysPressed(); - while (keys.size() > 0) { - int key = keys.pop(); - if (pressedKeys.contains(key)) continue; - pressedKeys.add(key); - switch (key) { - case 27 -> thread.setPaused(!thread.getPaused()); // ESC - case 76 -> System.out.println("Average fps: " + GraphicsThread.getFPS()); // L - } - } - keys = keyListener.getKeysDepressed(); - while (keys.size() > 0) { - int key = keys.pop(); - pressedKeys.remove((Integer) key); - } + public void clear () { + super.clear(); + playerScore = new AtomicInteger(0); + aiScore = new AtomicInteger(0); + } + + /** + * Adds to the given player's score. + * + * @param target The player that gets 1 added to their score. + */ + public void addScore (String target) { + if (target.equals("AI")) aiScore.incrementAndGet(); + else if (target.equals("Player")) playerScore.incrementAndGet(); + } + + /** + * Serves the pong ball towards the given player. + * + * @param target The player that is 'serving' the ball. + */ + public void serve (String target) { + double posY = new Random().nextDouble() * (panel.getHeight() / 4.0); + double velY = new Random().nextBoolean() ? 6.0 : -6.0; + if (velY < 0) posY += panel.getHeight() / 2.0; + GameObject ball = objects.get("Ball"); + if (ball == null) return; + ball.getPos().setY(posY); + ball.getPos().setX(panel.getWidth() / 2.0); + ball.getVelocity().setY(velY); + ball.getVelocity().setX(target.equals("AI") ? -6.0 : 6.0); + } + + /** + * Returns whether this PongGame is in dev mode or not. + * + * @return True if Pong is in dev mode. Otherwise, false. + */ + public static boolean getDevMode () { + return devMode; + } + + /** + * Sets the value of dev mode to the given value. + * + * @param dev The new value for whether the game is in dev mode or not. + */ + public static void setDevMode (boolean dev) { + devMode = dev; } } diff --git a/src/main/java/pong/ai/PaddleAgent.java b/src/main/java/pong/ai/PaddleAgent.java new file mode 100644 index 0000000..684e3ec --- /dev/null +++ b/src/main/java/pong/ai/PaddleAgent.java @@ -0,0 +1,38 @@ +package pong.ai; + +import pong.objects.Paddle; +import sugaEngine.AIAgent; +import sugaEngine.GameObject; + +/** + * Wobbly boi. + * + * @author Sugaku + */ +public class PaddleAgent extends AIAgent { + + /** + * The ball in the PongGame. + */ + protected GameObject ball; + + /** + * Creates a new AIAgent with control over the given object. + * + * @param object The GameObject that this AIAgent has control over. + * @param ball The ball that's in the same game as the PaddleAgent. + */ + public PaddleAgent (GameObject object, GameObject ball) { + super(object); + this.ball = ball; + } + + /** + * Runs the logic of the AIAgent. + */ + @Override + public void logic () { + if (object.getPos().getY() > ball.getPos().getY() - 20) object.getAccel().setY(-1 * Paddle.PADDLE_ACCELERATION); + else if (object.getPos().getY() < ball.getPos().getY() + 20) object.getAccel().setY(Paddle.PADDLE_ACCELERATION); + } +} diff --git a/src/main/java/pong/objects/Ball.java b/src/main/java/pong/objects/Ball.java new file mode 100644 index 0000000..0907133 --- /dev/null +++ b/src/main/java/pong/objects/Ball.java @@ -0,0 +1,74 @@ +package pong.objects; + +import pong.PongGame; +import sugaEngine.Game; +import sugaEngine.graphics.GraphicsPanel; +import sugaEngine.physics.Collidable; +import sugaEngine.physics.HitBox; +import sugaEngine.physics.Vector; + +import java.awt.*; + +public class Ball extends PongGameObject { + + /** + * Creates a new Collidable object with the immutable property set to either true or false. + * + * @param pos The starting position of the ball. + * @param vel The starting velocity of the ball. + * @param game The game that this ball belongs to. + */ + public Ball (Vector pos, Vector vel, Game game) { + super(false, 20, 20, game); + this.pos = pos; + this.velocity = vel; + } + + /** + * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. + * + * @param width The width of the pixel map. + * @param height The height of the pixel map. + * @param panel The panel to apply changes to. + */ + @Override + public void applyChanges (int width, int height, GraphicsPanel panel) { + Color c = game.getThread().getPaused() ? Color.DARK_GRAY : Color.WHITE; + panel.setBigPixel((int) pos.getX(), (int) pos.getY(), 20, c); + if (PongGame.getDevMode()) drawHitBox(panel, Color.BLUE.brighter()); + } + + /** + * Runs collision logic. May, but in general should not modify the object passed. + * + * @param obj The object that this collidable collided with. + */ + @Override + public void collision (HitBox obj) { + if (obj instanceof Collidable collided) + if (collided.getName().equals("Paddle")) { + velocity.scale(-1.0, 1.0, 1.0); + velocity.add(new Vector(velocity.getX() > 0 ? 0.2 : -0.2, 0, 0)); + } else if (collided.getName().equals("Wall")) velocity.scale(1.0, -1.0, 1.0); + } + + /** + * Runs touching logic. May modify the object passed. + * + * @param obj The object that this collidable is touching. + */ + @Override + public void touch (HitBox obj) { + + } + + /** + * Returns the name of this object for use during collisions. + * + * @return The name of this object. + */ + @Override + public String getName () { + return "Ball"; + } +} diff --git a/src/main/java/pong/objects/Goal.java b/src/main/java/pong/objects/Goal.java new file mode 100644 index 0000000..98547f2 --- /dev/null +++ b/src/main/java/pong/objects/Goal.java @@ -0,0 +1,87 @@ +package pong.objects; + +import pong.PongGame; +import sugaEngine.GameObject; +import sugaEngine.graphics.GraphicsPanel; +import sugaEngine.physics.HitBox; +import sugaEngine.physics.Vector; + +import java.awt.*; + +/** + * The goal is used to add points to the score of a player when the ball makes contact. + * + * @author Sugaku + */ +public class Goal extends PongGameObject { + + /** + * A pointer to the PongGame instance which is used to add to scores and re-serve the ball. + */ + private final PongGame game; + + /** + * Creates a new Collidable object with the immutable property set to either true or false. + * + * @param pos The position of this goal. + * @param height The height of the HitBox. + * @param game The pong game instance. + */ + public Goal (Vector pos, double height, PongGame game) { + super(true, 200, height, game); + this.pos = pos; + this.game = game; + } + + /** + * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. + * + * @param width The width of the pixel map. + * @param height The height of the pixel map. + * @param panel The panel to apply changes to. + */ + @Override + public void applyChanges (int width, int height, GraphicsPanel panel) { + if (PongGame.getDevMode()) drawHitBox(panel, Color.MAGENTA); + } + + /** + * Runs collision logic. May, but in general should not modify the object passed. + * + * @param obj The object that this collidable collided with. + */ + @Override + public void collision (HitBox obj) { + if (obj instanceof GameObject collided) + if (collided.getName().equals("Ball")) { + String target = collided.getVelocity().getX() < 0 ? "AI" : "Player"; + try { + Thread.sleep(1); + } catch (Exception e) { + e.printStackTrace(); + } + game.addScore(target); + game.serve(target); // For some reason this is getting raced to. + } + } + + /** + * Runs touching logic. May modify the object passed. + * + * @param obj The object that this collidable is touching. + */ + @Override + public void touch (HitBox obj) { + + } + + /** + * Returns the name of this object for use during collisions. + * + * @return The name of this object. + */ + @Override + public String getName () { + return "Goal"; + } +} diff --git a/src/main/java/pong/objects/Paddle.java b/src/main/java/pong/objects/Paddle.java index e5ce267..7d5c6b0 100644 --- a/src/main/java/pong/objects/Paddle.java +++ b/src/main/java/pong/objects/Paddle.java @@ -1,27 +1,60 @@ package pong.objects; +import pong.PongGame; +import sugaEngine.Game; import sugaEngine.GameObject; import sugaEngine.graphics.GraphicsPanel; import sugaEngine.physics.HitBox; import sugaEngine.physics.Vector; +import java.awt.*; + /** * A paddle is controlled by a player or AI and is used to hit the ball back and forth. * * @author Sugaku */ -public class Paddle extends GameObject { +public class Paddle extends PongGameObject { + + /** + * Max velocity of the paddle vertically. + */ + public static final double MAX_PADDLE_SPEED = 8.0; + + /** + * The inertia values of the paddle. + */ + public static final double PADDLE_INERTIA_VALUES = 0.4; + + /** + * The max acceleration value of the paddle. + */ + public static final double PADDLE_ACCELERATION = 0.8; /** * Creates a new Collidable object with the immutable property set to either true or false. * * @param pos The starting position of the Paddle. + * @param game The game that this paddle belongs to. */ - public Paddle (Vector pos) { - super(false, 11, 101); + public Paddle (Vector pos, Game game) { + super(false, 10, 100, game); this.pos = pos; } + /** + * Called every logic frame to run the logic on this GameObject. + */ + @Override + public void runLogic () { + pos.add(velocity); + velocity.add(accel); + if (Math.abs(velocity.getY()) > MAX_PADDLE_SPEED) velocity.setY(velocity.getY() > 0 ? MAX_PADDLE_SPEED : -1 * MAX_PADDLE_SPEED); + if (accel.getY() == 0 && velocity.magnitude() > PADDLE_INERTIA_VALUES) + velocity.setY(velocity.getY() > 0 ? velocity.getY() - PADDLE_INERTIA_VALUES : velocity.getY() + PADDLE_INERTIA_VALUES); + else if (accel.getY() == 0 && velocity.magnitude() <= PADDLE_INERTIA_VALUES) velocity.setY(0); + } + /** * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. * @@ -31,10 +64,10 @@ public Paddle (Vector pos) { */ @Override public void applyChanges (int width, int height, GraphicsPanel panel) { -// Color c = PongGame.getPaused() ? Color.GRAY : Color.WHITE; -// for (int i = (int) Math.max(0, pos.getY() - 40); i <= pos.getY() + 50; i++) -// panel.setBigPixel((int) pos.getX() + 5, i, 10, c); -// if (PongGame.getDevMode()) drawHitBox(panel, Color.RED); + Color c = game.getThread().getPaused() ? Color.DARK_GRAY : Color.WHITE; + for (int i = (int) (pos.getY() - 45); i <= (int) (pos.getY() + 45); i += 10) + panel.setBigPixel((int) pos.getX(), i, 10, c); + if (PongGame.getDevMode()) drawHitBox(panel, Color.RED); } /** @@ -44,7 +77,25 @@ public void applyChanges (int width, int height, GraphicsPanel panel) { */ @Override public void collision (HitBox obj) { - + if (obj instanceof GameObject collided) { + if (collided.getName().equals("Ball")) { + int x = (int) (collided.getVelocity().getX() > 0 ? + (int) (pos.getX() + (this.width / 2)) + (obj.getWidth() / 2) : + (int) (pos.getX() - (this.width / 2)) - (obj.getWidth() / 2)); + collided.getPos().setX(x); + collided.getVelocity().setY(obj.getPos().getY() - pos.getY()); + collided.getVelocity().scale(1.0, 0.2, 1.0); + } else if (collided.getName().equals("Wall")) { + if (pos.getY() - collided.getPos().getY() > 0 && velocity.getY() < 0) { + pos.setY(collided.getPos().getY() + (collided.getHeight() / 2.0) + (height / 2.0)); + velocity.setY(0); + } + else if (pos.getY() - collided.getPos().getY() < 0 && velocity.getY() > 0) { + pos.setY(collided.getPos().getY() - (collided.getHeight() / 2.0) - (height / 2.0)); + velocity.setY(0); + } + } + } } /** @@ -63,7 +114,7 @@ public void touch (HitBox obj) { * @return The name of this object. */ @Override - public String getName() { + public String getName () { return "Paddle"; } } diff --git a/src/main/java/pong/objects/PongGameObject.java b/src/main/java/pong/objects/PongGameObject.java new file mode 100644 index 0000000..ea53f09 --- /dev/null +++ b/src/main/java/pong/objects/PongGameObject.java @@ -0,0 +1,33 @@ +package pong.objects; + +import sugaEngine.Game; +import sugaEngine.GameObject; + +/** + * todo refactor and remove. + * + * A PongGameObject functions exactly the same as a normal game object except that it has a reference to the instance of + * pong being run. + * + * @author Sugaku + */ +public abstract class PongGameObject extends GameObject { + + /** + * The game that this object belongs to. + */ + protected final Game game; + + /** + * Creates a new Collidable object with the immutable property set to either true or false. + * + * @param immutable Whether this object moves during collisions or not. + * @param width The width of the HitBox. + * @param height The height of the HitBox. + * @param game The game that this object is being run in. + */ + public PongGameObject (boolean immutable, double width, double height, Game game) { + super(immutable, width, height); + this.game = game; + } +} diff --git a/src/main/java/pong/objects/Wall.java b/src/main/java/pong/objects/Wall.java new file mode 100644 index 0000000..92b96c1 --- /dev/null +++ b/src/main/java/pong/objects/Wall.java @@ -0,0 +1,71 @@ +package pong.objects; + +import pong.PongGame; +import sugaEngine.Game; +import sugaEngine.graphics.GraphicsPanel; +import sugaEngine.physics.HitBox; +import sugaEngine.physics.Vector; + +import java.awt.*; + +/** + * Walls are used to prevent paddles from exiting the screen as well as preventing the ball from exiting the screen. + * + * @author Sugaku + */ +public class Wall extends PongGameObject { + + /** + * Creates a new Collidable object with the immutable property set to either true or false. + * + * @param width The width of the HitBox. + * @param pos The position of the wall object. + * @param game The game that this wall belongs to. + */ + public Wall (double width, Vector pos, Game game) { + super(true, width, 100, game); + this.pos = pos; + } + + /** + * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. + * + * @param width The width of the pixel map. + * @param height The height of the pixel map. + * @param panel The panel to apply changes to. + */ + @Override + public void applyChanges (int width, int height, GraphicsPanel panel) { + if (PongGame.getDevMode()) drawHitBox(panel, Color.GREEN); + } + + /** + * Runs collision logic. May, but in general should not modify the object passed. + * + * @param obj The object that this collidable collided with. + */ + @Override + public void collision (HitBox obj) { + + } + + /** + * Runs touching logic. May modify the object passed. + * + * @param obj The object that this collidable is touching. + */ + @Override + public void touch (HitBox obj) { + + } + + /** + * Returns the name of this object for use during collisions. + * + * @return The name of this object. + */ + @Override + public String getName () { + return "Wall"; + } +} diff --git a/src/main/java/pong/scenes/MainGame.java b/src/main/java/pong/scenes/MainGame.java new file mode 100644 index 0000000..6125a08 --- /dev/null +++ b/src/main/java/pong/scenes/MainGame.java @@ -0,0 +1,106 @@ +package pong.scenes; + +import pong.PongGame; +import pong.ai.PaddleAgent; +import pong.objects.Ball; +import pong.objects.Goal; +import pong.objects.Paddle; +import pong.objects.Wall; +import pong.ui.DividingLine; +import pong.ui.PauseMenu; +import pong.ui.ScoreCounter; +import sugaEngine.Game; +import sugaEngine.Scene; +import sugaEngine.graphics.Graphics2d; +import sugaEngine.input.KeyValues; +import sugaEngine.physics.Vector; +import sugaEngine.threads.GraphicsThread; + +import java.awt.*; + +/** + * The main game is the game with two paddles, a ball, goals, walls etc. + * + * @author Sugaku + */ +public class MainGame extends Scene { + + /** + * The pause screen instance. + */ + private PauseMenu pauseScreen; + + /** + * Loads this scene into the given game. + * + * @param game The game to load this scene into. + * @return True if loading was successful. Otherwise, false. + */ + @Override + public boolean load (Game game) { + super.load(game); + game.clear(); + game.addDrawingListener(new DividingLine(game)); + Graphics2d panel = (Graphics2d) game.getPanel(); + game.addDrawingListener( + new ScoreCounter(((PongGame) game).getPlayerScorer(), new Vector((panel.getWidth() * 3.0) / 8.0, panel.getHeight() / 32.0, 0))); + game.addDrawingListener( + new ScoreCounter(((PongGame) game).getAiScorer(), new Vector((panel.getWidth() * 5.0) / 8.0, panel.getHeight() / 32.0, 0))); + Paddle aiPaddle = new Paddle(new Vector(panel.getWidth() / 8.0, panel.getHeight() / 2.0, 0), game); + Ball ball = new Ball(new Vector((panel.getWidth() * 3.0) / 4.0, panel.getHeight() / 2.0, 0), new Vector(-6.0, 0, 0), game); + game.addGameObject("Ball", ball); + game.addGameObject("AI Paddle", aiPaddle); + game.addAgent(new PaddleAgent(aiPaddle, ball)); + game.addGameObject("Player Paddle", new Paddle(new Vector((panel.getWidth() * 7.0) / 8.0, panel.getHeight() / 2.0, 0), game)); + game.addGameObject("Wall1", new Wall(panel.getWidth(), new Vector(panel.getWidth() / 2.0, -50, 0), game)); + game.addGameObject("Wall2", new Wall(panel.getWidth(), new Vector(panel.getWidth() / 2.0, panel.getHeight() + 49, 0), game)); + game.addGameObject("Player Goal", + new Goal(new Vector(((panel.getWidth() * 7.0) / 8.0) + 150, panel.getHeight() / 2.0, 0), panel.getHeight(), (PongGame) game)); + game.addGameObject("AI Goal", + new Goal(new Vector((panel.getWidth() / 8.0) - 150, panel.getHeight() / 2.0, 0), panel.getHeight(), (PongGame) game)); + pauseScreen = new PauseMenu(game.getMouseListener(), game); + game.addDrawingListener(pauseScreen); + return true; + } + + /** + * Passes a keyboard input into the scene. + * + * @param key The value of the key pressed. + * @param pressed True if the key was pressed, false if it was released. + */ + @Override + public void keyboardInput (KeyValues key, boolean pressed) { + if (pressed) { + if (game.getThread().getPaused()) { + if (key == KeyValues.ARROW_UP || key == KeyValues.ARROW_DOWN) pauseScreen.move(key); + else if (key == KeyValues.ENTER) pauseScreen.enter(game); + } + switch (key) { + case ESC -> game.getThread().setPaused(true); + case L -> System.out.printf("Average fps: %.1f\n", GraphicsThread.getFPS()); + case I -> PongGame.setDevMode(!PongGame.getDevMode()); + case ARROW_UP -> game.getGameObject("Player Paddle").getAccel().add(new Vector(0, -1 * Paddle.PADDLE_ACCELERATION, 0)); + case ARROW_DOWN -> game.getGameObject("Player Paddle").getAccel().add(new Vector(0, Paddle.PADDLE_ACCELERATION, 0)); + } + } else { // Depressed key + switch (key) { + case ARROW_UP -> game.getGameObject("Player Paddle").getAccel().add(new Vector(0, Paddle.PADDLE_ACCELERATION, 0)); + case ARROW_DOWN -> game.getGameObject("Player Paddle").getAccel().add(new Vector(0, -1 * Paddle.PADDLE_ACCELERATION, 0)); + } + } + } + + /** + * Passes a mouse input into the scene. + * + * @param pos The position of the mouse when it was clicked. + * @param pressed True if the button was pressed, false if it was released. + */ + @Override + public void mouseInput (Point pos, boolean pressed) { + if (pressed) + if (game.getThread().getPaused()) + pauseScreen.enter(game); + } +} diff --git a/src/main/java/pong/scenes/MainMenu.java b/src/main/java/pong/scenes/MainMenu.java new file mode 100644 index 0000000..095dfd4 --- /dev/null +++ b/src/main/java/pong/scenes/MainMenu.java @@ -0,0 +1,57 @@ +package pong.scenes; + +import pong.ui.MainMenuDrawer; +import sugaEngine.Game; +import sugaEngine.Scene; +import sugaEngine.input.KeyValues; + +import java.awt.*; +import java.io.IOException; + +/** + * The main menu for the PongGame. + * + * @author Sugaku + */ +public class MainMenu extends Scene { + + /** + * Loads this scene into the given game. + * + * @param game The game to load this scene into. + * @return True if loading was successful. Otherwise, false. + */ + @Override + public boolean load (Game game) { + super.load(game); + game.clear(); + try { + game.addDrawingListener(new MainMenuDrawer()); + } catch (IOException exception) { + return false; + } + return true; + } + + /** + * Passes a keyboard input into the scene. + * + * @param key The value of the key pressed. + * @param pressed True if the key was pressed, false if it was released. + */ + @Override + public void keyboardInput (KeyValues key, boolean pressed) { + if (key == KeyValues.ESC && !pressed) game.loadScene("Main Game"); + } + + /** + * Passes a mouse input into the scene. + * + * @param pos The position of the mouse when it was clicked. + * @param pressed True if the button was pressed, false if it was released. + */ + @Override + public void mouseInput (Point pos, boolean pressed) { + + } +} diff --git a/src/main/java/pong/DividingLine.java b/src/main/java/pong/ui/DividingLine.java similarity index 51% rename from src/main/java/pong/DividingLine.java rename to src/main/java/pong/ui/DividingLine.java index 829ca87..be053a9 100644 --- a/src/main/java/pong/DividingLine.java +++ b/src/main/java/pong/ui/DividingLine.java @@ -1,5 +1,6 @@ -package pong; +package pong.ui; +import sugaEngine.Game; import sugaEngine.graphics.DrawListener; import sugaEngine.graphics.GraphicsPanel; @@ -12,6 +13,21 @@ */ public class DividingLine implements DrawListener { + /** + * Reference to the run time instance of game this listener is working for. + */ + private final Game game; + + /** + * Creates a new DividingLine Drawer which requires access to the PongGame instance for pause screen color changes. + * + * @param game The game this DrawListener is attached to. + */ + public DividingLine (Game game) { + super(); + this.game = game; + } + /** * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. * @@ -21,7 +37,8 @@ public class DividingLine implements DrawListener { */ @Override public void applyChanges (int width, int height, GraphicsPanel panel) { - for (int y = 0; y < height; y += 20) - panel.setBigPixel(width / 2, y, 5, Color.WHITE); + Color c = game.getThread().getPaused() ? Color.DARK_GRAY : Color.WHITE; + for (int y = 10; y < height; y += 20) + panel.setBigPixel(width / 2, y, 5, c); } } diff --git a/src/main/java/pong/ui/MainMenuDrawer.java b/src/main/java/pong/ui/MainMenuDrawer.java new file mode 100644 index 0000000..0594564 --- /dev/null +++ b/src/main/java/pong/ui/MainMenuDrawer.java @@ -0,0 +1,70 @@ +package pong.ui; + +import sugaEngine.graphics.DrawListener; +import sugaEngine.graphics.GraphicsPanel; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +/** + * The MainMenuDrawer handles the drawing of the main menu elements such as title, start, settings, and quit buttons. + * + * @author Sugaku + */ +public class MainMenuDrawer implements DrawListener { + + /** + * The title image as a BufferedImage, so it can remain in RAM. + */ + private final BufferedImage title; + + /** + * The start image as a BufferedImage, so it can remain in RAM. + */ + private final BufferedImage start; + + /** + * The settings image as a BufferedImage, so it can remain in RAM. + */ + private final BufferedImage settings; + + /** + * The quit image as a BufferedImage, so it can remain in RAM. + */ + private final BufferedImage quit; + + /** + * Creates a new MainMenuDrawer by loading the needed images from jar resources. + */ + public MainMenuDrawer () throws IOException { + InputStream stream = this.getClass().getResourceAsStream("/pong/Title.png"); + if (stream != null) title = ImageIO.read(stream); + else throw new IOException("Failed to access /pong/Title.png resource!"); + stream = this.getClass().getResourceAsStream("/pong/Start.png"); + if (stream != null) start = ImageIO.read(stream); + else throw new IOException("Failed to access /pong/Start.png resource!"); + stream = this.getClass().getResourceAsStream("/pong/Settings.png"); + if (stream != null) settings = ImageIO.read(stream); + else throw new IOException("Failed to access /pong/Settings.png resource!"); + stream = this.getClass().getResourceAsStream("/pong/Quit.png"); + if (stream != null) quit = ImageIO.read(stream); + else throw new IOException("Failed to access /pong/Quit.png resource!"); + } + + /** + * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. + * + * @param width The width of the pixel map. + * @param height The height of the pixel map. + * @param panel The panel to apply changes to. + */ + @Override + public void applyChanges (int width, int height, GraphicsPanel panel) { // todo change pixel sizes to be dynamic to screen, implement menu stuffs. + panel.addImage(100, 50, 400, 100, title); + panel.addImage(100, 450, 210, 50, start); + panel.addImage(100, 600, 340, 50, settings); + panel.addImage(100, 750, 170, 50, quit); + } +} diff --git a/src/main/java/pong/ui/PauseMenu.java b/src/main/java/pong/ui/PauseMenu.java new file mode 100644 index 0000000..3b6518f --- /dev/null +++ b/src/main/java/pong/ui/PauseMenu.java @@ -0,0 +1,205 @@ +package pong.ui; + +import sugaEngine.Game; +import sugaEngine.graphics.DrawListener; +import sugaEngine.graphics.GraphicsPanel; +import sugaEngine.input.GameMouseListener; +import sugaEngine.input.KeyValues; +import sugaEngine.threads.SugaThread; + +import java.awt.*; + +/** + * The PauseMenu is used by players to restart the game, exit pong, return to main menu, or to resume the game. Should + * support mouse clicks and buttons should change on hover. + * + * @author Sugaku + */ +public class PauseMenu implements DrawListener { + + /** + * The mouse listener being used to highlight active elements of the PauseMenu and get when the player clicks on an + * element. + */ + private final GameMouseListener mouseListener; + + /** + * The game instance associated with this PauseMenu. + */ + private final Game game; + + /** + * The currently highlighted element of the pause menu. + */ + private MenuOptions highlighted = MenuOptions.CONTINUE; + + /** + * The mouse position last time it was checked. + */ + private Point lastPos = new Point(0,0); + + /** + * An enum of menu options that can be highlighted. + * + * @author Sugaku + */ + private enum MenuOptions { + + /** + * The 'continue' option in the pause menu. + */ + CONTINUE, + + /** + * The 'restart' option in the pause menu. + */ + RESTART, + + /** + * The 'settings' option in the pause menu. + */ + SETTINGS, + + /** + * The 'main menu' option in the pause menu. + */ + MAIN_MENU, + + /** + * The 'quit' option in the pause menu. + */ + QUIT + } + + /** + * Called by the game whenever enter or click is called. + * + * @param game Sometimes scenes need to be loaded from this method. Hence, the need to pass the game instance. + */ + public void enter (Game game) { + SugaThread thread = game.getThread(); + if (thread.getPaused()) { + switch (highlighted) { + case CONTINUE -> thread.setPaused(false); + case RESTART -> { + thread.setPaused(false); + game.loadScene("Main Game"); + } + case SETTINGS -> game.loadScene("Settings"); + case MAIN_MENU -> { + thread.setPaused(false); + game.loadScene("Main Menu"); + } + case QUIT -> thread.setStopped(true); // TODO: Stop graphics thread. + } + highlighted = MenuOptions.CONTINUE; + } + } + + /** + * Creates a new PauseMenu instance with the given GameMouseListener. + * + * @param mouseListener The mouse listener that will be associated with this PauseMenu. + * @param game The game instance of this pause menu. + */ + public PauseMenu (GameMouseListener mouseListener, Game game) { + this.mouseListener = mouseListener; + this.game = game; + } + + /** + * Called when trying to determine which pause screen element to highlight. + * + * @param height The height of the screen. + */ + public void checkMouse (int height) { + height = height / 2; + Point current = mouseListener.getMousePos(); + if (current == null) return; + if (current.distance(lastPos) < 20) return; + lastPos = (Point) current.clone(); + if (lastPos.y <= height - 198) highlighted = MenuOptions.CONTINUE; + else if (lastPos.y <= height - 78) highlighted = MenuOptions.RESTART; + else if (lastPos.y <= height + 42) highlighted = MenuOptions.SETTINGS; + else if (lastPos.y <= height + 162) highlighted = MenuOptions.MAIN_MENU; + else highlighted = MenuOptions.QUIT; + } + + /** + * Moves the currently highlighted menu option up or down depending on the value given. + * + * @param input The key that was pressed to move the menu selection. + */ + public void move (KeyValues input) { + if (input == KeyValues.ARROW_DOWN) { + switch (highlighted) { + case QUIT -> highlighted = MenuOptions.CONTINUE; + case CONTINUE -> highlighted = MenuOptions.RESTART; + case RESTART -> highlighted = MenuOptions.SETTINGS; + case SETTINGS -> highlighted = MenuOptions.MAIN_MENU; + case MAIN_MENU -> highlighted = MenuOptions.QUIT; + } + } else if (input == KeyValues.ARROW_UP) { + switch (highlighted) { + case RESTART -> highlighted = MenuOptions.CONTINUE; + case SETTINGS -> highlighted = MenuOptions.RESTART; + case MAIN_MENU -> highlighted = MenuOptions.SETTINGS; + case QUIT -> highlighted = MenuOptions.MAIN_MENU; + case CONTINUE -> highlighted = MenuOptions.QUIT; + } + } + } + + /** + * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. + * + * @param width The width of the pixel map. + * @param height The height of the pixel map. + * @param panel The panel to apply changes to. + */ + @Override + public void applyChanges (int width, int height, GraphicsPanel panel) { + if (game.getThread().getPaused()) { + checkMouse(height); + int dx = 0; + int y = 0; + int[] scales = new int[]{ 6, 6, 6, 6, 6}; + switch (highlighted) { + case CONTINUE -> { + dx = (38 * 4) + 20; + y = (height / 2) - 258 + 20; + scales[0] = 8; + } + case RESTART -> { + dx = (29 * 4) + 20; + y = (height / 2) - 138 + 20; + scales[1] = 8; + } + case SETTINGS -> { + dx = (34 * 4) + 20; + y = (height / 2) - 18 + 20; + scales[2] = 8; + } + case MAIN_MENU -> { + dx = (43 * 4) + 20; + y = (height / 2) + 102 + 20; + scales[3] = 8; + } + case QUIT -> { + dx = (17 * 4) + 20; + y = (height / 2) + 222 + 20; + scales[4] = 8; + } + } + for (int dy = -16; dy <= 16; dy += 8) { + panel.setBigPixel((width / 2) + dx, y + dy, 8, Color.WHITE); + panel.setBigPixel((width / 2) - dx, y + dy, 8, Color.WHITE); + } + panel.addImage((width / 2) - (38 * (scales[0] / 2)), (height / 2) - 258, 38 * scales[0], 5 * scales[0], "/pong/Continue.png"); + panel.addImage((width / 2) - (29 * (scales[1] / 2)), (height / 2) - 138, 29 * scales[1], 5 * scales[1], "/pong/Restart.png"); + panel.addImage((width / 2) - (34 * (scales[2] / 2)), (height / 2) - 18, 34 * scales[2], 5 * scales[2],"/pong/Settings.png"); + panel.addImage((width / 2) - (43 * (scales[3] / 2)), (height / 2) + 102, 43 * scales[3], 5 * scales[3],"/pong/MainMenu.png"); + panel.addImage((width / 2) - (17 * (scales[4] / 2)), (height / 2) + 222, 17 * scales[4], 5 * scales[4],"/pong/Quit.png"); + } + } +} diff --git a/src/main/java/pong/ui/ScoreCounter.java b/src/main/java/pong/ui/ScoreCounter.java new file mode 100644 index 0000000..06f7d00 --- /dev/null +++ b/src/main/java/pong/ui/ScoreCounter.java @@ -0,0 +1,85 @@ +package pong.ui; + +import sugaEngine.graphics.DrawListener; +import sugaEngine.graphics.GraphicsPanel; +import sugaEngine.physics.Vector; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The ScoreCounter draws the current score value to the screen. + * + * @author Sugaku + */ +@SuppressWarnings("ClassCanBeRecord") +public class ScoreCounter implements DrawListener { + + /** + * The counter for the ScoreCounter. + */ + private final AtomicInteger score; + + /** + * The position to display this ScoreCounter. + */ + private final Vector pos; + + /** + * Creates a new ScoreCounter object with the given score, and centered at the given position. + * + * @param score The score that should be displayed by this score counter. + * @param pos The center position that this score counter should be displayed at. + */ + public ScoreCounter (AtomicInteger score, Vector pos) { + this.score = score; + this.pos = pos; + } + + /** + * Called every drawing frame so programs have a chance to make their voices heard on what gets drawn. + * + * @param width The width of the pixel map. + * @param height The height of the pixel map. + * @param panel The panel to apply changes to. + */ + @Override + public void applyChanges (int width, int height, GraphicsPanel panel) { + List digits = new ArrayList<>(); + int score = this.score.get(); + digits.add(score % 10); + score = (int) (score / 10.0); + while (score > 0) { + digits.add(score % 10); + score = (int) (score / 10.0); + } + Vector origin = pos.clone(); + for (Integer i : digits) { + drawDigit(panel, origin, i); + origin.add(new Vector(-40, 0, 0)); + } + } + + /** + * Draws the given digit to the screen, centered on the given position. + * + * @param panel The panel to draw the number to. + * @param origin The origin point to draw the number relative to. + * @param digit The digit to draw to the screen. + */ + public void drawDigit (GraphicsPanel panel, Vector origin, int digit) { + switch (digit) { + case 0 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number0.png"); + case 1 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number1.png"); + case 2 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number2.png"); + case 3 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number3.png"); + case 4 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number4.png"); + case 5 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number5.png"); + case 6 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number6.png"); + case 7 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number7.png"); + case 8 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number8.png"); + case 9 -> panel.addImage((int) origin.getX(), (int) origin.getY(), 30, 50, "/pong/Number9.png"); + } + } +} diff --git a/src/main/java/sugaEngine/Game.java b/src/main/java/sugaEngine/Game.java index fb7a7a7..6a02f53 100644 --- a/src/main/java/sugaEngine/Game.java +++ b/src/main/java/sugaEngine/Game.java @@ -4,13 +4,12 @@ import sugaEngine.graphics.DrawListener; import sugaEngine.input.GameKeyListener; import sugaEngine.input.GameMouseListener; +import sugaEngine.input.KeyValues; import sugaEngine.physics.PhysicsEngine; import sugaEngine.threads.SugaThread; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.awt.event.MouseEvent; +import java.util.*; /** * Games require a main game loop to run along with game components that need to be run every game cycle. @@ -19,6 +18,12 @@ */ public abstract class Game { + /** + * These are keys that are currently being held. That can be useful information in it of itself but this is used to + * ignore future key pressed messages. + */ + protected List pressedKeys = new ArrayList<>(); + /** * A list of game objects for this game. Their logic should be called every cycle. */ @@ -108,7 +113,26 @@ public void loop () { /** * Processes inputs given by players. Is run during pause. */ - public abstract void processInput (); + public void processInput () { + Stack mice = mouseListener.getEvents(); + while (mice.size() > 0) { + MouseEvent e = mice.pop(); + loadedScene.mouseInput(e.getPoint(), e.getButton() == 1); + } + Stack keys = keyListener.getKeysPressed(); + while (keys.size() > 0) { + int key = keys.pop(); + if (pressedKeys.contains(key)) continue; + pressedKeys.add(key); + loadedScene.keyboardInput(KeyValues.toEnum(key), true); + } + keys = keyListener.getKeysDepressed(); + while (keys.size() > 0) { + int key = keys.pop(); + pressedKeys.remove((Integer) key); + loadedScene.keyboardInput(KeyValues.toEnum(key), false); + } + } /** * Adds a GameObject by the given name. @@ -122,6 +146,16 @@ public void addGameObject (String name, GameObject object) { physics.addObject(object); } + /** + * Accessor method for game objects. + * + * @param name The name of the object to attempt to get. + * @return The found object or null. + */ + public GameObject getGameObject (String name) { + return objects.get(name); + } + /** * Registers a new AI agent so that it can be called every tick after collisions but before object logic. * diff --git a/src/main/java/sugaEngine/Scene.java b/src/main/java/sugaEngine/Scene.java index 2ae9ac2..66c8bc2 100644 --- a/src/main/java/sugaEngine/Scene.java +++ b/src/main/java/sugaEngine/Scene.java @@ -1,5 +1,6 @@ package sugaEngine; +import sugaEngine.input.KeyValues; import java.awt.*; /** @@ -28,10 +29,10 @@ public boolean load (Game game) { /** * Passes a keyboard input into the scene. * - * @param key The keycode of the key. + * @param key The value of the key pressed. * @param pressed True if the key was pressed, false if it was released. */ - public abstract void keyboardInput (int key, boolean pressed); + public abstract void keyboardInput (KeyValues key, boolean pressed); /** * Passes a mouse input into the scene. diff --git a/src/main/java/sugaEngine/input/KeyValues.java b/src/main/java/sugaEngine/input/KeyValues.java index d4937a7..a6e7bc8 100644 --- a/src/main/java/sugaEngine/input/KeyValues.java +++ b/src/main/java/sugaEngine/input/KeyValues.java @@ -7,6 +7,11 @@ */ public enum KeyValues { + /** + * The enter key maps to keycode '10'. + */ + ENTER(10), + /** * The escape key maps to keycode 27. */ @@ -42,6 +47,11 @@ public enum KeyValues { */ D(68), + /** + * The 'i' key maps to keycode 73. + */ + I(73), + /** * The 'l' key maps to keycode 76. */ @@ -79,4 +89,17 @@ public enum KeyValues { public int getValue () { return value; } + + /** + * Converts the given int keycode into an enum value from KeyValues. + * + * @param code The code of the key pressed. + * @return The enum value of the key if present, otherwise null. + */ + public static KeyValues toEnum (int code) { // Might be a bit slow for input handling. Perhaps prioritize more popular keys. + for (KeyValues k : KeyValues.values()) // This would also be a good level for key remapping. + if (k.getValue() == code) + return k; + return null; + } } diff --git a/src/main/java/sugaEngine/physics/HitBox.java b/src/main/java/sugaEngine/physics/HitBox.java index 7cd0042..87c2f06 100644 --- a/src/main/java/sugaEngine/physics/HitBox.java +++ b/src/main/java/sugaEngine/physics/HitBox.java @@ -1,6 +1,7 @@ package sugaEngine.physics; import sugaEngine.graphics.Graphics2d; +import sugaEngine.graphics.GraphicsPanel; import java.awt.*; import java.util.ArrayList; @@ -112,7 +113,7 @@ public Vector getPos () { * @param panel The panel to draw the HitBox test points to. * @param color The color to draw the test points as. */ - public void drawHitBox (Graphics2d panel, Color color) { + public void drawHitBox (GraphicsPanel panel, Color color) { int y = (int) (pos.getY() + (height / 2.0)); int x = (int) (pos.getX() - (width / 2.0)); for (int i = x; i <= (int) (pos.getX() + (width / 2.0)); i++) panel.setPixel(i, y, color); diff --git a/src/main/java/sugaEngine/threads/GraphicsThread.java b/src/main/java/sugaEngine/threads/GraphicsThread.java index 3f5cac7..7669e35 100644 --- a/src/main/java/sugaEngine/threads/GraphicsThread.java +++ b/src/main/java/sugaEngine/threads/GraphicsThread.java @@ -2,6 +2,10 @@ import sugaEngine.graphics.GraphicsPanel; +import java.awt.event.WindowEvent; + +import static main.Main.frame; + /** * A thread used to refresh the graphics of a panel as fast as possible. * @@ -61,6 +65,7 @@ public void run () { if (!paused) panel.repaint(); frames++; } + frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); } /** diff --git a/src/main/resources/media/music/Itro & Tobu - Cloud 9.wav b/src/main/resources/media/music/Itro & Tobu - Cloud 9.wav deleted file mode 100644 index 81b8de5..0000000 Binary files a/src/main/resources/media/music/Itro & Tobu - Cloud 9.wav and /dev/null differ diff --git a/src/main/resources/pong/Continue.png b/src/main/resources/pong/Continue.png new file mode 100644 index 0000000..8509248 Binary files /dev/null and b/src/main/resources/pong/Continue.png differ diff --git a/src/main/resources/pong/MainMenu.png b/src/main/resources/pong/MainMenu.png new file mode 100644 index 0000000..6c5d3b0 Binary files /dev/null and b/src/main/resources/pong/MainMenu.png differ diff --git a/src/main/resources/pong/Number0.png b/src/main/resources/pong/Number0.png new file mode 100644 index 0000000..18b005d Binary files /dev/null and b/src/main/resources/pong/Number0.png differ diff --git a/src/main/resources/pong/Number1.png b/src/main/resources/pong/Number1.png new file mode 100644 index 0000000..bbc65f2 Binary files /dev/null and b/src/main/resources/pong/Number1.png differ diff --git a/src/main/resources/pong/Number2.png b/src/main/resources/pong/Number2.png new file mode 100644 index 0000000..1f94ce9 Binary files /dev/null and b/src/main/resources/pong/Number2.png differ diff --git a/src/main/resources/pong/Number3.png b/src/main/resources/pong/Number3.png new file mode 100644 index 0000000..ff93755 Binary files /dev/null and b/src/main/resources/pong/Number3.png differ diff --git a/src/main/resources/pong/Number4.png b/src/main/resources/pong/Number4.png new file mode 100644 index 0000000..fcf6a97 Binary files /dev/null and b/src/main/resources/pong/Number4.png differ diff --git a/src/main/resources/pong/Number5.png b/src/main/resources/pong/Number5.png new file mode 100644 index 0000000..168bf28 Binary files /dev/null and b/src/main/resources/pong/Number5.png differ diff --git a/src/main/resources/pong/Number6.png b/src/main/resources/pong/Number6.png new file mode 100644 index 0000000..bdf16ec Binary files /dev/null and b/src/main/resources/pong/Number6.png differ diff --git a/src/main/resources/pong/Number7.png b/src/main/resources/pong/Number7.png new file mode 100644 index 0000000..8673299 Binary files /dev/null and b/src/main/resources/pong/Number7.png differ diff --git a/src/main/resources/pong/Number8.png b/src/main/resources/pong/Number8.png new file mode 100644 index 0000000..1003d3b Binary files /dev/null and b/src/main/resources/pong/Number8.png differ diff --git a/src/main/resources/pong/Number9.png b/src/main/resources/pong/Number9.png new file mode 100644 index 0000000..c26531e Binary files /dev/null and b/src/main/resources/pong/Number9.png differ diff --git a/src/main/resources/pong/Quit.png b/src/main/resources/pong/Quit.png new file mode 100644 index 0000000..b4239dc Binary files /dev/null and b/src/main/resources/pong/Quit.png differ diff --git a/src/main/resources/pong/Restart.png b/src/main/resources/pong/Restart.png new file mode 100644 index 0000000..73e3209 Binary files /dev/null and b/src/main/resources/pong/Restart.png differ diff --git a/src/main/resources/pong/Settings.png b/src/main/resources/pong/Settings.png new file mode 100644 index 0000000..fc096d1 Binary files /dev/null and b/src/main/resources/pong/Settings.png differ diff --git a/src/main/resources/pong/Start.png b/src/main/resources/pong/Start.png new file mode 100644 index 0000000..cc34e66 Binary files /dev/null and b/src/main/resources/pong/Start.png differ diff --git a/src/main/resources/pong/Title.png b/src/main/resources/pong/Title.png new file mode 100644 index 0000000..8666843 Binary files /dev/null and b/src/main/resources/pong/Title.png differ