Add new pipeline and other fun stuff

This commit is contained in:
Kai S. K. Engelbart 2020-07-01 19:19:50 +02:00
parent a8908f7e13
commit 52a4c876fa
No known key found for this signature in database
GPG Key ID: 0A48559CA32CB48F
7 changed files with 272 additions and 248 deletions

40
src/main/dev/lh/Food.java Normal file
View File

@ -0,0 +1,40 @@
package dev.lh;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
/**
* Project: <strong>Snake</strong><br>
* File: <strong>Food.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @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; }
}

View File

@ -2,202 +2,68 @@ package dev.lh;
import static java.awt.Color.*; import static java.awt.Color.*;
import java.awt.*; import java.awt.Color;
import java.awt.Rectangle;
import java.util.Random; import java.util.Random;
import dev.lh.ui.GameWindow;
/** /**
* Generates food items with predefined properties at random positions and calculates the next
* spawning time.
* <p>
* Project: <strong>Snake</strong><br> * Project: <strong>Snake</strong><br>
* File: <strong>FoodFactory.java</strong><br> * File: <strong>FoodFactory.java</strong><br>
* Created: <strong>11 Mar 2020</strong><br> * Created: <strong>11 Mar 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @since Snake 1.0 * @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 * Initializes a food factory.
* of an element, the less it is worth.<br>
* <br>
* Project: <strong>Snake</strong><br>
* File: <strong>FoodFactory.java</strong><br>
* Created: <strong>11 Mar 2020</strong><br>
* *
* @author Leon Hofmeister * @param width the width of the viewport
* @since Snake 1.0 * @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. * @return a new food item
* @since Snake 1.1
*/ */
white( public synchronized Food spawn() {
WHITE, 40 nextSpawnTime = System.currentTimeMillis() + random.nextInt(15000) + 1000;
), int seed = random.nextInt(5);
return new Food(
/** FOOD_COLORS[seed],
* Use if yellow food is wanted. FOOD_LENGTH_BONUSES[seed],
*/ new Rectangle(random.nextInt(width - 100) + 50,
yellow( random.nextInt(height - 100) + 50,
YELLOW, 15 seed * 10,
), seed * 10
)
/**
* 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
); );
/**
* 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 * @return the time after which a new food item should be spawned
* will be constructed. * @since Snake 1.1
*
* @since Snake 1.0
*/ */
public void setTimeToNextFoodMillis() { public long getNextSpawnTime() { return nextSpawnTime; }
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;
}
} }

View File

@ -0,0 +1,46 @@
package dev.lh;
import java.awt.Graphics2D;
/**
* Manages the state of game objects.
* <p>
* Project: <strong>Snake</strong><br>
* File: <strong>Handler.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @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);
}
}

View File

@ -7,7 +7,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import dev.lh.ui.Endscreen; import dev.lh.ui.Endscreen;
import dev.lh.ui.GameWindow;
/** /**
* Project: <strong>Snake</strong><br> * Project: <strong>Snake</strong><br>
@ -51,7 +50,6 @@ public class Snake implements Updateable {
DOWN; DOWN;
} }
private static FoodFactory foodFactory = FoodFactory.getInstance();
private static Endscreen endscreen; private static Endscreen endscreen;
private Direction direction = Direction.RIGHT; private Direction direction = Direction.RIGHT;
private int length; private int length;
@ -103,14 +101,8 @@ public class Snake implements Updateable {
Main.getGame().close(); Main.getGame().close();
} }
/**
* @return the current {@link Direction} of the snake
* @since Snake 1.0
*/
public Direction getRichtung() { return direction; }
@Override @Override
public void nextFrame() { public void tick() {
int velX = 0, velY = 0; int velX = 0, velY = 0;
switch (direction) { switch (direction) {
case UP: case UP:
@ -140,18 +132,11 @@ public class Snake implements Updateable {
return; return;
} }
// TODO: Test on Linux // TODO: Test on Linux
if (!Main.getGame().getBounds().contains(tiles.get(0))) { if (!Main.getGame().getBounds().contains(getHead())) {
gameOver(); gameOver();
System.out.println("Snake went out of bounds."); System.out.println("Snake went out of bounds.");
return; 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 @Override
@ -160,9 +145,21 @@ public class Snake implements Updateable {
tiles.forEach(g::fill); 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 * @param direction the new {@link Direction} of the snake
* @since Snake 1.0 * @since Snake 1.0
*/ */
public void setDirection(Direction direction) { this.direction = direction; } 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); }
} }

View File

@ -20,7 +20,7 @@ public interface Updateable {
* *
* @since Snake 1.0 * @since Snake 1.0
*/ */
void nextFrame(); default void tick() {}
/** /**
* Renders the object. * Renders the object.
@ -28,5 +28,5 @@ public interface Updateable {
* @param g the 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 * @since Snake 1.0
*/ */
void render(Graphics2D g); default void render(Graphics2D g) {}
} }

View File

@ -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.
* <p>
* Project: <strong>Snake</strong><br>
* File: <strong>Viewport.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @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();
}
}

View File

@ -1,15 +1,14 @@
package dev.lh.ui; 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.KeyAdapter;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import dev.lh.FoodFactory; import dev.lh.*;
import dev.lh.Snake;
import dev.lh.Snake.Direction; import dev.lh.Snake.Direction;
/** /**
@ -23,20 +22,19 @@ import dev.lh.Snake.Direction;
public class GameWindow extends JFrame { public class GameWindow extends JFrame {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Snake s = new Snake(7);
private FoodFactory foodFactory = FoodFactory.getInstance(); private Viewport viewport;
private Timer timer;
/** /**
* @param title the title of the frame * @param title the title of the frame
* @since Snake 1.0 * @since Snake 1.0
*/ */
public GameWindow(String title) { public GameWindow(String title) {
// Initialize window
super(title); super(title);
Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
setBounds(new Rectangle(size)); setBounds(new Rectangle(size));
setLocation(0, 0); setLocation(0, 0);
setLocationRelativeTo(null);
setMinimumSize(size); setMinimumSize(size);
setPreferredSize(size); setPreferredSize(size);
setMaximumSize(size); setMaximumSize(size);
@ -44,19 +42,14 @@ public class GameWindow extends JFrame {
setResizable(false); setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE); 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; // Initialize viewport
viewport = new Viewport(handler);
@Override add(viewport);
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);
}
});
addKeyListener(new KeyAdapter() { addKeyListener(new KeyAdapter() {
@ -66,56 +59,40 @@ public class GameWindow extends JFrame {
switch (e.getKeyCode()) { switch (e.getKeyCode()) {
case KeyEvent.VK_W: case KeyEvent.VK_W:
case KeyEvent.VK_UP: 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; break;
case KeyEvent.VK_A: case KeyEvent.VK_A:
case KeyEvent.VK_LEFT: 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; break;
case KeyEvent.VK_S: case KeyEvent.VK_S:
case KeyEvent.VK_DOWN: 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; break;
case KeyEvent.VK_D: case KeyEvent.VK_D:
case KeyEvent.VK_RIGHT: 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; break;
} }
} }
}); });
newFood();
timer = new Timer(
50,
evt -> {
s.nextFrame();
if (System.currentTimeMillis() >= foodFactory.getTimeOfNextFood())
newFood();
repaint();
}
);
timer.start();
setVisible(true); setVisible(true);
viewport.start();
} }
/** /**
* Generates new food. * Disposes this frame.
*
* @since Snake 1.1
*/
public void newFood() {
foodFactory.generateFood();
foodFactory.generateFoodLocation(getWidth(), getHeight());
}
/**
* Disposes this frame
* *
* @since Snake 1.1 * @since Snake 1.1
*/ */
public void close() { public void close() {
timer.stop(); viewport.stop();
setVisible(false);
dispose(); dispose();
} }
} }