From 7ceadb7ebd02f79c663923285772f8b9ac2a7919 Mon Sep 17 00:00:00 2001 From: Daniel Merkle Date: Tue, 28 Jan 2025 09:06:43 +0100 Subject: [PATCH] Initial commit, for 2024/2025 students --- pom.xml | 65 +++++ src/asteroids/AsteroidsApplication.java | 369 ++++++++++++++++++++++++ 2 files changed, 434 insertions(+) create mode 100644 pom.xml create mode 100644 src/asteroids/AsteroidsApplication.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2dccf70 --- /dev/null +++ b/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + de.techfak + Part14 + jar + 1.0 + Part14 + + + 17 + 17 + 21.0.2 + + + + + + + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + + org.openjfx + javafx-base + ${javafx.version} + + + org.openjfx + javafx-graphics + ${javafx.version} + + + org.openjfx + javafx-media + ${javafx.version} + + + + + + ${basedir}/src + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + + + + + + + diff --git a/src/asteroids/AsteroidsApplication.java b/src/asteroids/AsteroidsApplication.java new file mode 100644 index 0000000..a3a6950 --- /dev/null +++ b/src/asteroids/AsteroidsApplication.java @@ -0,0 +1,369 @@ +package asteroids; + +import javafx.animation.AnimationTimer; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.Pane; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.scene.shape.Shape; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class AsteroidsApplication extends Application { + public static final int WIDTH = 800; + public static final int HEIGHT = 600; + private GameController controller; + + @Override + public void start(Stage stage) { + GameView view = new GameView(); + GameModel model = new GameModel(); // Initialize GameModel with no controller + controller = new GameController(view, model); // Pass the model to the controller + + model.setController(controller); // Set the controller in the model after initialization + controller.startGameLoop(); // Start the game loop after everything is initialized + + stage.setTitle("Asteroids!"); + stage.setScene(view.getScene()); + stage.show(); + } + + public static void main(String[] args) { + launch(AsteroidsApplication.class); + } +} + +class GameModel { + private final List asteroids = new ArrayList<>(); + private final List projectiles = new ArrayList<>(); + private final Ship ship; + private final AtomicInteger points = new AtomicInteger(); + private boolean isGameOver = false; // Game Over state + private GameController controller; // Reference to the controller + + public GameModel() { + ship = new Ship(AsteroidsApplication.WIDTH / 2, AsteroidsApplication.HEIGHT / 2); + for (int i = 0; i < 15; i++) { + asteroids.add(new Asteroid(new Random().nextInt(AsteroidsApplication.WIDTH / 3), new Random().nextInt(AsteroidsApplication.HEIGHT))); + } + } + + // Setter for controller + public void setController(GameController controller) { + this.controller = controller; + } + + public Ship getShip() { return ship; } + public List getAsteroids() { return asteroids; } + public List getProjectiles() { return projectiles; } + public AtomicInteger getPoints() { return points; } + public boolean isGameOver() { return isGameOver; } + + public void update() { + if (isGameOver) return; // Stop updates when game over + + ship.move(); + asteroids.forEach(Asteroid::move); + projectiles.forEach(Projectile::move); + + checkCollisions(); + } + + private void checkCollisions() { + List toRemoveProjectiles = new ArrayList<>(); + List toRemoveAsteroids = new ArrayList<>(); + + long currentTime = System.currentTimeMillis(); + + for (Projectile projectile : projectiles) { + if (currentTime - projectile.getSpawnTime() > 200 && projectile.collide(ship)) { + gameOver(); + return; + } + + for (Asteroid asteroid : asteroids) { + if (projectile.collide(asteroid)) { + toRemoveProjectiles.add(projectile); + toRemoveAsteroids.add(asteroid); + points.addAndGet(1000); + } + } + } + + for (Asteroid asteroid : asteroids) { + if (asteroid.collide(ship)) { + gameOver(); + return; + } + } + + // Update the view in the controller + toRemoveAsteroids.forEach(asteroid -> asteroids.remove(asteroid)); + toRemoveProjectiles.forEach(projectile -> projectiles.remove(projectile)); + + if (!toRemoveAsteroids.isEmpty() || !toRemoveProjectiles.isEmpty()) { + controller.updateViewAfterCollisions(toRemoveAsteroids, toRemoveProjectiles); + } + } + + private void gameOver() { + isGameOver = true; // End the game + } +} + + +class GameView { + private final Pane pane; + private final Text scoreText; + private final Scene scene; + private final Text gameOverText; + + public GameView() { + pane = new Pane(); + pane.setPrefSize(AsteroidsApplication.WIDTH, AsteroidsApplication.HEIGHT); + + scoreText = new Text(10, 20, "Points: 0"); + pane.getChildren().add(scoreText); + + gameOverText = new Text(AsteroidsApplication.WIDTH / 2 - 50, AsteroidsApplication.HEIGHT / 2, "Game Over!"); + gameOverText.setVisible(false); + pane.getChildren().add(gameOverText); + + scene = new Scene(pane); + } + + public Scene getScene() { return scene; } + public Pane getPane() { return pane; } + public Text getScoreText() { return scoreText; } + + public void addGameObject(Character obj) { + pane.getChildren().add(obj.getCharacter()); + } + + public void removeGameObject(Character obj) { + pane.getChildren().remove(obj.getCharacter()); + } + + public void setGameOverVisible(boolean visible) { + gameOverText.setVisible(visible); + } +} + +class GameController { + private final GameView view; + private final GameModel model; + private final Map pressedKeys = new HashMap<>(); + private AnimationTimer gameLoop; + private long lastShotTime = 0; + + public GameController(GameView view, GameModel model) { + this.view = view; + this.model = model; + + view.addGameObject(model.getShip()); + model.getAsteroids().forEach(view::addGameObject); + + view.getScene().setOnKeyPressed(event -> pressedKeys.put(event.getCode(), true)); + view.getScene().setOnKeyReleased(event -> pressedKeys.put(event.getCode(), false)); + } + + public void startGameLoop() { + gameLoop = new AnimationTimer() { + @Override + public void handle(long now) { + if (model.isGameOver()) { + gameLoop.stop(); + view.setGameOverVisible(true); + return; + } + + handleInputs(); + spawnAsteroids(); + model.update(); + updateView(); + } + }; + gameLoop.start(); + } + + private void handleInputs() { + if (model.isGameOver()) return; + + if (pressedKeys.getOrDefault(KeyCode.LEFT, false)) model.getShip().turnLeft(); + if (pressedKeys.getOrDefault(KeyCode.RIGHT, false)) model.getShip().turnRight(); + if (pressedKeys.getOrDefault(KeyCode.UP, false)) model.getShip().accelerate(); + + long currentTime = System.currentTimeMillis(); + if (pressedKeys.getOrDefault(KeyCode.SPACE, false) && model.getProjectiles().size() < 3 + && currentTime - lastShotTime > 200) { + lastShotTime = currentTime; + double shipAngle = Math.toRadians(model.getShip().getCharacter().getRotate()); + double spawnX = model.getShip().getX() + Math.cos(shipAngle) * 15; + double spawnY = model.getShip().getY() + Math.sin(shipAngle) * 15; + + Projectile projectile = new Projectile(spawnX, spawnY, System.currentTimeMillis()); + projectile.getCharacter().setRotate(model.getShip().getCharacter().getRotate()); + model.getProjectiles().add(projectile); + projectile.accelerate(); + + view.addGameObject(projectile); + } + } + + private void spawnAsteroids() { + if (Math.random() < 0.005) { + Asteroid asteroid = new Asteroid(Math.random() * AsteroidsApplication.WIDTH, 0); + if (!asteroid.collide(model.getShip())) { + model.getAsteroids().add(asteroid); + view.addGameObject(asteroid); + } + } + } + + private void updateView() { + view.getScoreText().setText("Points: " + model.getPoints().get()); + + List toRemove = new ArrayList<>(); + for (Projectile projectile : model.getProjectiles()) { + if (projectile.getX() < 0 || projectile.getX() > AsteroidsApplication.WIDTH || + projectile.getY() < 0 || projectile.getY() > AsteroidsApplication.HEIGHT) { + toRemove.add(projectile); + } + } + + toRemove.forEach(projectile -> { + model.getProjectiles().remove(projectile); + view.removeGameObject(projectile); + }); + } + + public void updateViewAfterCollisions(List removedAsteroids, List removedProjectiles) { + // Handle view updates here + removedAsteroids.forEach(asteroid -> view.removeGameObject(asteroid)); + removedProjectiles.forEach(projectile -> view.removeGameObject(projectile)); + } +} + +abstract class Character { + protected final Polygon character; + protected double x, y, velocityX, velocityY; + + public Character(Polygon shape, double x, double y) { + this.character = shape; + this.x = x; + this.y = y; + this.velocityX = 0; + this.velocityY = 0; + this.character.setTranslateX(x); + this.character.setTranslateY(y); + } + + public void move() { + x += velocityX; + y += velocityY; + + // Wrap around screen horizontally + if (x < 0) x += AsteroidsApplication.WIDTH; + if (x > AsteroidsApplication.WIDTH) x -= AsteroidsApplication.WIDTH; + + // Wrap around screen vertically + if (y < 0) y += AsteroidsApplication.HEIGHT; + if (y > AsteroidsApplication.HEIGHT) y -= AsteroidsApplication.HEIGHT; + + character.setTranslateX(x); + character.setTranslateY(y); + } + + public boolean collide(Character other) { + Shape collisionArea = Shape.intersect(this.character, other.getCharacter()); + return !collisionArea.getBoundsInLocal().isEmpty(); + } + + public Polygon getCharacter() { + return character; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + public void setX(double x) { + this.x = x; + character.setTranslateX(x); + } + + public void setY(double y) { + this.y = y; + character.setTranslateY(y); + } +} + + +class Ship extends Character { + public Ship(double x, double y) { + super(new Polygon(-10, -10, 20, 0, -10, 10), x, y); + } + + public void turnLeft() { + character.setRotate(character.getRotate() - 5); + } + + public void turnRight() { + character.setRotate(character.getRotate() + 5); + } + + public void accelerate() { + double angle = Math.toRadians(character.getRotate()); + velocityX += Math.cos(angle) * 0.1; + velocityY += Math.sin(angle) * 0.1; + } +} + +class Asteroid extends Character { + public Asteroid(double x, double y) { + super(new PolygonFactory().createPolygon(), x, y); + Random rnd = new Random(); + velocityX = rnd.nextDouble() - 0.5; + velocityY = rnd.nextDouble() - 0.5; + } +} + + +class Projectile extends Character { + private final long spawnTime; // Track when the projectile was created + + public Projectile(double x, double y, long spawnTime) { + super(new Polygon(2, -2, 2, 2, -2, 2, -2, -2), x, y); + this.spawnTime = spawnTime; // Store the spawn time + } + + public void accelerate() { + double angle = Math.toRadians(character.getRotate()); + velocityX = Math.cos(angle) * 3; + velocityY = Math.sin(angle) * 3; + } + + public long getSpawnTime() { + return spawnTime; + } +} + +class PolygonFactory { + public Polygon createPolygon() { + Random rnd = new Random(); + double size = 10 + rnd.nextInt(10); + Polygon polygon = new Polygon(); + for (int i = 0; i < 5; i++) { + double angle = 2 * Math.PI * i / 5; + polygon.getPoints().addAll(size * Math.cos(angle), size * Math.sin(angle)); + } + return polygon; + } +} \ No newline at end of file