Compare commits

...

16 Commits

Author SHA1 Message Date
delvh
3fe0bb72da
Update README.md 2020-07-07 21:08:28 +02:00
delvh
f7fbc8b3e4 Fixed text on Startscreen 2020-07-01 21:10:48 +02:00
delvh
daf55abd93 Commented any usage of images out 2020-07-01 20:58:14 +02:00
delvh
b584589459 Fixed issue with wrong WindowConstant 2020-07-01 20:31:55 +02:00
a039837e89
Merge pull request #5 from delvh/feature/optimized_pipeline
Optimized Ticking and Rendering
2020-07-01 18:17:13 +00:00
04931dfd99
Apply suggestions from code review
Co-authored-by: delvh <dev.lh@web.de>
2020-07-01 18:16:59 +00:00
7c55e049cf
Add ol' JDoc 2020-07-01 19:29:52 +02:00
52a4c876fa
Add new pipeline and other fun stuff 2020-07-01 19:19:50 +02:00
a8908f7e13
Simplify snake drawing and movement calculation 2020-07-01 16:18:59 +02:00
5651ea76b0
Remove some try statements and switches 2020-07-01 16:01:37 +02:00
a0b0c09e1d
Merge pull request #4 from delvh/feature/simplification
Simplified some calculations
2020-07-01 12:38:31 +00:00
7ca0a14ea2
Simplify FoodFactory#generateFoodLocation 2020-07-01 14:33:23 +02:00
9131d0e2fd
Simplify FoodFactory#generateFood 2020-07-01 14:29:00 +02:00
c7035ff2c5
Simplified some calculations 2020-06-30 19:29:50 +02:00
ad329447a9
Fix missing ImageIcon for PlayButton and repeated EndScreen Spawning (#3)
* Fix multiple EndScreen Instances spawning

* Removed `Play Again` icon

Co-authored-by: delvh <dev.lh@web.de>
2020-06-26 16:54:31 +02:00
delvh
ab584c187d Provisorically fixed bug resulting in an empty Endscreen
Still, the screen neither shows the Labels or the images
2020-06-18 13:08:57 +02:00
10 changed files with 406 additions and 426 deletions

View File

@ -2,7 +2,7 @@
My advanced Hello World program is a version of the old game Snake. My advanced Hello World program is a version of the old game Snake.
## Features ## Features
- normal playing experience of Snake - normal playing experience of Snake but hardware-accelerated
- 5 different fruits that can be eaten to enlarge the Snake - 5 different fruits that can be eaten to enlarge the Snake
- theoretically Start- and Endscreen, currently mostly unused - theoretically Start- and Endscreen, currently mostly unused
- theoretically visual display whether a result is good or bad, commented out for now as it does not appear to work as expected - theoretically visual display whether a result is good or bad, commented out for now as it does not appear to work as expected
@ -23,4 +23,4 @@ My advanced Hello World program is a version of the old game Snake.
- as the name suggests, defines the visual appearance before and after games - as the name suggests, defines the visual appearance before and after games
## JAR of the final version ## JAR of the final version
If you follow <a href="https://github.com/delvh/Snake/releases">**this link**</a> and click on release "Snake - almost finished", you can download the runnable JAR of how the completed project should look like. Unfortunately due to poor Version-Control-skills, the original code got lost and had to be rewritten, however it should resemble the executed code closely If you follow <a href="https://github.com/delvh/Snake/releases">**this link**</a> and click on release "Snake - Rattlesnake version", you can download the runnable JAR of how the project currently looks like.

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

@ -0,0 +1,54 @@
package dev.lh;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
/**
* Represents a food item.
* <p>
* 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.2
*/
public final class Food implements Updateable {
private final Color color;
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.2
*/
public Food(Color color, int lengthBonus, Rectangle bounds) {
this.color = color;
this.lengthBonus = lengthBonus;
this.bounds = bounds;
}
@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.2
*/
public int getLengthBonus() { return lengthBonus; }
/**
* @return the bounds of the food item
* @since Snake 1.2
*/
public Rectangle getBounds() { return bounds; }
}

View File

@ -1,229 +1,69 @@
package dev.lh; package dev.lh;
import java.awt.*; import static java.awt.Color.*;
import dev.lh.ui.GameWindow; import java.awt.Color;
import java.awt.Rectangle;
import java.util.Random;
/** /**
* 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.2
*/ */
public static enum Food { public FoodFactory(int width, int height) {
/** this.width = width;
* Use if white food is wanted. this.height = height;
*/
white,
/**
* Use if yellow food is wanted.
*/
yellow,
/**
* Use if orange food is wanted.
*/
orange,
/**
* Use if red food is wanted.
*/
red,
/**
* Use if blue food is wanted.
*/
blue
}
private static FoodFactory foodFactory = new FoodFactory();
private long timeOfNextFood;
Point pFood = null;
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() {
int nextFoodIs = (int) Math.floor(Math.random() * 5);
switch (nextFoodIs) {
case 0:
nextFood = Food.white;
break;
case 1:
nextFood = Food.yellow;
break;
case 2:
nextFood = Food.orange;
break;
case 3:
nextFood = Food.red;
break;
case 4:
nextFood = Food.blue;
break;
default:
nextFood = generateFood();
}
rectangleSize = nextFood.ordinal() + 2;
setTimeToNextFoodMillis();
return nextFood;
} }
/** /**
* Generates the amount of time that needs to pass before the next food object * @return a new food item
* will be constructed. * @since Snake 1.2
*
* @since Snake 1.0
*/ */
public void setTimeToNextFoodMillis() { timeOfNextFood = System.currentTimeMillis() + (int) Math.round(Math.random() * 15000 + 1000); } public synchronized Food spawn() {
nextSpawnTime = System.currentTimeMillis() + random.nextInt(15000) + 1000;
/** int seed = random.nextInt(5);
* @return the type of the next food return new Food(
* @since Snake 1.0 FOOD_COLORS[seed],
*/ FOOD_LENGTH_BONUSES[seed],
public Food getNextFood() { return nextFood; } new Rectangle(random.nextInt(width - 100) + 50,
random.nextInt(height - 100) + 50,
/** 10 + seed * 5,
* @param nextFood the type the next food should have 10 + seed * 5
* @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) {
pFood = new Point((int) Math.round(Math.random() * width), (int) Math.round(Math.random() * height));
if (pFood.x < 50 || pFood.x > width - 50 || pFood.y < 50 || pFood.y > height - 50) {
pFood.x = (pFood.x < 50) ? 50 : (pFood.x > width - 50) ? width - 50 : pFood.x;
pFood.y = (pFood.y < 50) ? 50 : (pFood.y > height - 50) ? height - 50 : pFood.y;
}
return pFood;
} }
/** /**
* @return the size of the corresponding food (length = width) * @return the time after which a new food item should be spawned
* @since Snake 1.0 * @since Snake 1.2
*/ */
public int getRectangleSize() { return rectangleSize; } public long getNextSpawnTime() { return nextSpawnTime; }
/**
* @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) {
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;
}// switch
}
/**
* @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 snakehead
* @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() {
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;
}
} }

View File

@ -0,0 +1,54 @@
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.2
*/
public final class Handler implements Updateable {
private Snake snake = new Snake(7);
private FoodFactory foodFactory;
private volatile Food food;
/**
* Constructs a handler.
*
* @param snake the snake
* @param foodFactory the food factory
* @since Snake 1.2
*/
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);
}
}

160
src/main/dev/lh/Snake.java Executable file → Normal file
View File

@ -1,14 +1,12 @@
package dev.lh; package dev.lh;
import java.awt.Color; import java.awt.Color;
import java.awt.Graphics; import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.util.ArrayList; 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>
@ -30,34 +28,34 @@ public class Snake implements Updateable {
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Snake 1.0 * @since Snake 1.0
*/ */
public static enum Direction { public enum Direction {
/** /**
* Use if the snake should head left. * Use if the snake should head left.
*/ */
Left, LEFT,
/** /**
* Use if the snake should head right. * Use if the snake should head right.
*/ */
Right, RIGHT,
/** /**
* Use if the snake should head up. * Use if the snake should head up.
*/ */
Up, UP,
/** /**
* Use if the snake should head down. * Use if the snake should head down.
*/ */
Down; DOWN;
} }
private static FoodFactory foodFactory = FoodFactory.getInstance();
private static Endscreen endscreen; private static Endscreen endscreen;
private Direction Richtung; private Direction direction = Direction.RIGHT;
private int length; private int length;
private List<Point> tiles = new ArrayList<>(); private final List<Rectangle> tiles = new ArrayList<>();
private final int snakeSize = 10;
private static final int TILE_SIZE = 10;
/** /**
* Constructs a new Snake with the given length in tiles. * Constructs a new Snake with the given length in tiles.
@ -67,60 +65,23 @@ public class Snake implements Updateable {
*/ */
public Snake(int length) { public Snake(int length) {
this.length = length; this.length = length;
Richtung = Direction.Right;
// adding the initial tiles of the snake // Add initial tiles
for (int i = 0; i < length; i++) 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));
}
@Override
public void nextFrame() {
int velX = 0, velY = 0;
switch (Richtung) {
case Up:
velY = -snakeSize;
break;
case Down:
velY = snakeSize;
break;
case Left:
velX = -snakeSize;
break;
case Right:
velX = snakeSize;
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
if (checkSelfCollision()) gameOver();
// TODO: the game bounds checking below appears to work on Windows, however throws a NullPointerException on Linux/UNIX systems
// if (!Main.getGame().getBounds().contains(tiles.get(0))) gameOver();
// case if snake eats food
if (foodFactory.checkCollision(new Rectangle(tiles.get(0).x, tiles.get(0).y, snakeSize, snakeSize))) {
addLength(foodFactory.getAdditionalLength());
GameWindow game = Main.getGame();
game.newFood();
}
} }
/** /**
* Increases the given length to the current snake object.
* *
* @since Snake 1.1 * @param additional the number of tiles to add
* @since Snake 1.0
*/ */
private void gameOver() { public void addLength(int additional) {
endscreen = new Endscreen(length); final Rectangle last = tiles.get(tiles.size() - 1);
endscreen.setVisible(true); for (int i = 0; i < additional; i++)
Main.getGame().close(); tiles.add(last);
length += additional;
} }
/** /**
@ -128,44 +89,77 @@ public class Snake implements Updateable {
* @since Snake 1.1 * @since Snake 1.1
*/ */
private boolean checkSelfCollision() { private boolean checkSelfCollision() {
Point headIndex = tiles.get(0); return tiles.stream().skip(1).anyMatch(tiles.get(0)::contains);
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;
/**
* @since Snake 1.1
*/
private void gameOver() {
Main.getGame().close();
endscreen = new Endscreen(length);
endscreen.setVisible(true);
} }
@Override @Override
public void render(Graphics g) { public void tick() {
int velX = 0, velY = 0;
switch (direction) {
case UP:
velY = -TILE_SIZE;
break;
case DOWN:
velY = TILE_SIZE;
break;
case LEFT:
velX = -TILE_SIZE;
break;
case RIGHT:
velX = TILE_SIZE;
break;
}
// 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: Test on Linux
if (!Main.getGame().getBounds().contains(getHead())) {
gameOver();
System.out.println("Snake went out of bounds.");
return;
}
}
@Override
public void render(Graphics2D g) {
g.setColor(Color.green); g.setColor(Color.green);
for (int i = 0; i < length; i++) tiles.forEach(g::fill);
g.fillRect(tiles.get(i).x, tiles.get(i).y, snakeSize, snakeSize);
} }
/** /**
* @return the current {@link Direction} of the snake * @return the current {@link Direction} of the snake
* @since Snake 1.0 * @since Snake 1.2
*/ */
public Direction getRichtung() { return Richtung; } public Direction getDirection() { return direction; }
/** /**
* @param richtung 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 setRichtung(Direction richtung) { Richtung = richtung; } public void setDirection(Direction direction) { this.direction = direction; }
/** /**
* Adds the given length to the current snake object * @return a rectangle representing the head of the snake
* * @since Snake 1.2
* @param additional the number of tiles to add
* @since Snake 1.0
*/ */
public void addLength(int additional) { public Rectangle getHead() { return tiles.get(0); }
Point last = tiles.get(tiles.size() - 1);
for (int i = 0; i < additional; i++)
tiles.add(last);
length += additional;
}
} }

View File

@ -1,10 +1,10 @@
package dev.lh; package dev.lh;
import java.awt.Graphics; import java.awt.Graphics2D;
/** /**
* This interface contains everything that needs to updated regularly.<br> * This interface contains everything that needs to be updated regularly.
* <br> * <p>
* Project: <strong>Snake</strong><br> * Project: <strong>Snake</strong><br>
* File: <strong>Updateable.java</strong><br> * File: <strong>Updateable.java</strong><br>
* Created: <strong>11 Mar 2020</strong><br> * Created: <strong>11 Mar 2020</strong><br>
@ -18,15 +18,15 @@ public interface Updateable {
* Here should the actions be implemented that are supposed to happen when a new * Here should the actions be implemented that are supposed to happen when a new
* frame gets created. * frame gets created.
* *
* @since Snake 1.0 * @since Snake 1.2
*/ */
void nextFrame(); default void tick() {}
/** /**
* Renders the object. * 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 * @since Snake 1.0
*/ */
void render(Graphics g); default void render(Graphics2D g) {}
} }

View File

@ -0,0 +1,94 @@
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.2
*/
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.2
*/
public Viewport(Updateable gameRoot) {
this.gameRoot = gameRoot;
setIgnoreRepaint(true);
}
/**
* Starts the render task.
*
* @since Snake 1.2
*/
public void start() {
if (renderTask != null) renderTask.cancel();
else createBufferStrategy(2);
renderTask = new TimerTask() {
// private long lastTime = System.currentTimeMillis();
@Override
public void run() {
// final long time = System.currentTimeMillis();
// final double dt = (time - lastTime) * 1E-3;
// lastTime = time;
// TODO: Delta time adjustment
gameRoot.tick();
render();
}
};
timer.schedule(renderTask, 0, 100);
}
/**
* Stops the render task.
*
* @since Snake 1.2
*/
public void stop() { renderTask.cancel(); }
private void render() {
final 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();
}
}

48
src/main/dev/lh/ui/Endscreen.java Executable file → Normal file
View File

@ -1,6 +1,7 @@
package dev.lh.ui; package dev.lh.ui;
import java.awt.*; import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import javax.swing.*; import javax.swing.*;
@ -20,9 +21,9 @@ public class Endscreen extends JDialog {
private static final long serialVersionUID = -4457484397259161063L; private static final long serialVersionUID = -4457484397259161063L;
// private static final int goodOrBadResult = 200;
private final JPanel contentPanel = new JPanel(); private final JPanel contentPanel = new JPanel();
private int score = 0; private final int score;
private static final int goodOrBadResult = 200;
/** /**
* Create the dialog. * Create the dialog.
@ -31,49 +32,28 @@ public class Endscreen extends JDialog {
*/ */
public Endscreen(int score) { public Endscreen(int score) {
this.score = score; this.score = score;
try {
// readInHighscoresPoints();
// readInHighscoresPlayers();
setTitle("Endscreen"); setTitle("Endscreen");
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setBounds(100, 100, 700, 700); setBounds(100, 100, 700, 700);
getContentPane().setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout());
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPanel.setLayout(new BorderLayout(0, 0));
getContentPane().add(contentPanel, BorderLayout.CENTER); getContentPane().add(contentPanel, BorderLayout.CENTER);
addWindowListener(new java.awt.event.WindowAdapter() {
@Override final JButton btnNewButton = new JButton("Play again");
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
Thread.getAllStackTraces().forEach((thread, stackTraceElement) -> thread.interrupt());
System.exit(0);
}
});
} catch (Exception e) {
e.printStackTrace();
}
JButton btnNewButton = new JButton("Play again");
btnNewButton.setMnemonic(KeyEvent.VK_ENTER); btnNewButton.setMnemonic(KeyEvent.VK_ENTER);
btnNewButton.addActionListener(e -> { Main.startGame(); dispose(); }); btnNewButton.addActionListener(e -> { Main.startGame(); dispose(); });
contentPanel.setLayout(new BorderLayout(0, 0));
// btnNewButton.setIcon(new
// ImageIcon(ClassLoader.getSystemResource("/com/sun/javafx/webkit/prism/resources/mediaPlayDisabled.png")));
btnNewButton.setIconTextGap(5);
btnNewButton.setFont(new Font("Times New Roman", Font.PLAIN, 15)); btnNewButton.setFont(new Font("Times New Roman", Font.PLAIN, 15));
contentPanel.add(btnNewButton, BorderLayout.SOUTH); contentPanel.add(btnNewButton, BorderLayout.SOUTH);
JLabel lblDeinPunktestand = new JLabel("Dein Punktestand: " + String.valueOf(score)); final JLabel lblDeinPunktestand = new JLabel("Dein Punktestand: " + String.valueOf(score));
lblDeinPunktestand.setFont(new Font("Times New Roman", Font.PLAIN, 25)); lblDeinPunktestand.setFont(new Font("Times New Roman", Font.PLAIN, 25));
contentPanel.add(lblDeinPunktestand, BorderLayout.NORTH); contentPanel.add(lblDeinPunktestand, BorderLayout.NORTH);
//TODO: the display ofthe result image could work, but not guaranteed // final Image resultImage = Toolkit.getDefaultToolkit()
// Image resultImage = Toolkit.getDefaultToolkit() // .getImage(this.getClass().getResource(score < goodOrBadResult ?
// .getImage(this.getClass() // "/Try_Again.jpg" : "/1211548-200.png"));
// .getResource((score < goodOrBadResult) ? "/Snake/src/main/resources/Try_Again.jpg" : "/Snake/src/main/resources/1211548-200.png"));
// resultImage.flush(); // resultImage.flush();
setVisible(true);
} }
/** /**
@ -81,10 +61,4 @@ public class Endscreen extends JDialog {
* @since Snake 1.0 * @since Snake 1.0
*/ */
public int getScore() { return score; } public int getScore() { return score; }
/**
* @param score the new highscore
* @since Snake 1.0
*/
public void setScore(int score) { this.score = score; }
} }

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;
/** /**
* @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);
newFood();
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, super.getWidth(), super.getHeight());
s.render(g);
foodFactory.paintFood(g);
}
});
addKeyListener(new KeyAdapter() { addKeyListener(new KeyAdapter() {
@ -66,46 +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.setRichtung(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.setRichtung(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.setRichtung(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.setRichtung(Direction.Right); if (!snake.getDirection().equals(Direction.LEFT))
snake.setDirection(Direction.RIGHT);
break; break;
} }
} }
}); });
Timer 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 * @since Snake 1.1
*/ */
public void newFood() { public void close() {
foodFactory.generateFood(); viewport.stop();
foodFactory.generateFoodLocation(getWidth(), getHeight()); setVisible(false);
repaint(); dispose();
} }
/**
* Disposes this frame
*
* @since Snake 1.1
*/
public void close() { dispose(); }
} }

View File

@ -20,10 +20,9 @@ import dev.lh.Main;
public class StartScreen extends JFrame { public class StartScreen extends JFrame {
private static final long serialVersionUID = 6055940532003735543L; private static final long serialVersionUID = 6055940532003735543L;
private JPanel contentPane;
/** /**
* closes the application. * Closes the application.
*/ */
public static void close() { System.exit(0); } public static void close() { System.exit(0); }
@ -33,48 +32,32 @@ public class StartScreen extends JFrame {
* @param args the program arguments * @param args the program arguments
* @since Snake 1.0 * @since Snake 1.0
*/ */
public static void main(String[] args) { public static void main(String[] args) { EventQueue.invokeLater(StartScreen::new); }
EventQueue.invokeLater(() -> {
try {
StartScreen frame = new StartScreen();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
});
}
/** /**
* Create the frame. * Create the frame.
*/ */
public StartScreen() { public StartScreen() {
try {
// readInHighscores();
setTitle("Snake - Startscreen"); setTitle("Snake - Startscreen");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(500, 200, 550, 550); setBounds(500, 200, 550, 550);
contentPane = new JPanel();
final JPanel contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane); setContentPane(contentPane);
JButton buPlay = new JButton("Start Game"); final JButton buPlay = new JButton("Start Game");
buPlay.setBounds(158, 197, 190, 131); buPlay.setBounds(158, 197, 190, 131);
buPlay.setIcon(new ImageIcon(StartScreen.class.getResource("/com/sun/javafx/webkit/prism/resources/mediaPlayDisabled.png")));
buPlay.setMnemonic(KeyEvent.VK_ENTER); buPlay.setMnemonic(KeyEvent.VK_ENTER);
buPlay.setFont(new Font("Times New Roman", Font.PLAIN, 16)); buPlay.setFont(new Font("Times New Roman", Font.PLAIN, 16));
buPlay.addActionListener(a -> { buPlay.addActionListener(a -> {
Main.startGame(); Main.startGame();
setVisible(false); setVisible(false);
dispose(); dispose();
System.gc(); System.gc();
}); });
contentPane.add(buPlay);
contentPane.setLayout(null); contentPane.setLayout(null);
setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
} }
} }