From 5651ea76b07f7329b2c3cac1cb34a0f25671b2af Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 1 Jul 2020 16:01:37 +0200 Subject: [PATCH 1/5] Remove some try statements and switches --- src/main/dev/lh/FoodFactory.java | 82 +++++++++++++---------------- src/main/dev/lh/Updateable.java | 4 +- src/main/dev/lh/ui/Endscreen.java | 53 ++++++++++--------- src/main/dev/lh/ui/StartScreen.java | 53 +++++++++---------- 4 files changed, 92 insertions(+), 100 deletions(-) diff --git a/src/main/dev/lh/FoodFactory.java b/src/main/dev/lh/FoodFactory.java index f7c0b23..68316c4 100755 --- a/src/main/dev/lh/FoodFactory.java +++ b/src/main/dev/lh/FoodFactory.java @@ -1,5 +1,7 @@ package dev.lh; +import static java.awt.Color.*; + import java.awt.*; import java.util.Random; @@ -27,30 +29,56 @@ public class FoodFactory { * @since Snake 1.0 */ public static enum Food { + /** * Use if white food is wanted. */ - white, + white( + WHITE, 40 + ), /** * Use if yellow food is wanted. */ - yellow, + yellow( + YELLOW, 15 + ), /** * Use if orange food is wanted. */ - orange, + orange( + ORANGE, 6 + ), /** * Use if red food is wanted. */ - red, + red( + RED, 2 + ), /** * Use if blue food is wanted. */ - blue + blue( + BLUE, 1 + ); + + /** + * The color of the food item. + */ + public final Color color; + + /** + * The length bonus of the food item. + */ + public final int lengthBonus; + + private Food(Color color, int lengthBonus) { + this.color = color; + this.lengthBonus = lengthBonus; + } } private static FoodFactory foodFactory = new FoodFactory(); @@ -142,23 +170,7 @@ public class FoodFactory { * @since Snake 1.0 */ public void colorOfFood(Graphics g) { - switch (nextFood) { - case white: - g.setColor(Color.white); - break; - case yellow: - g.setColor(Color.yellow); - break; - case orange: - g.setColor(Color.orange); - break; - case red: - g.setColor(Color.red); - break; - case blue: - g.setColor(Color.blue); - break; - } + g.setColor(nextFood.color); } /** @@ -172,12 +184,12 @@ public class FoodFactory { /** * @param snakeHead the the head of a {@link Snake} object - * @return true if the current food intersects with the snakehead + * @return true if the current food intersects with the snake head * @since Snake 1.0 */ public boolean checkCollision(Rectangle snakeHead) { - int s = rectangleSize * 5; - Rectangle food = new Rectangle(pFood, new Dimension(s, s)); + int s = rectangleSize * 5; + Rectangle food = new Rectangle(pFood, new Dimension(s, s)); return food.intersects(snakeHead); } @@ -186,24 +198,6 @@ public class FoodFactory { * @since Snake 1.0 */ public int getAdditionalLength() { - int snakeAdditionalLength = 0; - switch (nextFood) { - case white: - snakeAdditionalLength = 40; - break; - case yellow: - snakeAdditionalLength = 15; - break; - case orange: - snakeAdditionalLength = 6; - break; - case red: - snakeAdditionalLength = 2; - break; - case blue: - snakeAdditionalLength = 1; - break; - } - return snakeAdditionalLength; + return nextFood.lengthBonus; } } diff --git a/src/main/dev/lh/Updateable.java b/src/main/dev/lh/Updateable.java index f2f91c6..2174917 100755 --- a/src/main/dev/lh/Updateable.java +++ b/src/main/dev/lh/Updateable.java @@ -3,8 +3,8 @@ package dev.lh; import java.awt.Graphics; /** - * This interface contains everything that needs to updated regularly.
- *
+ * This interface contains everything that needs to be updated regularly. + *

* Project: Snake
* File: Updateable.java
* Created: 11 Mar 2020
diff --git a/src/main/dev/lh/ui/Endscreen.java b/src/main/dev/lh/ui/Endscreen.java index be5cb30..1087106 100644 --- a/src/main/dev/lh/ui/Endscreen.java +++ b/src/main/dev/lh/ui/Endscreen.java @@ -20,9 +20,9 @@ public class Endscreen extends JDialog { private static final long serialVersionUID = -4457484397259161063L; - private static final int goodOrBadResult = 200; - private final JPanel contentPanel = new JPanel(); - private final int score; + private static final int goodOrBadResult = 200; + private final JPanel contentPanel = new JPanel(); + private final int score; /** * Create the dialog. @@ -31,32 +31,33 @@ public class Endscreen extends JDialog { */ public Endscreen(int score) { this.score = score; - try { + setTitle("Endscreen"); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setBounds(100, 100, 700, 700); + getContentPane().setLayout(new BorderLayout()); + contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + contentPanel.setLayout(new BorderLayout(0, 0)); + getContentPane().add(contentPanel, BorderLayout.CENTER); - setTitle("Endscreen"); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setBounds(100, 100, 700, 700); - getContentPane().setLayout(new BorderLayout()); - contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); - contentPanel.setLayout(new BorderLayout(0, 0)); - getContentPane().add(contentPanel, BorderLayout.CENTER); + JButton btnNewButton = new JButton("Play again"); + btnNewButton.setMnemonic(KeyEvent.VK_ENTER); + btnNewButton.addActionListener(e -> { + Main.startGame(); + dispose(); + }); + btnNewButton.setFont(new Font("Times New Roman", Font.PLAIN, 15)); + contentPanel.add(btnNewButton, BorderLayout.SOUTH); - JButton btnNewButton = new JButton("Play again"); - btnNewButton.setMnemonic(KeyEvent.VK_ENTER); - btnNewButton.addActionListener(e -> { Main.startGame(); dispose(); }); - btnNewButton.setFont(new Font("Times New Roman", Font.PLAIN, 15)); - contentPanel.add(btnNewButton, BorderLayout.SOUTH); + JLabel lblDeinPunktestand = new JLabel("Dein Punktestand: " + String.valueOf(score)); + lblDeinPunktestand.setFont(new Font("Times New Roman", Font.PLAIN, 25)); + contentPanel.add(lblDeinPunktestand, BorderLayout.NORTH); - JLabel lblDeinPunktestand = new JLabel("Dein Punktestand: " + String.valueOf(score)); - lblDeinPunktestand.setFont(new Font("Times New Roman", Font.PLAIN, 25)); - contentPanel.add(lblDeinPunktestand, BorderLayout.NORTH); - - Image resultImage = Toolkit.getDefaultToolkit() - .getImage(this.getClass().getResource((score < goodOrBadResult) ? "/Try_Again.jpg" : "/1211548-200.png")); - resultImage.flush(); - } catch (Exception e) { - e.printStackTrace(); - } + Image resultImage = Toolkit.getDefaultToolkit() + .getImage( + this.getClass() + .getResource((score < goodOrBadResult) ? "/Try_Again.jpg" : "/1211548-200.png") + ); + resultImage.flush(); } /** diff --git a/src/main/dev/lh/ui/StartScreen.java b/src/main/dev/lh/ui/StartScreen.java index c8d3cb9..0b908d7 100755 --- a/src/main/dev/lh/ui/StartScreen.java +++ b/src/main/dev/lh/ui/StartScreen.java @@ -21,13 +21,14 @@ import dev.lh.Main; */ public class StartScreen extends JFrame { - private static final long serialVersionUID = 6055940532003735543L; - private JPanel contentPane; + private static final long serialVersionUID = 6055940532003735543L; /** * Closes the application. */ - public static void close() { System.exit(0); } + public static void close() { + System.exit(0); + } /** * Launches Snake. @@ -43,31 +44,27 @@ public class StartScreen extends JFrame { * Create the frame. */ public StartScreen() { - try { - // readInHighscores(); - setTitle("Snake - Startscreen"); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setBounds(500, 200, 550, 550); - contentPane = new JPanel(); - contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); - setContentPane(contentPane); + setTitle("Snake - Startscreen"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(500, 200, 550, 550); - JButton buPlay = new JButton("Start Game"); - buPlay.setBounds(158, 197, 190, 131); - buPlay.setText("Play Again"); - buPlay.setMnemonic(KeyEvent.VK_ENTER); - buPlay.setFont(new Font("Times New Roman", Font.PLAIN, 16)); - buPlay.addActionListener(a -> { - Main.startGame(); - setVisible(false); - dispose(); - System.gc(); - }); - contentPane.add(buPlay); - contentPane.setLayout(null); - setVisible(true); - } catch (Exception e) { - e.printStackTrace(); - } + JPanel contentPane = new JPanel(); + contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + setContentPane(contentPane); + + JButton buPlay = new JButton("Start Game"); + buPlay.setBounds(158, 197, 190, 131); + buPlay.setText("Play Again"); + buPlay.setMnemonic(KeyEvent.VK_ENTER); + buPlay.setFont(new Font("Times New Roman", Font.PLAIN, 16)); + buPlay.addActionListener(a -> { + Main.startGame(); + setVisible(false); + dispose(); + System.gc(); + }); + contentPane.add(buPlay); + contentPane.setLayout(null); + setVisible(true); } } -- 2.45.2 From a8908f7e13b5f3c3dad57c1ccc339d6f3dfe38a0 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 1 Jul 2020 16:18:59 +0200 Subject: [PATCH 2/5] Simplify snake drawing and movement calculation --- src/main/dev/lh/FoodFactory.java | 2 +- src/main/dev/lh/Snake.java | 95 ++++++++++++++---------------- src/main/dev/lh/Updateable.java | 6 +- src/main/dev/lh/ui/GameWindow.java | 12 ++-- 4 files changed, 53 insertions(+), 62 deletions(-) diff --git a/src/main/dev/lh/FoodFactory.java b/src/main/dev/lh/FoodFactory.java index 68316c4..9e597e0 100755 --- a/src/main/dev/lh/FoodFactory.java +++ b/src/main/dev/lh/FoodFactory.java @@ -28,7 +28,7 @@ public class FoodFactory { * @author Leon Hofmeister * @since Snake 1.0 */ - public static enum Food { + public enum Food { /** * Use if white food is wanted. diff --git a/src/main/dev/lh/Snake.java b/src/main/dev/lh/Snake.java index 2c41ed3..6fda5aa 100644 --- a/src/main/dev/lh/Snake.java +++ b/src/main/dev/lh/Snake.java @@ -1,6 +1,8 @@ package dev.lh; -import java.awt.*; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; @@ -31,30 +33,31 @@ public class Snake implements Updateable { /** * Use if the snake should head left. */ - Left, + LEFT, /** * Use if the snake should head right. */ - Right, + RIGHT, /** * Use if the snake should head up. */ - Up, + UP, /** * Use if the snake should head down. */ - Down; + DOWN; } - private static FoodFactory foodFactory = FoodFactory.getInstance(); - private static Endscreen endscreen; - private Direction direction; - private int length; - private List tiles = new ArrayList<>(); - private final int snakeSize = 10; + private static FoodFactory foodFactory = FoodFactory.getInstance(); + private static Endscreen endscreen; + private Direction direction = Direction.RIGHT; + private int length; + private List tiles = new ArrayList<>(); + + private static final int TILE_SIZE = 10; /** * Constructs a new Snake with the given length in tiles. @@ -63,21 +66,21 @@ public class Snake implements Updateable { * @since Snake 1.0 */ public Snake(int length) { - this.length = length; - direction = Direction.Right; - // adding the initial tiles of the snake + this.length = length; + + // Add initial tiles for (int i = 0; i < length; i++) - tiles.add(new Point(320 - snakeSize * i, 240)); + tiles.add(new Rectangle(320 - TILE_SIZE * i, 240, TILE_SIZE, TILE_SIZE)); } /** - * Adds the given length to the current snake object + * Increases the given length to the current snake object. * * @param additional the number of tiles to add * @since Snake 1.0 */ public void addLength(int additional) { - Point last = tiles.get(tiles.size() - 1); + Rectangle last = tiles.get(tiles.size() - 1); for (int i = 0; i < additional; i++) tiles.add(last); length += additional; @@ -88,17 +91,10 @@ public class Snake implements Updateable { * @since Snake 1.1 */ private boolean checkSelfCollision() { - Point headIndex = tiles.get(0); - Rectangle head = new Rectangle(headIndex.x, headIndex.y, snakeSize, snakeSize); - for (int index = 1; index < tiles.size(); index++) { - Point bodyIndex = tiles.get(index); - if (head.contains(new Rectangle(bodyIndex.x, bodyIndex.y, snakeSize, snakeSize))) return true; - } - return false; + return tiles.stream().skip(1).anyMatch(tiles.get(0)::contains); } /** - * * @since Snake 1.1 */ private void gameOver() { @@ -117,45 +113,41 @@ public class Snake implements Updateable { public void nextFrame() { int velX = 0, velY = 0; switch (direction) { - case Up: - velY = -snakeSize; + case UP: + velY = -TILE_SIZE; break; - case Down: - velY = snakeSize; + case DOWN: + velY = TILE_SIZE; break; - case Left: - velX = -snakeSize; + case LEFT: + velX = -TILE_SIZE; break; - case Right: - velX = snakeSize; + case RIGHT: + velX = TILE_SIZE; break; } - Point next = (Point) tiles.get(0).clone(), cur; - tiles.get(0).x += velX; - tiles.get(0).y += velY; - - for (int i = 1; i < length; i++) { - cur = tiles.get(i); - tiles.set(i, (Point) next.clone()); - next = cur; - } - - // case if snake is outside of the screen or touches itself + // Add a new tile at the front + tiles.add( + 0, + new Rectangle(tiles.get(0).x + velX, tiles.get(0).y + velY, TILE_SIZE, TILE_SIZE) + ); + // Remove the last tile + tiles.remove(tiles.size() - 1); + // Case if snake is outside of the screen or touches itself if (checkSelfCollision()) { gameOver(); System.out.println("Snake collided with itself."); return; } - // TODO: the game bounds checking below appears to work on Windows, however - // throws a NullPointerException on Linux/UNIX systems + // TODO: Test on Linux if (!Main.getGame().getBounds().contains(tiles.get(0))) { gameOver(); System.out.println("Snake went out of bounds."); return; } - - // case if snake eats food - if (foodFactory.checkCollision(new Rectangle(tiles.get(0).x, tiles.get(0).y, snakeSize, snakeSize))) { + // TODO: Move to Food class + // Case if snake eats food + if (foodFactory.checkCollision(tiles.get(0))) { addLength(foodFactory.getAdditionalLength()); GameWindow game = Main.getGame(); game.newFood(); @@ -163,10 +155,9 @@ public class Snake implements Updateable { } @Override - public void render(Graphics g) { + public void render(Graphics2D g) { g.setColor(Color.green); - for (int i = 0; i < length; i++) - g.fillRect(tiles.get(i).x, tiles.get(i).y, snakeSize, snakeSize); + tiles.forEach(g::fill); } /** diff --git a/src/main/dev/lh/Updateable.java b/src/main/dev/lh/Updateable.java index 2174917..272d812 100755 --- a/src/main/dev/lh/Updateable.java +++ b/src/main/dev/lh/Updateable.java @@ -1,6 +1,6 @@ package dev.lh; -import java.awt.Graphics; +import java.awt.Graphics2D; /** * This interface contains everything that needs to be updated regularly. @@ -25,8 +25,8 @@ public interface Updateable { /** * Renders the object. * - * @param g the {@link Graphics} object that is used to render this object + * @param g the graphics object that is used to render this object * @since Snake 1.0 */ - void render(Graphics g); + void render(Graphics2D g); } diff --git a/src/main/dev/lh/ui/GameWindow.java b/src/main/dev/lh/ui/GameWindow.java index f315436..211771d 100755 --- a/src/main/dev/lh/ui/GameWindow.java +++ b/src/main/dev/lh/ui/GameWindow.java @@ -52,8 +52,8 @@ public class GameWindow extends JFrame { protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); - g.fillRect(0, 0, super.getWidth(), super.getHeight()); - s.render(g); + g.fillRect(0, 0, getWidth(), getHeight()); + s.render((Graphics2D) g); foodFactory.paintFood(g); } }); @@ -66,19 +66,19 @@ public class GameWindow extends JFrame { switch (e.getKeyCode()) { case KeyEvent.VK_W: case KeyEvent.VK_UP: - if (!s.getRichtung().equals(Direction.Down)) s.setDirection(Direction.Up); + if (!s.getRichtung().equals(Direction.DOWN)) s.setDirection(Direction.UP); break; case KeyEvent.VK_A: case KeyEvent.VK_LEFT: - if (!s.getRichtung().equals(Direction.Right)) s.setDirection(Direction.Left); + if (!s.getRichtung().equals(Direction.RIGHT)) s.setDirection(Direction.LEFT); break; case KeyEvent.VK_S: case KeyEvent.VK_DOWN: - if (!s.getRichtung().equals(Direction.Up)) s.setDirection(Direction.Down); + if (!s.getRichtung().equals(Direction.UP)) s.setDirection(Direction.DOWN); break; case KeyEvent.VK_D: case KeyEvent.VK_RIGHT: - if (!s.getRichtung().equals(Direction.Left)) s.setDirection(Direction.Right); + if (!s.getRichtung().equals(Direction.LEFT)) s.setDirection(Direction.RIGHT); break; } } -- 2.45.2 From 52a4c876fa872c6d35c38aa1a71d2b1f2b2740e4 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 1 Jul 2020 19:19:50 +0200 Subject: [PATCH 3/5] Add new pipeline and other fun stuff --- src/main/dev/lh/Food.java | 40 ++++++ src/main/dev/lh/FoodFactory.java | 224 ++++++----------------------- src/main/dev/lh/Handler.java | 46 ++++++ src/main/dev/lh/Snake.java | 31 ++-- src/main/dev/lh/Updateable.java | 4 +- src/main/dev/lh/Viewport.java | 98 +++++++++++++ src/main/dev/lh/ui/GameWindow.java | 77 ++++------ 7 files changed, 272 insertions(+), 248 deletions(-) create mode 100644 src/main/dev/lh/Food.java create mode 100644 src/main/dev/lh/Handler.java create mode 100644 src/main/dev/lh/Viewport.java diff --git a/src/main/dev/lh/Food.java b/src/main/dev/lh/Food.java new file mode 100644 index 0000000..560ed93 --- /dev/null +++ b/src/main/dev/lh/Food.java @@ -0,0 +1,40 @@ +package dev.lh; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; + +/** + * Project: Snake
+ * File: Food.java
+ * Created: 01.07.2020
+ * + * @author Kai S. K. Engelbart + * @since Snake 1.1 + */ +public final class Food implements Updateable { + + private final Color color; + private final int lengthBonus; + private final Rectangle bounds; + + public Food(Color color, int lengthBonus, Rectangle bounds) { + this.color = color; + this.lengthBonus = lengthBonus; + this.bounds = bounds; + } + + public void checkCollision(Snake snake) { + if (bounds.intersects(snake.getHead())) {} + } + + @Override + public void render(Graphics2D g) { + g.setColor(color); + g.fill(bounds); + } + + public int getLengthBonus() { return lengthBonus; } + + public Rectangle getBounds() { return bounds; } +} diff --git a/src/main/dev/lh/FoodFactory.java b/src/main/dev/lh/FoodFactory.java index 9e597e0..7de2c2c 100755 --- a/src/main/dev/lh/FoodFactory.java +++ b/src/main/dev/lh/FoodFactory.java @@ -2,202 +2,68 @@ package dev.lh; import static java.awt.Color.*; -import java.awt.*; +import java.awt.Color; +import java.awt.Rectangle; import java.util.Random; -import dev.lh.ui.GameWindow; - /** + * Generates food items with predefined properties at random positions and calculates the next + * spawning time. + *

* Project: Snake
* File: FoodFactory.java
* Created: 11 Mar 2020
* * @author Leon Hofmeister + * @author Kai S. K. Engelbart * @since Snake 1.0 */ -public class FoodFactory { +public final class FoodFactory { + + private int width, height; + private long nextSpawnTime; + private Random random = new Random(); + + private static final Color[] FOOD_COLORS = { + WHITE, YELLOW, ORANGE, RED, BLUE + }; + private static final int[] FOOD_LENGTH_BONUSES = { + 40, 15, 6, 2, 1 + }; /** - * This enum contains all possible variations of foods. The higher the ordinal - * of an element, the less it is worth.
- *
- * Project: Snake
- * File: FoodFactory.java
- * Created: 11 Mar 2020
- * - * @author Leon Hofmeister - * @since Snake 1.0 + * Initializes a food factory. + * + * @param width the width of the viewport + * @param height the height of the viewport + * @since Snake 1.1 */ - public enum Food { + public FoodFactory(int width, int height) { + this.width = width; + this.height = height; + } - /** - * Use if white food is wanted. - */ - white( - WHITE, 40 - ), - - /** - * Use if yellow food is wanted. - */ - yellow( - YELLOW, 15 - ), - - /** - * Use if orange food is wanted. - */ - orange( - ORANGE, 6 - ), - - /** - * Use if red food is wanted. - */ - red( - RED, 2 - ), - - /** - * Use if blue food is wanted. - */ - blue( - BLUE, 1 + /** + * @return a new food item + * @since Snake 1.1 + */ + public synchronized Food spawn() { + nextSpawnTime = System.currentTimeMillis() + random.nextInt(15000) + 1000; + int seed = random.nextInt(5); + return new Food( + FOOD_COLORS[seed], + FOOD_LENGTH_BONUSES[seed], + new Rectangle(random.nextInt(width - 100) + 50, + random.nextInt(height - 100) + 50, + seed * 10, + seed * 10 + ) ); - - /** - * The color of the food item. - */ - public final Color color; - - /** - * The length bonus of the food item. - */ - public final int lengthBonus; - - private Food(Color color, int lengthBonus) { - this.color = color; - this.lengthBonus = lengthBonus; - } - } - - private static FoodFactory foodFactory = new FoodFactory(); - - private long timeOfNextFood; - - private Point pFood; - - private Food nextFood = Food.white; - - private int rectangleSize = 6; - - private FoodFactory() {} - - /** - * @return the (singleton) instance of FoodFactory - * @since Snake 1.0 - */ - public static FoodFactory getInstance() { return foodFactory; } - - /** - * @return a new {@link Food} object without its position - * @since Snake 1.0 - */ - public Food generateFood() { - nextFood = Food.values()[new Random().nextInt(Food.values().length)]; - rectangleSize = nextFood.ordinal() + 2; - setTimeToNextFoodMillis(); - return nextFood; } /** - * Generates the amount of time that needs to pass before the next food object - * will be constructed. - * - * @since Snake 1.0 + * @return the time after which a new food item should be spawned + * @since Snake 1.1 */ - public void setTimeToNextFoodMillis() { - timeOfNextFood = System.currentTimeMillis() + new Random().nextInt(15000) + 1000; - } - - /** - * @return the type of the next food - * @since Snake 1.0 - */ - public Food getNextFood() { return nextFood; } - - /** - * @param nextFood the type the next food should have - * @since Snake 1.0 - */ - public void setNext(Food nextFood) { this.nextFood = nextFood; } - - /** - * @return the time at which a new food object will be automatically created - * @since Snake 1.0 - */ - public long getTimeOfNextFood() { return timeOfNextFood; } - - /** - * @param width the width of the currently used {@link GameWindow} - * @param height the height of the currently used {@link GameWindow} - * @return the position of the new {@link Food} object - * @since Snake 1.0 - */ - public Point generateFoodLocation(int width, int height) { - assert (width > 100 && height > 100); - Random r = new Random(); - return pFood = new Point(r.nextInt(width - 100) + 50, r.nextInt(height - 100) + 50); - } - - /** - * @return the size of the corresponding food (length = width) - * @since Snake 1.0 - */ - public int getRectangleSize() { return rectangleSize; } - - /** - * @return the location of the currently displayed food - * @since Snake 1.0 - */ - public Point getFoodLocation() { return pFood; } - - /** - * Sets the color of the given {@link Graphics} object according to the type of - * food. - * - * @param g the graphics object to paint - * @since Snake 1.0 - */ - public void colorOfFood(Graphics g) { - g.setColor(nextFood.color); - } - - /** - * @param g the {@link Graphics} object used to paint the current food object - * @since Snake 1.0 - */ - public void paintFood(Graphics g) { - colorOfFood(g); - g.fillRect(pFood.x, pFood.y, 5 * rectangleSize, 5 * rectangleSize); - } - - /** - * @param snakeHead the the head of a {@link Snake} object - * @return true if the current food intersects with the snake head - * @since Snake 1.0 - */ - public boolean checkCollision(Rectangle snakeHead) { - int s = rectangleSize * 5; - Rectangle food = new Rectangle(pFood, new Dimension(s, s)); - return food.intersects(snakeHead); - } - - /** - * @return the length that will be added to the snake - * @since Snake 1.0 - */ - public int getAdditionalLength() { - return nextFood.lengthBonus; - } + public long getNextSpawnTime() { return nextSpawnTime; } } diff --git a/src/main/dev/lh/Handler.java b/src/main/dev/lh/Handler.java new file mode 100644 index 0000000..c1fe515 --- /dev/null +++ b/src/main/dev/lh/Handler.java @@ -0,0 +1,46 @@ +package dev.lh; + +import java.awt.Graphics2D; + +/** + * Manages the state of game objects. + *

+ * Project: Snake
+ * File: Handler.java
+ * Created: 01.07.2020
+ * + * @author Kai S. K. Engelbart + * @since Snake 1.1 + */ +public final class Handler implements Updateable { + + private Snake snake = new Snake(7); + private FoodFactory foodFactory; + + private volatile Food food; + + public Handler(Snake snake, FoodFactory foodFactory) { + this.snake = snake; + this.foodFactory = foodFactory; + food = foodFactory.spawn(); + } + + @Override + public void tick() { + snake.tick(); + food.tick(); + // Check for food collision + if (snake.getHead().intersects(food.getBounds())) { + snake.addLength(food.getLengthBonus()); + food = foodFactory.spawn(); + } + if (System.currentTimeMillis() > foodFactory.getNextSpawnTime()) + food = foodFactory.spawn(); + } + + @Override + public void render(Graphics2D g) { + snake.render(g); + food.render(g); + } +} diff --git a/src/main/dev/lh/Snake.java b/src/main/dev/lh/Snake.java index 6fda5aa..a774a9b 100644 --- a/src/main/dev/lh/Snake.java +++ b/src/main/dev/lh/Snake.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; import dev.lh.ui.Endscreen; -import dev.lh.ui.GameWindow; /** * Project: Snake
@@ -51,7 +50,6 @@ public class Snake implements Updateable { DOWN; } - private static FoodFactory foodFactory = FoodFactory.getInstance(); private static Endscreen endscreen; private Direction direction = Direction.RIGHT; private int length; @@ -103,14 +101,8 @@ public class Snake implements Updateable { Main.getGame().close(); } - /** - * @return the current {@link Direction} of the snake - * @since Snake 1.0 - */ - public Direction getRichtung() { return direction; } - @Override - public void nextFrame() { + public void tick() { int velX = 0, velY = 0; switch (direction) { case UP: @@ -140,18 +132,11 @@ public class Snake implements Updateable { return; } // TODO: Test on Linux - if (!Main.getGame().getBounds().contains(tiles.get(0))) { + if (!Main.getGame().getBounds().contains(getHead())) { gameOver(); System.out.println("Snake went out of bounds."); return; } - // TODO: Move to Food class - // Case if snake eats food - if (foodFactory.checkCollision(tiles.get(0))) { - addLength(foodFactory.getAdditionalLength()); - GameWindow game = Main.getGame(); - game.newFood(); - } } @Override @@ -160,9 +145,21 @@ public class Snake implements Updateable { tiles.forEach(g::fill); } + /** + * @return the current {@link Direction} of the snake + * @since Snake 1.0 + */ + public Direction getDirection() { return direction; } + /** * @param direction the new {@link Direction} of the snake * @since Snake 1.0 */ public void setDirection(Direction direction) { this.direction = direction; } + + /** + * @return a rectangle representing the head of the snake + * @since Snake 1.1 + */ + public Rectangle getHead() { return tiles.get(0); } } diff --git a/src/main/dev/lh/Updateable.java b/src/main/dev/lh/Updateable.java index 272d812..1daa80d 100755 --- a/src/main/dev/lh/Updateable.java +++ b/src/main/dev/lh/Updateable.java @@ -20,7 +20,7 @@ public interface Updateable { * * @since Snake 1.0 */ - void nextFrame(); + default void tick() {} /** * Renders the object. @@ -28,5 +28,5 @@ public interface Updateable { * @param g the graphics object that is used to render this object * @since Snake 1.0 */ - void render(Graphics2D g); + default void render(Graphics2D g) {} } diff --git a/src/main/dev/lh/Viewport.java b/src/main/dev/lh/Viewport.java new file mode 100644 index 0000000..9c5b6f2 --- /dev/null +++ b/src/main/dev/lh/Viewport.java @@ -0,0 +1,98 @@ +package dev.lh; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Implements a hardware-accelerated rendering loop. + *

+ * Project: Snake
+ * File: Viewport.java
+ * Created: 01.07.2020
+ * + * @author Kai S. K. Engelbart + * @since Snake 1.0 + */ +public class Viewport extends Canvas { + + private static final long serialVersionUID = 1L; + + // Enable OpenGL hardware acceleration + static { + System.setProperty("sun.java2d.trace", "timestamp,log,count"); + System.setProperty("sun.java2d.transaccel", "True"); + System.setProperty("sun.java2d.opengl", "True"); + } + + private Updateable gameRoot; + private Timer timer = new Timer(); + private TimerTask renderTask; + + /** + * @param gameRoot the game object responsible for updating the rest + * @since Snake 1.0 + */ + public Viewport(Updateable gameRoot) { + this.gameRoot = gameRoot; + setIgnoreRepaint(true); + } + + /** + * Starts the render task. + * + * @since Snake 1.1 + */ + public void start() { + if (renderTask != null) + renderTask.cancel(); + else + createBufferStrategy(2); + + renderTask = new TimerTask() { + + private long lastTime = System.currentTimeMillis(); + + @Override + public void run() { + long time = System.currentTimeMillis(); + double dt = (time - lastTime) * 1E-3; + lastTime = time; + + gameRoot.tick(); + render(); + } + }; + + timer.schedule(renderTask, 0, 100); + } + + /** + * Stops the render task. + * + * @since Snake 1.1 + */ + public void stop() { + renderTask.cancel(); + } + + private void render() { + Graphics2D g = (Graphics2D) getBufferStrategy().getDrawGraphics(); + + // Clear the screen + g.setColor(Color.BLACK); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Perform the actual rendering + gameRoot.render(g); + + // Flip buffers + g.dispose(); + getBufferStrategy().show(); + + // Synchronize with display refresh rate + getToolkit().sync(); + } +} diff --git a/src/main/dev/lh/ui/GameWindow.java b/src/main/dev/lh/ui/GameWindow.java index 211771d..d190736 100755 --- a/src/main/dev/lh/ui/GameWindow.java +++ b/src/main/dev/lh/ui/GameWindow.java @@ -1,15 +1,14 @@ package dev.lh.ui; -import java.awt.*; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.Timer; -import dev.lh.FoodFactory; -import dev.lh.Snake; +import dev.lh.*; import dev.lh.Snake.Direction; /** @@ -22,21 +21,20 @@ import dev.lh.Snake.Direction; */ public class GameWindow extends JFrame { - private static final long serialVersionUID = 1L; - private Snake s = new Snake(7); - private FoodFactory foodFactory = FoodFactory.getInstance(); - private Timer timer; + private static final long serialVersionUID = 1L; + + private Viewport viewport; /** * @param title the title of the frame * @since Snake 1.0 */ public GameWindow(String title) { + // Initialize window super(title); Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); setBounds(new Rectangle(size)); setLocation(0, 0); - setLocationRelativeTo(null); setMinimumSize(size); setPreferredSize(size); setMaximumSize(size); @@ -44,19 +42,14 @@ public class GameWindow extends JFrame { setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); - add(new JPanel() { + // Initialize game objects + Snake snake = new Snake(7); + FoodFactory foodFactory = new FoodFactory(getWidth(), getHeight()); + Handler handler = new Handler(snake, foodFactory); - private static final long serialVersionUID = 1L; - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - g.setColor(Color.black); - g.fillRect(0, 0, getWidth(), getHeight()); - s.render((Graphics2D) g); - foodFactory.paintFood(g); - } - }); + // Initialize viewport + viewport = new Viewport(handler); + add(viewport); addKeyListener(new KeyAdapter() { @@ -66,56 +59,40 @@ public class GameWindow extends JFrame { switch (e.getKeyCode()) { case KeyEvent.VK_W: case KeyEvent.VK_UP: - if (!s.getRichtung().equals(Direction.DOWN)) s.setDirection(Direction.UP); + if (!snake.getDirection().equals(Direction.DOWN)) + snake.setDirection(Direction.UP); break; case KeyEvent.VK_A: case KeyEvent.VK_LEFT: - if (!s.getRichtung().equals(Direction.RIGHT)) s.setDirection(Direction.LEFT); + if (!snake.getDirection().equals(Direction.RIGHT)) + snake.setDirection(Direction.LEFT); break; case KeyEvent.VK_S: case KeyEvent.VK_DOWN: - if (!s.getRichtung().equals(Direction.UP)) s.setDirection(Direction.DOWN); + if (!snake.getDirection().equals(Direction.UP)) + snake.setDirection(Direction.DOWN); break; case KeyEvent.VK_D: case KeyEvent.VK_RIGHT: - if (!s.getRichtung().equals(Direction.LEFT)) s.setDirection(Direction.RIGHT); + if (!snake.getDirection().equals(Direction.LEFT)) + snake.setDirection(Direction.RIGHT); break; } } }); - newFood(); - timer = new Timer( - 50, - evt -> { - s.nextFrame(); - if (System.currentTimeMillis() >= foodFactory.getTimeOfNextFood()) - newFood(); - repaint(); - } - ); - timer.start(); - setVisible(true); + viewport.start(); } /** - * Generates new food. - * - * @since Snake 1.1 - */ - public void newFood() { - foodFactory.generateFood(); - foodFactory.generateFoodLocation(getWidth(), getHeight()); - } - - /** - * Disposes this frame + * Disposes this frame. * * @since Snake 1.1 */ public void close() { - timer.stop(); + viewport.stop(); + setVisible(false); dispose(); } } -- 2.45.2 From 7c55e049cfebd256c779070458c58648cb4e2c5e Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 1 Jul 2020 19:29:52 +0200 Subject: [PATCH 4/5] Add ol' JDoc --- src/main/dev/lh/Food.java | 22 ++++++++++++++++++---- src/main/dev/lh/FoodFactory.java | 4 ++-- src/main/dev/lh/Handler.java | 8 ++++++++ src/main/dev/lh/Viewport.java | 2 +- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/dev/lh/Food.java b/src/main/dev/lh/Food.java index 560ed93..d47fa77 100644 --- a/src/main/dev/lh/Food.java +++ b/src/main/dev/lh/Food.java @@ -5,6 +5,8 @@ import java.awt.Graphics2D; import java.awt.Rectangle; /** + * Represents a food item. + *

* Project: Snake
* File: Food.java
* Created: 01.07.2020
@@ -18,23 +20,35 @@ public final class Food implements Updateable { private final int lengthBonus; private final Rectangle bounds; + /** + * Constructs a food item. + * + * @param color the color of the food item + * @param lengthBonus the length added to the snake when the food item is eaten + * @param bounds the bounds of the food item + * @since Snake 1.1 + */ public Food(Color color, int lengthBonus, Rectangle bounds) { this.color = color; this.lengthBonus = lengthBonus; this.bounds = bounds; } - public void checkCollision(Snake snake) { - if (bounds.intersects(snake.getHead())) {} - } - @Override public void render(Graphics2D g) { g.setColor(color); g.fill(bounds); } + /** + * @return the length added to the snake when the food item is eaten + * @since Snake 1.1 + */ public int getLengthBonus() { return lengthBonus; } + /** + * @return the bounds of the food item + * @since Snake 1.1 + */ public Rectangle getBounds() { return bounds; } } diff --git a/src/main/dev/lh/FoodFactory.java b/src/main/dev/lh/FoodFactory.java index 7de2c2c..e8aaa18 100755 --- a/src/main/dev/lh/FoodFactory.java +++ b/src/main/dev/lh/FoodFactory.java @@ -55,8 +55,8 @@ public final class FoodFactory { FOOD_LENGTH_BONUSES[seed], new Rectangle(random.nextInt(width - 100) + 50, random.nextInt(height - 100) + 50, - seed * 10, - seed * 10 + 10 + seed * 5, + 10 + seed * 5 ) ); } diff --git a/src/main/dev/lh/Handler.java b/src/main/dev/lh/Handler.java index c1fe515..562ae0b 100644 --- a/src/main/dev/lh/Handler.java +++ b/src/main/dev/lh/Handler.java @@ -19,6 +19,13 @@ public final class Handler implements Updateable { private volatile Food food; + /** + * Constructs a handler. + * + * @param snake the snake + * @param foodFactory the food factory + * @since Snake 1.1 + */ public Handler(Snake snake, FoodFactory foodFactory) { this.snake = snake; this.foodFactory = foodFactory; @@ -29,6 +36,7 @@ public final class Handler implements Updateable { public void tick() { snake.tick(); food.tick(); + // Check for food collision if (snake.getHead().intersects(food.getBounds())) { snake.addLength(food.getLengthBonus()); diff --git a/src/main/dev/lh/Viewport.java b/src/main/dev/lh/Viewport.java index 9c5b6f2..943f3a7 100644 --- a/src/main/dev/lh/Viewport.java +++ b/src/main/dev/lh/Viewport.java @@ -60,7 +60,7 @@ public class Viewport extends Canvas { long time = System.currentTimeMillis(); double dt = (time - lastTime) * 1E-3; lastTime = time; - + // TODO: Delta time adjustment gameRoot.tick(); render(); } -- 2.45.2 From 04931dfd9919b088df5214c3e4a741ed87d1e00a Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 1 Jul 2020 18:16:59 +0000 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: delvh --- src/main/dev/lh/Food.java | 8 ++++---- src/main/dev/lh/FoodFactory.java | 6 +++--- src/main/dev/lh/Handler.java | 4 ++-- src/main/dev/lh/Snake.java | 4 ++-- src/main/dev/lh/Updateable.java | 2 +- src/main/dev/lh/Viewport.java | 8 ++++---- src/main/dev/lh/ui/Endscreen.java | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/dev/lh/Food.java b/src/main/dev/lh/Food.java index d47fa77..484e21c 100644 --- a/src/main/dev/lh/Food.java +++ b/src/main/dev/lh/Food.java @@ -12,7 +12,7 @@ import java.awt.Rectangle; * Created: 01.07.2020
* * @author Kai S. K. Engelbart - * @since Snake 1.1 + * @since Snake 1.2 */ public final class Food implements Updateable { @@ -26,7 +26,7 @@ public final class Food implements Updateable { * @param color the color of the food item * @param lengthBonus the length added to the snake when the food item is eaten * @param bounds the bounds of the food item - * @since Snake 1.1 + * @since Snake 1.2 */ public Food(Color color, int lengthBonus, Rectangle bounds) { this.color = color; @@ -42,13 +42,13 @@ public final class Food implements Updateable { /** * @return the length added to the snake when the food item is eaten - * @since Snake 1.1 + * @since Snake 1.2 */ public int getLengthBonus() { return lengthBonus; } /** * @return the bounds of the food item - * @since Snake 1.1 + * @since Snake 1.2 */ public Rectangle getBounds() { return bounds; } } diff --git a/src/main/dev/lh/FoodFactory.java b/src/main/dev/lh/FoodFactory.java index e8aaa18..8811273 100755 --- a/src/main/dev/lh/FoodFactory.java +++ b/src/main/dev/lh/FoodFactory.java @@ -36,7 +36,7 @@ public final class FoodFactory { * * @param width the width of the viewport * @param height the height of the viewport - * @since Snake 1.1 + * @since Snake 1.2 */ public FoodFactory(int width, int height) { this.width = width; @@ -45,7 +45,7 @@ public final class FoodFactory { /** * @return a new food item - * @since Snake 1.1 + * @since Snake 1.2 */ public synchronized Food spawn() { nextSpawnTime = System.currentTimeMillis() + random.nextInt(15000) + 1000; @@ -63,7 +63,7 @@ public final class FoodFactory { /** * @return the time after which a new food item should be spawned - * @since Snake 1.1 + * @since Snake 1.2 */ public long getNextSpawnTime() { return nextSpawnTime; } } diff --git a/src/main/dev/lh/Handler.java b/src/main/dev/lh/Handler.java index 562ae0b..9c00d10 100644 --- a/src/main/dev/lh/Handler.java +++ b/src/main/dev/lh/Handler.java @@ -10,7 +10,7 @@ import java.awt.Graphics2D; * Created: 01.07.2020
* * @author Kai S. K. Engelbart - * @since Snake 1.1 + * @since Snake 1.2 */ public final class Handler implements Updateable { @@ -24,7 +24,7 @@ public final class Handler implements Updateable { * * @param snake the snake * @param foodFactory the food factory - * @since Snake 1.1 + * @since Snake 1.2 */ public Handler(Snake snake, FoodFactory foodFactory) { this.snake = snake; diff --git a/src/main/dev/lh/Snake.java b/src/main/dev/lh/Snake.java index a774a9b..f0fa54d 100644 --- a/src/main/dev/lh/Snake.java +++ b/src/main/dev/lh/Snake.java @@ -147,7 +147,7 @@ public class Snake implements Updateable { /** * @return the current {@link Direction} of the snake - * @since Snake 1.0 + * @since Snake 1.2 */ public Direction getDirection() { return direction; } @@ -159,7 +159,7 @@ public class Snake implements Updateable { /** * @return a rectangle representing the head of the snake - * @since Snake 1.1 + * @since Snake 1.2 */ public Rectangle getHead() { return tiles.get(0); } } diff --git a/src/main/dev/lh/Updateable.java b/src/main/dev/lh/Updateable.java index 1daa80d..87b3f7d 100755 --- a/src/main/dev/lh/Updateable.java +++ b/src/main/dev/lh/Updateable.java @@ -18,7 +18,7 @@ public interface Updateable { * Here should the actions be implemented that are supposed to happen when a new * frame gets created. * - * @since Snake 1.0 + * @since Snake 1.2 */ default void tick() {} diff --git a/src/main/dev/lh/Viewport.java b/src/main/dev/lh/Viewport.java index 943f3a7..874c436 100644 --- a/src/main/dev/lh/Viewport.java +++ b/src/main/dev/lh/Viewport.java @@ -14,7 +14,7 @@ import java.util.TimerTask; * Created: 01.07.2020
* * @author Kai S. K. Engelbart - * @since Snake 1.0 + * @since Snake 1.2 */ public class Viewport extends Canvas { @@ -33,7 +33,7 @@ public class Viewport extends Canvas { /** * @param gameRoot the game object responsible for updating the rest - * @since Snake 1.0 + * @since Snake 1.2 */ public Viewport(Updateable gameRoot) { this.gameRoot = gameRoot; @@ -43,7 +43,7 @@ public class Viewport extends Canvas { /** * Starts the render task. * - * @since Snake 1.1 + * @since Snake 1.2 */ public void start() { if (renderTask != null) @@ -72,7 +72,7 @@ public class Viewport extends Canvas { /** * Stops the render task. * - * @since Snake 1.1 + * @since Snake 1.2 */ public void stop() { renderTask.cancel(); diff --git a/src/main/dev/lh/ui/Endscreen.java b/src/main/dev/lh/ui/Endscreen.java index 1087106..56fdc07 100644 --- a/src/main/dev/lh/ui/Endscreen.java +++ b/src/main/dev/lh/ui/Endscreen.java @@ -32,7 +32,7 @@ public class Endscreen extends JDialog { public Endscreen(int score) { this.score = score; setTitle("Endscreen"); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setBounds(100, 100, 700, 700); getContentPane().setLayout(new BorderLayout()); contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); -- 2.45.2