commit 4f72ad85e7b1317b697a51b8ee2cf60f67504e16 Author: Love Billenius Date: Thu Jan 11 16:18:34 2024 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6774fba --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..30a0395 --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + com.billenius + plingpong + 1.0 + + + 8 + 8 + UTF-8 + + + + org.jetbrains + annotations + 24.0.1 + + + + + + + maven-clean-plugin + 3.1.0 + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + + com.billenius.Main + + + + jar-with-dependencies + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + com.billenius.Main + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/billenius/GetDimension.java b/src/main/java/com/billenius/GetDimension.java new file mode 100644 index 0000000..4df0471 --- /dev/null +++ b/src/main/java/com/billenius/GetDimension.java @@ -0,0 +1,7 @@ +package com.billenius; + +import java.awt.*; + +public interface GetDimension { + Dimension get(); +} diff --git a/src/main/java/com/billenius/Main.java b/src/main/java/com/billenius/Main.java new file mode 100644 index 0000000..d2d9480 --- /dev/null +++ b/src/main/java/com/billenius/Main.java @@ -0,0 +1,32 @@ +package com.billenius; + +import javax.swing.*; +import java.awt.*; + + +public class Main extends JFrame { + public Main() throws HeadlessException { + super("Pling Pong!"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setContentPane(new Pong(this)); + pack(); + setVisible(true); + + Timer timer = new Timer(10, e -> { + repaint(); + }); + timer.start(); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | + IllegalAccessException ignored) { + } + new Main(); + }); + } +} diff --git a/src/main/java/com/billenius/Pong.java b/src/main/java/com/billenius/Pong.java new file mode 100644 index 0000000..9d8d787 --- /dev/null +++ b/src/main/java/com/billenius/Pong.java @@ -0,0 +1,93 @@ +package com.billenius; + +import com.billenius.VisibleObjects.*; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +public class Pong extends PongBackground implements KeyListener, ComponentListener { + private final PlayerPaddle[] playerPaddles = new PlayerPaddle[2]; + private final VisibleObject[] visibleObjects; + + public Pong(JFrame root) { + setPreferredSize(new Dimension(800, 600)); + setFocusable(true); + addKeyListener(this); + addComponentListener(this); + playerPaddles[0] = new PlayerPaddle(Side.LEFT, this::getSize); + playerPaddles[1] = new PlayerPaddle(Side.RIGHT, this::getSize); + Score score = new Score(this::getSize, 5, (playerWon) -> { + String[] choices = new String[]{"Yes", "No"}; + deSpawnAll(); + int choice = JOptionPane.showOptionDialog(this, + String.format("Player %d won. Do you want to play again?", playerWon + 1), + "Game Finished", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + choices, + choices[1] + ); + if (choice == 0) + spawnAll(); + else { + root.dispatchEvent(new WindowEvent(root, WindowEvent.WINDOW_CLOSING)); + } + }); + Ball ball = new Ball(this::getSize, playerPaddles, score); + visibleObjects = new VisibleObject[]{playerPaddles[0], playerPaddles[1], ball, score}; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + for (VisibleObject visibleObject : visibleObjects) + visibleObject.paint(g2); + } + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + for (PlayerPaddle playerPaddle : playerPaddles) + playerPaddle.startMoving(e.getKeyCode()); + } + + @Override + public void keyReleased(KeyEvent e) { + for (PlayerPaddle playerPaddle : playerPaddles) + playerPaddle.stopMoving(e.getKeyCode()); + } + + @Override + public void componentResized(ComponentEvent e) { + spawnAll(); + } + + @Override + public void componentMoved(ComponentEvent e) { + } + + @Override + public void componentShown(ComponentEvent e) { + } + + @Override + public void componentHidden(ComponentEvent e) { + } + + private void spawnAll() { + for (VisibleObject visibleObject : visibleObjects) + visibleObject.spawn(); + } + + private void deSpawnAll() { + for (VisibleObject visibleObject : visibleObjects) + visibleObject.deSpawn(); + } + +} diff --git a/src/main/java/com/billenius/PongBackground.java b/src/main/java/com/billenius/PongBackground.java new file mode 100644 index 0000000..c163ecb --- /dev/null +++ b/src/main/java/com/billenius/PongBackground.java @@ -0,0 +1,43 @@ +package com.billenius; + +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.Point2D; + +public class PongBackground extends JPanel { + private static final int DASH_LENGTH = 10; + private static final int DASH_SPACE = 10; + private static final Color STRIPES_BACKGROUND = new Color(Color.WHITE.getRed(), Color.WHITE.getGreen(), Color.WHITE.getBlue(), 100); + + @NotNull + private RadialGradientPaint getRadialGradientPaint() { + double radius = Math.sqrt(Math.pow(getWidth() / 2d, 2) + Math.pow(getHeight() / 2d, 2)); + + // Bright in center, dark in corners + return new RadialGradientPaint( + new Point2D.Double(getWidth() / 2d, getHeight() / 2d), + (float) radius, + new float[]{0f, 1f}, + new Color[]{Color.BLUE.brighter(), Color.BLUE.darker().darker().darker().darker()} + ); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g.create(); + + RadialGradientPaint radialGradientPaint = getRadialGradientPaint(); + g2d.setPaint(radialGradientPaint); + g2d.fillRect(0, 0, getWidth(), getHeight()); + + // Draw the dashed line + g2d.setColor(STRIPES_BACKGROUND); + g2d.setStroke(new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{DASH_LENGTH, DASH_SPACE}, 0)); + g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight()); + + g2d.dispose(); + } +} diff --git a/src/main/java/com/billenius/Utils.java b/src/main/java/com/billenius/Utils.java new file mode 100644 index 0000000..674be1c --- /dev/null +++ b/src/main/java/com/billenius/Utils.java @@ -0,0 +1,10 @@ +package com.billenius; + +public class Utils { + public static boolean arrayContains(int[] array, int toCheck){ + for (int value : array) + if (value == toCheck) + return true; + return false; + } +} diff --git a/src/main/java/com/billenius/Vector2D/BumpType.java b/src/main/java/com/billenius/Vector2D/BumpType.java new file mode 100644 index 0000000..043adb2 --- /dev/null +++ b/src/main/java/com/billenius/Vector2D/BumpType.java @@ -0,0 +1,5 @@ +package com.billenius.Vector2D; + +public enum BumpType { + WALL, PADDLE, BOTH; +} diff --git a/src/main/java/com/billenius/Vector2D/PaddleDirection.java b/src/main/java/com/billenius/Vector2D/PaddleDirection.java new file mode 100644 index 0000000..19fa7d2 --- /dev/null +++ b/src/main/java/com/billenius/Vector2D/PaddleDirection.java @@ -0,0 +1,5 @@ +package com.billenius.Vector2D; + +public enum PaddleDirection { + UP, NOT_MOVING, DOWN +} diff --git a/src/main/java/com/billenius/Vector2D/Vector2D.java b/src/main/java/com/billenius/Vector2D/Vector2D.java new file mode 100644 index 0000000..98af87d --- /dev/null +++ b/src/main/java/com/billenius/Vector2D/Vector2D.java @@ -0,0 +1,64 @@ +package com.billenius.Vector2D; + +import com.billenius.VisibleObjects.Side; +import com.billenius.VisibleObjects.Wall; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.Random; + +public class Vector2D { + private static final Random random = new Random(); + private static final double HYPOTENUSE = 2; + double x, y; + + /** + * Creates a vector and randomizes the angle + */ + public Vector2D() { + double angle = Math.toRadians(random.nextFloat() % 60 - 30); + int sign = random.nextBoolean() ? 1 : -1; + x = Math.cos(angle) * HYPOTENUSE * sign; + y = Math.sin(angle) * HYPOTENUSE * sign; + } + + + public void bump(@NotNull BumpType bumpType, @Nullable PaddleDirection paddleDirection) { + if (bumpType == BumpType.BOTH) { + x = -x; + y = -y; + } else if (bumpType == BumpType.WALL) { + y = -y; + } else if (bumpType == BumpType.PADDLE) { + x = -x; + double angle = 0; + if (paddleDirection != PaddleDirection.NOT_MOVING) { + double degrees = random.nextDouble() % 5 + 15; + degrees *= paddleDirection == PaddleDirection.UP ? -1 : 1; + angle = Math.toRadians(degrees); + + // Adjusting y direction based on the angle + y = Math.sin(angle) * HYPOTENUSE; + } + } + } + + /** + * This will modify the Point + */ + public void applyVector(Point point) { + point.x += (int) Math.round(x); + point.y += (int) Math.round(y); + } + + public Side horizontalDirection() { + return x > 0 ? Side.RIGHT : Side.LEFT; + } + + public Wall verticalDirection() { + return y > 0 ? Wall.DOWN : Wall.UP; + } + +} + diff --git a/src/main/java/com/billenius/VisibleObjects/Ball.java b/src/main/java/com/billenius/VisibleObjects/Ball.java new file mode 100644 index 0000000..117b0d7 --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/Ball.java @@ -0,0 +1,118 @@ +package com.billenius.VisibleObjects; + +import com.billenius.GetDimension; +import com.billenius.Vector2D.BumpType; +import com.billenius.Vector2D.Vector2D; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +import static java.util.Objects.nonNull; + +public class Ball implements VisibleObject { + private static final int RADIUS = 12; + private static final Color COLOR = Color.WHITE; + Vector2D vector = null; + /** NULL before the ball is spawned */ + Point point = null; + GetDimension gameDimension; + PlayerPaddle[] playerPaddles; + Score score; + + public Ball(GetDimension gameDimension, PlayerPaddle[] playerPaddles, Score score) { + this.gameDimension = gameDimension; + this.playerPaddles = playerPaddles; + this.score = score; + } + + @Override + public void spawn() { + Dimension dimension = gameDimension.get(); + point = new Point(dimension.width / 2, dimension.height / 2); + vector = new Vector2D(); + } + + @Override + public boolean isSpawned() { + return point != null; + } + + @Override + public void deSpawn() { + point = null; + vector = null; + } + + @Override + public void paint(Graphics2D g2) { + if (!isSpawned()) + return; + Side scored = sideScored(); + if (nonNull(scored)){ + score.incrementPlayerScore(scored.toInt()); + spawn(); + return; + } + move(); + vector.applyVector(point); + Stroke stroke = g2.getStroke(); + + g2.setStroke(new BasicStroke(0)); + g2.setColor(COLOR); + g2.fillOval(point.x, point.y, RADIUS, RADIUS); + g2.setStroke(stroke); + } + + private @Nullable Side sideScored() { + Dimension dimension = gameDimension.get(); + if (point.x >= dimension.width) + return Side.LEFT; + if (point.x <= 0) + return Side.RIGHT; + return null; + } + + public void move() { + Wall wall = collidedWithWall(); + PlayerPaddle paddle = collidedWithPaddle(playerPaddles); + + if (nonNull(wall) && nonNull(paddle)) vector.bump(BumpType.BOTH, null); + else if (nonNull(wall) && vector.verticalDirection() == wall) { + vector.bump(BumpType.WALL, null); + } else if (nonNull(paddle) && vector.horizontalDirection() == paddle.getSide()) { + vector.bump(BumpType.PADDLE, paddle.getPaddleDirection()); + } else vector.applyVector(point); + } + + + @Nullable PlayerPaddle collidedWithPaddle(PlayerPaddle[] playerPaddles) { + for (PlayerPaddle playerPaddle : playerPaddles) { + int paddleX = playerPaddle.getX(); + int paddleY = playerPaddle.getY(); + int paddleWidth = playerPaddle.getWidth(); + int paddleHeight = playerPaddle.getHeight(); + + // Check if the ball is within the x and y range of the paddle + if (point.x + RADIUS >= paddleX && point.x - RADIUS <= paddleX + paddleWidth / 2 && + point.y + RADIUS >= paddleY && point.y - RADIUS <= paddleY + paddleHeight) { + return playerPaddle; + } + } + return null; + } + + + protected int getHeight() { + return RADIUS; + } + + public @Nullable Wall collidedWithWall() { + Dimension dimension = gameDimension.get(); + if (point.y <= 0) + return Wall.UP; + if (point.y + getHeight() >= dimension.height) + return Wall.DOWN; + return null; + } +} + diff --git a/src/main/java/com/billenius/VisibleObjects/PlayerPaddle.java b/src/main/java/com/billenius/VisibleObjects/PlayerPaddle.java new file mode 100644 index 0000000..96df086 --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/PlayerPaddle.java @@ -0,0 +1,114 @@ +package com.billenius.VisibleObjects; + +import com.billenius.GetDimension; +import com.billenius.Utils; +import com.billenius.Vector2D.PaddleDirection; + +import java.awt.*; +import java.awt.event.KeyEvent; + +public class PlayerPaddle implements VisibleObject { + + protected static final int height = 80, width = 20; + private static final int MOVE_PER_TICK = 5; + private final int[] upDown; + private final Color color; + private boolean movingDown = false, movingUp = false; + private final GetDimension gameDimension; + private final Side side; + private int y = Integer.MIN_VALUE; + + public PlayerPaddle(Side side, GetDimension gameDimension) { + upDown = getUpDown(side); + this.gameDimension = gameDimension; + this.side = side; + color = (side == Side.LEFT ? Color.YELLOW : Color.RED).brighter().brighter(); + } + + + protected int getX() { + if (side == Side.LEFT) + return 0; + return gameDimension.get().width - width; + } + + protected int getY() { + return y; + } + + private static int[] getUpDown(Side side) { + return side == Side.LEFT ? new int[]{KeyEvent.VK_W, KeyEvent.VK_S} : new int[]{KeyEvent.VK_UP, KeyEvent.VK_DOWN}; + } + + + public void paint(Graphics2D g2) { + if (!isSpawned()) + return; + move(); + g2.setColor(color); + g2.fillRect(getX(), getY(), width, height); + } + + protected int getHeight() { + return height; + } + + protected int getWidth() { + return width; + } + + private void move() { + // We can't move both up and down. Don't move if we are down + if (movingUp && movingDown) + return; + + boolean canMoveDown = getY() + getHeight() < gameDimension.get().getHeight(); + boolean canMoveUp = getY() > 0; + if (movingUp && canMoveUp) + y -= MOVE_PER_TICK; + else if (movingDown && canMoveDown) + y += MOVE_PER_TICK; + } + + public void startMoving(int keyCode) { + if (!Utils.arrayContains(upDown, keyCode)) + return; + if (keyCode == upDown[0]) + movingUp = true; + else movingDown = true; + } + + public void stopMoving(int keyCode) { + if (!Utils.arrayContains(upDown, keyCode)) + return; + movingUp = false; + movingDown = false; + } + + protected PaddleDirection getPaddleDirection() { + if (movingUp != movingDown) + return PaddleDirection.NOT_MOVING; + else if (movingUp) + return PaddleDirection.UP; + return PaddleDirection.DOWN; + } + + @Override + public void spawn() { + y = gameDimension.get().height / 2 - height / 2; + } + + @Override + public boolean isSpawned() { + return getY() != Integer.MIN_VALUE; + } + + @Override + public void deSpawn() { + y = Integer.MIN_VALUE; + } + + public Side getSide() { + return side; + } +} diff --git a/src/main/java/com/billenius/VisibleObjects/Score.java b/src/main/java/com/billenius/VisibleObjects/Score.java new file mode 100644 index 0000000..53a8fb9 --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/Score.java @@ -0,0 +1,88 @@ +package com.billenius.VisibleObjects; + +import com.billenius.GetDimension; + +import java.awt.*; +import java.util.Arrays; + +public class Score implements VisibleObject { + private static final Color COLOR = Color.BLUE.brighter().brighter().brighter(); + private final int[] playerScores = new int[]{0, 0}; + private final int winningScore; + private final WinAction winAction; + private final GetDimension gameDimension; + private boolean spawned = false; + + public Score(GetDimension gameDimension, int winningScore, WinAction winAction) { + this.gameDimension = gameDimension; + this.winAction = winAction; + this.winningScore = winningScore; + } + + public void incrementPlayerScore(int player) { + playerScores[player]++; + int playerWon = playerHasWon(); + if (playerWon != -1) + winAction.run(playerWon); + } + + public int playerHasWon() { + for (int i = 0; i < playerScores.length; i++) { + if (playerScores[i] == winningScore) { + return i; + } + } + return -1; + } + + + @Override + public void spawn() { + spawned = true; + Arrays.fill(playerScores, 0); + } + + @Override + public boolean isSpawned() { + return spawned; + } + + @Override + public void deSpawn() { + spawned = false; + } + + private static final Color SHADOW_COLOR = Color.GRAY; + private static final Color TEXT_COLOR = Color.WHITE; + private static final int SHADOW_OFFSET = 3; + + public void paint(Graphics2D g2) { + if (!isSpawned()) + return; + /* We don't want to have the minus anywhere else than the in the middle */ + String measure = "0 - 0"; + String score = String.format("%d - %d", playerScores[0], playerScores[1]); + + g2.setColor(SHADOW_COLOR); + Font font = g2.getFont().deriveFont(Font.BOLD, 42f); + g2.setFont(font); + FontMetrics metrics = g2.getFontMetrics(); + int width = metrics.stringWidth(measure); + int height = metrics.getHeight(); + int x = gameDimension.get().width / 2 - width / 2; + int y = height - 3; + + // Draw shadow + g2.setColor(SHADOW_COLOR); + g2.drawString(score, x + SHADOW_OFFSET, y + SHADOW_OFFSET); + + // Create gradient from white to yellow from left to right across the text + GradientPaint gradient = new GradientPaint( + x, y, Color.YELLOW.brighter().brighter(), // start point for gradient (left edge of text) + x + width, y, Color.RED.brighter().brighter()); // end point for gradient (right edge of text) + + // Apply gradient and draw text + g2.setPaint(gradient); + g2.drawString(score, x, y); + } +} diff --git a/src/main/java/com/billenius/VisibleObjects/Side.java b/src/main/java/com/billenius/VisibleObjects/Side.java new file mode 100644 index 0000000..0c3f250 --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/Side.java @@ -0,0 +1,16 @@ +package com.billenius.VisibleObjects; + +public enum Side { + LEFT(0), RIGHT(1); + + private final int value; + + Side(int value) { + this.value = value; + } + + public int toInt() { + return value; + } + +} diff --git a/src/main/java/com/billenius/VisibleObjects/VisibleObject.java b/src/main/java/com/billenius/VisibleObjects/VisibleObject.java new file mode 100644 index 0000000..45ffacc --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/VisibleObject.java @@ -0,0 +1,13 @@ +package com.billenius.VisibleObjects; + +import java.awt.*; + +public interface VisibleObject { + void spawn(); + + boolean isSpawned(); + void deSpawn(); + + void paint(Graphics2D g2); + +} diff --git a/src/main/java/com/billenius/VisibleObjects/Wall.java b/src/main/java/com/billenius/VisibleObjects/Wall.java new file mode 100644 index 0000000..cb92fa2 --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/Wall.java @@ -0,0 +1,5 @@ +package com.billenius.VisibleObjects; + +public enum Wall { + UP, DOWN; +} diff --git a/src/main/java/com/billenius/VisibleObjects/WinAction.java b/src/main/java/com/billenius/VisibleObjects/WinAction.java new file mode 100644 index 0000000..ddba03a --- /dev/null +++ b/src/main/java/com/billenius/VisibleObjects/WinAction.java @@ -0,0 +1,5 @@ +package com.billenius.VisibleObjects; + +public interface WinAction { + void run(int playerWon); +}