Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

PlantUML Macro
class SpaceObject {
	Point2D position
	Point2D speed
	double getMass()
}
class Asteroid {
	double density
	double radius
}
class SpaceShip {
	double angleSpeed
}
SpaceObject <|-- Asteroid
SpaceObject <|-- SpaceShip

Det er i hovedsak to typer rom-objekter, de som svever fritt (planeter, asteroider og stjerner) og romskipet, som har motorkraft. Det som er felles for alle rom-objektene ønsker vi å samle i superklassen SpaceObject, som de to mer spesialiserte typene Asteroid og SpaceShip arver fra. Dette er illustrert i diagrammet til venstre. Vi har brukt typen Point2D for å angi at posisjon (position) og fart (speed) beskrives av to verdier (koordinatpar/vektorkoordinater). Massen beregnes, så den er angitt som en operasjon og ikke som et attributt.

Det som ikke kommer frem i dette diagrammet er hvordan vi også ønsker å bygge på Polygon-klassen i JavaFX, for å gjøre det lettere å vise rom-objektene som grafikk i et vindu på skjermen. Med Polygon som superklasse for SpaceObject, så vil en kunne angi et sett punkter som utgjør konturen til rom-objektene. Siden Polygon i praksis også har en posisjon (i kraft av å være en Node), så trenger en ikke lagre denne som et eget felt i SpaceObject, så den faktiske implementasjon blir litt annerledes enn vist til venstre. Og for å gjøre det litt lettere å utnytte mulighetene i Polygon-klassen (spesielt koordinat-håndtering er litt fiklete), så har vi lage en hjelpeklasse, kalt BaseSpaceObject, som kan brukes som den direkte superklassen til SpaceObject og som selv arver fra Polygon. Dette er illustrert til høyre.

Kode m/dokumentasjon for BaseSpaceObject-klassen finner du nederst på denne siden.

De spesifikke metodene i SpaceObject, Asteroids og SpaceShip, som du må implementere er som følger:

SpaceObject

  • Point2D getSpeed() - returnerer farten som et (vektor)koordinatpar-objekt.
  • setSpeed(double vx, double vy) - setter farten til den angitt vektoren [vx, vy].
  • accelerate(double ax, double ay) - øker farten med en angitte akselerasjonsvektoren
  • double getMass() - returnerer massen, som er 0 hvis ikke annet er angitt.
  • applyForce(double fx, double fy) - justerer objektet iht. den angitte kraftvektoren. Dersom massen er 0, så skal det utløses et unntak av typen IllegalStateException.
  • boolean intersects(SpaceObject other) - returnerer true dersom den andre SpaceObject-instansen overlapper (kolliderer) med denne instansen, ellers false. (Hint: Bruk contains-metoden i BaseSpaceObject).
  • void tick() - denne metoden kalles for hvert steg i simuleringen og skal justere alle dynamiske egenskaper iht. reglene for verdenen, f.eks. posisjon basert på farta.

Asteroid

  • public Asteroid(double density, double radius) - initialiserer tetthet og radius. Lag gjerne flere konstruktører, for å gjøre det lettere å lage asteroider med ulike konturer.
  • double getMass() - beregner massen fra tetthet og radius

SpaceShip

  • SpaceShip() - initialiserer vinkelfarta til 0
  • sidewaysThrust(double thrust) - endrer vinkelfarta tilsvarende. Positiv verdi gir fart mot urvisere. (Hint: bruk get/setRotate-metodene i Polygon).
  • forwardThrust(double thrust) - øker farta tilsvarende i retningen romskipet peker.
PlantUML Macro
class Polygon {
	Point2D position
}
class Point2D {
}
Polygon *-right-> "*" Point2D: points
class BaseSpaceObject {
}
class SpaceObject {
 	Point2D speed
	double getMass()
}
Polygon <|-- BaseSpaceObject
BaseSpaceObject <|-- SpaceObject
class Asteroid {
	double density
	double radius
}
class SpaceShip {
	double angleSpeed
}
SpaceObject <|-- Asteroid
SpaceObject <|-- SpaceShip

Del 2 - Oppsett av verden og simulering

Denne delen tar utgangspunkt i en delvis ferdigskrevet Asteroids-klasse. Oppgaven til Asteroids-klassen er todel: 1) å rigge opp verdenen med rom-objekter og 2) kjøre selve simuleringen. I koden for Asteroids-klassen, som ligger nederst på denne siden, legges det opp til at dette gjøres i henholdsvis init()- og run()-metoden, men du står forsåvidt fritt til å (gjen)bruke denne koden som du vil. Men for at du skal lære mest mulig om arv, så prøv å legge mest mulig av din egen kode i subklasser av Asteroids, heller enn å skrive den om. Vi foreslår at du gjør oppgaven i følgende trinn, med én subklasse pr. trinn.

 

 

Code Block
languagejava
titleBaseSpaceObject
collapsetrue
package inheritance;

import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.shape.Polygon;

public class BaseSpaceObject extends Polygon {
    /**
     * Adds the point given by x,y to this Polygon
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    protected void addPoint(double x, double y) {
        getPoints().add(x);
        getPoints().add(y);
    }
    /**
     * Adds the points given by the sequence of x- and y-coordinates
     * @param xys the x- and y-coordinates
     */
    protected void addPoints(double... xys) {
        for (int i = 0; i < xys.length; i += 2) {
            addPoint(xys[i], xys[i + 1]);
        }
    }
    
Code Block
languagejava
titleBaseSpaceObject
collapsetrue
package inheritance;

import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.shape.Polygon;

public class BaseSpaceObject extends Polygon {
    /**
     * Adds the point given by x,y to this Polygon
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    protected void addPoint(double x, double y) {
        getPoints().add(x);
        getPoints().add(y);
    }
    /**
     * Adds the points given by the sequence of x- and y-coordinates
     * @param xys the x- and y-coordinates
     */
    protected void addPoints(double... xys) {
        for (int i = 0; i < xys.length; i += 2) {
            addPoint(xys[i], xys[i + 1]);
        }
    }
    
    /**
     * Adds the point given by angle, length in polar coordinates
     * @param angle the angle
     * @param length the length
     */
    protected void addPolarPoint(double angle, double length) {
        addPoint(Math.cos(angle) * length, Math.sin(angle) * length);
    }
    
    /**
     * Returns the position of this Polygon as a Point2D object
     * @return the position as a Point2D object
     */
    public Point2D getPosition() {
        return new Point2D(getTranslateX(), getTranslateY());
    }
    /**
     * Moves (displaces) this Polygon by the given dx, dy
     * @param dx the x displacement
     * @param dy the y displacement
     */
    public void translate(double dx, double dy) {
        setTranslateX(getTranslateX() + dx);
        setTranslateY(getTranslateY() + dy);
    }
    
    /**
     * Returns the position of the center of this Polygon.
     * The asParentCoordinates determines if the position is transformed into the parent coordinate system.
     * @param asParentCoordinates determines if the position is transformed into the parent coordinate system
     * @return the center position
     */
    public Point2D getCenter(boolean asParentCoordinates) {
        Bounds bounds = getBoundsInLocal();
        Point2D center = new Point2D((bounds.getMaxX() + bounds.getMinX()) / 2, (bounds.getMaxY() + bounds.getMinY()) / 2);
        if (asParentCoordinates) {
            center = localToParent(center);
        }
        return center;
    }
    /**
     * Returns the number of points in this Polygon
     * @return the number of points in this Polygon
     */
    public int getPointCount() {
        return getPoints().size() / 2;
    }
    /**
     * DeterminesAdds ofthe a specific point ofgiven anotherby Polygonangle, islength insidein thispolar Polygoncoordinates
     * @param otherangle the other Polygonangle
     * @param pointNumlength the number of the point to checklength
     * @return
     */
    publicprotected booleanvoid containsaddPolarPoint(BaseSpaceObjectdouble otherangle, intdouble pointNumlength) {
        addPoint(Math.cos(angle) * ObservableList<Double> points = other.getPoints(length, Math.sin(angle) * length);
    }
    double
 x = points.get(pointNum /**
 2), y = points.get(pointNum * 2Returns + 1);
        return this.contains(parentToLocal(other.localToParent(x, y)));the position of this Polygon as a Point2D object
    }
}
Code Block
languagejava
titleAsteroids-klassen
collapsetrue
package inheritance;

import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.util.Duration;

public class Asteroids extends Pane implements EventHandler<KeyEvent>  * @return the position as a Point2D object
     */
    public Point2D getPosition() {
    private List<SpaceObject> spaceObjects;
  return  private int tickCount = 0;new Point2D(getTranslateX(), getTranslateY());
    }
    /**
     protected* voidMoves add(SpaceObject so, double x, double y) {displaces) this Polygon by the given dx, dy
     * @param  so.translate(x, y);dx the x displacement
     * @param  getChildren().add(so);dy the y displacement
     */
     spaceObjects.add(so);
    }
public void translate(double dx, double dy) {
      public void initsetTranslateX(getTranslateX() {+ dx);
        spaceObjects = new ArrayList<SpaceObject>(setTranslateY(getTranslateY() + dy);
    }
    public
  void run() {
 /**
     * Returns the position Timelineof tickTimerthe =center newof Timeline(new KeyFrame(Duration.millis(50), new EventHandler<ActionEvent>() {
      this Polygon.
     * The asParentCoordinates determines if the position is transformed into the parent coordinate system.
     * @Override
@param asParentCoordinates determines if the position is transformed into the parent coordinate publicsystem
 void handle(ActionEvent event) {
 * @return the center position
     */
    public Point2D tick(tickCount++);getCenter(boolean asParentCoordinates) {
        Bounds bounds = getBoundsInLocal();
 }
       Point2D }));
center = new Point2D((bounds.getMaxX() + bounds.getMinX()) / 2, tickTimer(bounds.setCycleCount(Timeline.INDEFINITEgetMaxY() + bounds.getMinY()) / 2);
        if tickTimer.play(asParentCoordinates); {
        requestFocus();
     center =  setOnKeyPressedlocalToParent(thiscenter);
        setOnKeyTyped(this);}
    }
    protected void handleKey(KeyCode keyCode) {return center;
    }
    protected/**
 void handleKey(String character) {
 * Returns the number of points in if ("+".equals(character)) {this Polygon
     * @return the number of points  zoom(2);in this Polygon
     */
   } elsepublic ifint ("-".equals(character)getPointCount() {
        return    zoom(0.5)getPoints().size() / 2;
    }
    }/**
    }
 * Determines of 
a specific point of another privatePolygon voidis zoom(double factor) {inside this Polygon
     * @param other setScaleX(getScaleX() * factor);the other Polygon
     * @param pointNum setScaleY(getScaleY() * factor);
    }
the number of the point to check
     * @return
     @Override*/
    public voidboolean handle(KeyEvent keyEventcontains(BaseSpaceObject other, int pointNum) {
        if (keyEvent.getCode()ObservableList<Double> points == KeyCodeother.UNDEFINEDgetPoints() {;
        double x =  handleKey(keyEvent.getCharacter());
        } else {points.get(pointNum * 2), y = points.get(pointNum * 2 + 1);
            handleKey(keyEvent.getCode())return this.contains(parentToLocal(other.localToParent(x, y)));
        }
}
Code Block
languagejava
titleAsteroids-klassen
collapsetrue
package inheritance;

import   }
    private void tick(int tickCount) {
        // accelerate due to gravity
        for (int i = 0; i < spaceObjects.size(); i++) {
            for (int j = i + 1; j < spaceObjects.size(); j++java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.util.Duration;

public class Asteroids extends Pane implements EventHandler<KeyEvent>  {

	private List<SpaceObject> spaceObjects;
    
    protected void add(SpaceObject so, double x, double y) {
                handleGravity(spaceObjects.get(i), spaceObjects.get(j)so.translate(x, y);
            }getChildren().add(so);
        }spaceObjects.add(so);
    }
    //public movevoid allinit() objects{
        forspaceObjects (SpaceObject= spaceObject : spaceObjects) {new ArrayList<SpaceObject>();
    }

	public        spaceObject.tick();
   void run() {
		// setter opp simuleringstakt
     }
   Timeline tickTimer = new  
   Timeline(new KeyFrame(Duration.millis(50), new EventHandler<ActionEvent>() {
    		private //int checktickCount for= collision0;
        List<SpaceObject> collisions = new ArrayList<SpaceObject>(); @Override
        for (int i = 0; ipublic <void spaceObjects.size(); i++handle(ActionEvent event) {
            for    (int j = i + 1; j < spaceObjects.size(); j++) {tick(tickCount++);
            }
        }));
        tickTimer.setCycleCount(Timeline.INDEFINITE);
     SpaceObject spaceObject1 = spaceObjectstickTimer.get(i), spaceObject2 = spaceObjects.get(jplay();
        requestFocus();
        setOnKeyPressed(this);
        if (spaceObject1.intersects(spaceObject2)) {setOnKeyTyped(this);
    }

	// kalles når spesialtaster som pilene trykkes
	protected void handleKey(KeyCode keyCode) {
      collisions.add(spaceObject1);
    }

	// kalles når vanlige bokstavtaster trykkes
    protected void handleKey(String character) {
        if collisions.add(spaceObject2);("+".equals(character)) {
                }zoom(2);
        } else if  }("-".equals(character)) {
        }
     zoom(0.5);
   for (SpaceObject spaceObject : spaceObjects) {}
    }
    
    private ifvoid zoom(! collisions.contains(spaceObject))double factor) {
        setScaleX(getScaleX() * factor);
        handleCollision(spaceObject, nullsetScaleY(getScaleY() * factor);
    }
    
    }@Override
    public void handle(KeyEvent keyEvent) }{
        for (int i = 0; i < collisions.sizeif (keyEvent.getCode(); i +== 2KeyCode.UNDEFINED) {
            handleKey(keyEvent.getCharacter());
  SpaceObject spaceObject1 = collisions.get(i), spaceObject2 = collisions.get(i + 1);} else {
            handleCollision(spaceObject1, spaceObject2handleKey(keyEvent.getCode());
        }
    }

	private void tick(int tickCount) {
    public   void handleGravity(SpaceObject spaceObject1, SpaceObject spaceObject2) {// accelerate due to gravity
        if (spaceObject1.getMass() * spaceObject2.getMass() != 0for (int i = 0; i < spaceObjects.size(); i++) {
            Point2D center = spaceObject1.getCenter(true);
            Point2D otherCenter = spaceObject2.getCenter(true);
            double dx = otherCenter.getX() - center.getX(), dy = otherCenter.getY() - center.getY();
for (int j = i + 1; j < spaceObjects.size(); j++) {
				// fyll inn kode her, for å håndtere gravitasjonskraften
            }
        }
     double d2 = dx// *move dxall +objects
 dy * dy, d = Math.sqrt(d2);
  for (SpaceObject spaceObject : spaceObjects) {
     double force = spaceObject1.getMass() * spaceObject2.getMass() / d2;
     spaceObject.tick();
        }
     spaceObject1.applyForce(force * dx // d,check forcefor *collision
 dy / d);
     for (int i = 0; i < spaceObject2spaceObjects.applyForce(force * (- dx) / d, force * (- dy) / d);
        }
    }
    public void handleCollision(SpaceObject spaceObject1, SpaceObject spaceObject2) {size(); i++) {
            for (int j = i + 1; j < spaceObjects.size(); j++) {
				// fyll inn kode her, for å håndtere kollisjoner, f.eks.
				// endre fargen, implementere støtfysikk e.l.
        if (spaceObject2 == null) {}
        }
    spaceObject1.setStroke(null);
        } else {
            spaceObject1.setStroke(Color.RED);
            spaceObject2.setStroke(Color.RED);
        }}

	public void handleGravity(SpaceObject spaceObject1, SpaceObject spaceObject2) {
		// regn ut avstanden mellom spaceObject1 og spaceObject2 (bruk getCenter-metoden),
		// beregn den gjensidige gravitasjonskraften basert på formelen
		// masse1 * masse2 / avstand ^ 2, og
		// påfør kraften med SpaceObject.applyForce-metoden
    }
}
Code Block
languagejava
titleAsteroidsProgram
collapsetrue
package inheritance;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class AsteroidsProgram extends Application {
    private int width = 800, height = 600;
    @Override
    public void start(Stage stage) throws Exception {
        String paneClassName = getParameters().getRaw().get(0);
        final Asteroids asteroidsPane = (Asteroids) Class.forName(paneClassName).newInstance();
        asteroidsPane.setPrefSize(width, height);
        Scene scene = new Scene(asteroidsPane, width, height, Color.BLACK);
        stage.setScene(scene);
        stage.show();
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                asteroidsPane.init();
                asteroidsPane.run();
            }
        });
    }
    
    public static void main(String[] args) {
        launch(AsteroidsProgram.class, args);
    }
}