Commit 1a605f66 authored by Rick van der Wal's avatar Rick van der Wal
Browse files

Added test assignments for snake

parent 1719269d
package snake;
public enum Direction {
UP(0, -1),
RIGHT(1, 0),
DOWN(0, 1),
LEFT(-1, 0);
private final int dX, dY;
private Direction(int dX, int dY) {
this.dX = dX;
this.dY = dY;
}
public int getDX() {
return dX;
}
public int getDY() {
return dY;
}
public Direction rotateLeft() {
switch(this) {
case UP: return LEFT;
case LEFT: return DOWN;
case DOWN: return RIGHT;
default: return UP;
}
}
public Direction rotateRight() {
switch(this) {
case UP: return RIGHT;
case RIGHT: return DOWN;
case DOWN: return LEFT;
default: return UP;
}
}
}
package snake;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Food {
private final IntegerProperty x = new SimpleIntegerProperty(), y = new SimpleIntegerProperty();
public void moveTo(int x, int y) {
this.x.set(x);
this.y.set(y);
}
public int getX() {
return x.get();
}
public int getY() {
return y.get();
}
public IntegerProperty getXProperty() {
return x;
}
public IntegerProperty getYProperty() {
return y;
}
}
package snake;
import javafx.event.EventHandler;
import javafx.scene.input.KeyEvent;
/**
* Handles controls of a snake game, where the 'a' and 'd' keys can be used to move and 's' (un)pauses the game
*/
public class InputHandler implements EventHandler<KeyEvent> {
private final World world;
private final Snake snake;
public InputHandler(World world) {
this.world = world;
snake = world.getSnake();
}
@Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCode()) {
case A:
snake.setDirection(snake.getDirection().rotateLeft());
break;
case D:
snake.setDirection(snake.getDirection().rotateRight());
break;
case S:
world.setRunning(!world.isRunning());
break;
}
keyEvent.consume();
}
}
package snake;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
World world = new World(25);
BorderPane root = new BorderPane();
SnakeGame game = new SnakeGame(world);
Pane ui = SnakeGame.createUserInterface(world);
game.setStyle("-fx-background-color: #30B080;");
ui.setPadding(new Insets(10));
root.setLeft(game);
root.setRight(ui);
Scene scene = new Scene(root);
scene.setOnKeyPressed(new InputHandler(world));
primaryStage.setTitle("Snake");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package snake;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
/**
* Represents one body part of a snake
*/
public class Segment {
private final IntegerProperty x, y;
public Segment(int x, int y) {
this.x = new SimpleIntegerProperty(x);
this.y = new SimpleIntegerProperty(y);
}
public void setPosition(int x, int y) {
this.x.setValue(x);
this.y.setValue(y);
}
public int getX() {
return x.get();
}
public int getY() {
return y.get();
}
public IntegerProperty getXProperty() {
return x;
}
public IntegerProperty getYProperty() {
return y;
}
}
package snake;
import java.util.LinkedList;
import java.util.List;
/**
* Snake consists of segments, where this head segment keeps track of the other body segments
*/
public class Snake extends Segment {
public interface SnakeSegmentListener {
void onNewSegment(Segment segment);
}
private Direction direction = Direction.RIGHT;
private final World world;
private final List<Segment> body = new LinkedList<>();
private final List<SnakeSegmentListener> listeners = new LinkedList<>();
public Snake(int x, int y, World world) {
super(x, y);
this.world = world;
}
public void move() {
int newX = getX() + direction.getDX();
int newY = getY() + direction.getDY();
if (isAt(newX, newY) || newX < 0 || newY < 0 || newX >= world.getSize() || newY >= world.getSize()) {
// Bitten itself or the border, game over
world.endGame();
} else {
Food food = world.getFood();
if (food.getX() == newX && food.getY() == newY) {
// Eating fruit, increment score and add new segment before the head
world.setScore(world.getScore() + 1);
world.moveFoodRandomly();
Segment segment = new Segment(getX(), getY());
for (SnakeSegmentListener listener : listeners) {
listener.onNewSegment(segment);
}
body.add(segment);
} else {
// Moving normally, recycle tail and move it before head
if (!body.isEmpty()) {
Segment tail = body.remove(0);
body.add(tail);
tail.setPosition(getX(), getY());
}
}
// Move head to new location
setPosition(newX, newY);
}
}
public void addListener(SnakeSegmentListener listener) {
listeners.add(listener);
}
public void setDirection(Direction newDirection) {
direction = newDirection;
}
public boolean isAt(int x, int y) {
for (Segment segment : body) {
if (segment.getX() == x && segment.getY() == y) {
return true;
}
}
return false;
}
public Direction getDirection() {
return direction;
}
}
package snake;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
/**
* A JavaFX Pane that displays the snake game represented by the given world
*/
public class SnakeGame extends Pane {
public static final int SCALE = 16;
public SnakeGame(World world) {
setPrefSize(world.getSize() * SCALE, world.getSize() * SCALE);
// Snake
Snake snake = world.getSnake();
Rectangle head = new Rectangle(SCALE, SCALE, Color.RED);
head.translateXProperty().bind(snake.getXProperty().multiply(SCALE));
head.translateYProperty().bind(snake.getYProperty().multiply(SCALE));
getChildren().add(head);
snake.addListener(segment -> {
Rectangle body = new Rectangle(SCALE, SCALE, Color.GREEN);
body.translateXProperty().bind(segment.getXProperty().multiply(SCALE));
body.translateYProperty().bind(segment.getYProperty().multiply(SCALE));
getChildren().add(body);
});
// Food
Food food = world.getFood();
Circle circle = new Circle(SCALE / 2F, Color.BLUE);
circle.translateXProperty().bind(food.getXProperty().multiply(SCALE).add(SCALE / 2F));
circle.translateYProperty().bind(food.getYProperty().multiply(SCALE).add(SCALE / 2F));
getChildren().add(circle);
}
public static Pane createUserInterface(World world) {
VBox ui = new VBox();
Label scoreText = new Label();
Label runningText = new Label("Press 's' to start");
scoreText.textProperty().bind(world.getScoreProperty().asString("%d points"));
world.getRunningProperty().addListener((observableValue, aBoolean, t1) -> {
if (t1) {
runningText.textProperty().set("");
} else {
runningText.textProperty().set("Paused");
}
});
ui.getChildren().addAll(scoreText, runningText);
return ui;
}
}
package snake;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.util.Duration;
import java.util.Random;
/**
* World keeps track of the state of a snake game
*/
public class World {
public final static int DELAY = 200;
private final int size;
private final Snake snake;
private final Food food;
private final Random random = new Random();
private final BooleanProperty running = new SimpleBooleanProperty(false);
private final IntegerProperty score = new SimpleIntegerProperty(0);
public World(int size) {
this.size = size;
snake = new Snake(size / 2, size / 2, this);
food = new Food();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(DELAY), e -> snake.move()));
running.addListener((observableValue, aBoolean, t1) -> {
if (t1) {
timeline.play();
} else {
timeline.pause();
}
});
timeline.setCycleCount(Animation.INDEFINITE);
moveFoodRandomly();
}
public void moveFoodRandomly() {
do {
food.moveTo(random.nextInt(size), random.nextInt(size));
} while (snake.isAt(food.getX(), food.getY()));
}
public void endGame() {
running.set(false);
}
public void setRunning(boolean running) {
this.running.set(running);
}
public void setScore(int score) {
this.score.set(score);
}
public boolean isRunning() {
return running.get();
}
public int getSize() {
return size;
}
public int getScore() {
return score.get();
}
public Snake getSnake() {
return snake;
}
public Food getFood() {
return food;
}
public BooleanProperty getRunningProperty() {
return running;
}
public IntegerProperty getScoreProperty() {
return score;
}
}
package snake;
public enum Direction {
UP(0, -1),
RIGHT(1, 0),
DOWN(0, 1),
LEFT(-1, 0);
private final int dX, dY;
private Direction(int dX, int dY) {
this.dX = dX;
this.dY = dY;
}
public int getDX() {
return dX;
}
public int getDY() {
return dY;
}
public Direction rotateLeft() {
switch(this) {
case UP: return LEFT;
case LEFT: return DOWN;
case DOWN: return RIGHT;
default: return UP;
}
}
public Direction rotateRight() {
switch(this) {
case UP: return RIGHT;
case RIGHT: return DOWN;
case DOWN: return LEFT;
default: return UP;
}
}
}
package snake;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.shape.Circle;
public class Food extends Circle {
private final IntegerProperty x = new SimpleIntegerProperty(), y = new SimpleIntegerProperty();
public void moveTo(int x, int y) {
this.x.set(x);
this.y.set(y);
}
public int getFoodX() {
return x.get();
}
public int getFoodY() {
return y.get();
}
public IntegerProperty getXProperty() {
return x;
}
public IntegerProperty getYProperty() {
return y;
}
}
package snake;
import javafx.event.EventHandler;
import javafx.scene.input.KeyEvent;
/**
* Handles controls of a snake game, where the 'a' and 'd' keys can be used to move and 's' (un)pauses the game
*/
public class InputHandler implements EventHandler<KeyEvent> {
private final World world;
private final Snake snake;
public InputHandler(World world) {
this.world = world;
snake = world.getSnake();
}
@Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCode()) {
case A:
snake.setDirection(snake.getDirection().rotateLeft());
break;
case D:
snake.setDirection(snake.getDirection().rotateRight());
break;
case S:
world.setRunning(!world.isRunning());
break;
}
keyEvent.consume();
}
}
package snake;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
World world = new World(25);
BorderPane root = new BorderPane();
SnakeGame game = new SnakeGame(world);
Pane ui = SnakeGame.createUserInterface(world);
game.setStyle("-fx-background-color: #30B080;");
ui.setPadding(new Insets(10));
root.setLeft(game);
root.setRight(ui);
Scene scene = new Scene(root);
scene.setOnKeyPressed(new InputHandler(world));
primaryStage<