{"id":"asteroids-vectors","slug":"vectors","title":"Engines on: moving with vectors","text":"The submarine turns. Now it needs to move: forward in the direction its nose points, faster while Up is held, and on by itself when Up is let go, the way things drift in water (and fly in space, in the MOOC's game).\n\n### Point2D\n\nA position is two numbers, an x and a y, and so is a movement: \"2 pixels right and 1 up in every frame\". JavaFX has a class for such pairs, **Point2D** (from `javafx.geometry`). A Point2D is **immutable**, like a String: its methods return a new point and leave the old one as it was.\n\n```java\nimport javafx.geometry.Point2D;\n\npublic class Main {\n    public static void main(String[] args) {\n        Point2D place = new Point2D(100, 50);\n        Point2D velocity = new Point2D(3, -4);\n\n        place.add(velocity);\n        System.out.println(place);\n\n        place = place.add(velocity);\n        System.out.println(place);\n        System.out.println(place.getX() + \" and \" + place.getY());\n\n        System.out.println(velocity.magnitude());\n        System.out.println(velocity.multiply(2));\n    }\n}\n```\n\n```output\nPoint2D [x = 100.0, y = 50.0]\nPoint2D [x = 103.0, y = 46.0]\n103.0 and 46.0\n5.0\nPoint2D [x = 6.0, y = -8.0]\n```\n\n- `add(other)` returns a new point with the x values added and the y values added. The first `place.add(velocity)` changes nothing: its result is thrown away. To keep it, assign it: `place = place.add(velocity)`. There's also `add(x, y)` with two numbers.\n- `getX()` and `getY()` return the two numbers, as doubles.\n- `magnitude()` is the length of the arrow from (0, 0) to the point: the speed, for a velocity. (3, -4) is 5 long, by Pythagoras.\n- `multiply(factor)` returns the point with both numbers multiplied.\n\nPoint2D is a plain class: as here, a program can use it without a window.\n\n### A velocity, added every frame\n\nA **velocity** says how far something moves in one frame: (1.5, -0.5) is 1.5 pixels right and half a pixel up per frame. In each frame, the game adds the velocity to the position:\n\n```java\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Scene;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\nimport javafx.scene.shape.Rectangle;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n    private Point2D velocity = new Point2D(1.5, -0.5);\n\n    @Override\n    public void start(Stage stage) {\n        Pane pane = new Pane();\n        pane.setPrefSize(360, 240);\n        Polygon sub = new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6);\n        sub.setFill(Color.GOLD);\n        sub.setTranslateX(100);\n        sub.setTranslateY(160);\n        pane.getChildren().addAll(new Rectangle(360, 240, Color.MIDNIGHTBLUE), sub);\n\n        new AnimationTimer() {\n            @Override\n            public void handle(long now) {\n                sub.setTranslateX(sub.getTranslateX() + velocity.getX());\n                sub.setTranslateY(sub.getTranslateY() + velocity.getY());\n            }\n        }.start();\n\n        stage.setScene(new Scene(pane));\n        stage.setTitle(\"Drifting\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[\"frames 80\"],\"window\":{\"windows\":[{\"title\":\"Drifting\",\"scene\":{\"width\":360,\"height\":240,\"root\":{\"n\":1,\"type\":\"Pane\",\"f\":1,\"prefWidth\":360,\"prefHeight\":240,\"children\":[{\"n\":2,\"type\":\"Rectangle\",\"f\":2,\"x\":0,\"y\":0,\"width\":360,\"height\":240,\"fill\":\"#191970\"},{\"n\":3,\"type\":\"Polygon\",\"f\":3,\"translateX\":220,\"translateY\":120,\"points\":[-15,-6,9,-6,15,0,9,6,-15,6],\"fill\":\"#ffd700\"}]}}}],\"problems\":[],\"frames\":{\"count\":80,\"list\":[[{\"roots\":[1]},{\"k\":1,\"type\":\"Pane\",\"children\":[2,3]},{\"k\":2,\"type\":\"Rectangle\",\"width\":360,\"height\":240,\"fill\":\"#191970\"},{\"k\":3,\"type\":\"Polygon\",\"tx\":101.5,\"ty\":159.5,\"points\":[-15,-6,9,-6,15,0,9,6,-15,6],\"fill\":\"#ffd700\"}],[{\"k\":3,\"tx\":103,\"ty\":159}],[{\"k\":3,\"tx\":104.5,\"ty\":158.5}],[{\"k\":3,\"tx\":106,\"ty\":158}],[{\"k\":3,\"tx\":107.5,\"ty\":157.5}],[{\"k\":3,\"tx\":109,\"ty\":157}],[{\"k\":3,\"tx\":110.5,\"ty\":156.5}],[{\"k\":3,\"tx\":112,\"ty\":156}],[{\"k\":3,\"tx\":113.5,\"ty\":155.5}],[{\"k\":3,\"tx\":115,\"ty\":155}],[{\"k\":3,\"tx\":116.5,\"ty\":154.5}],[{\"k\":3,\"tx\":118,\"ty\":154}],[{\"k\":3,\"tx\":119.5,\"ty\":153.5}],[{\"k\":3,\"tx\":121,\"ty\":153}],[{\"k\":3,\"tx\":122.5,\"ty\":152.5}],[{\"k\":3,\"tx\":124,\"ty\":152}],[{\"k\":3,\"tx\":125.5,\"ty\":151.5}],[{\"k\":3,\"tx\":127,\"ty\":151}],[{\"k\":3,\"tx\":128.5,\"ty\":150.5}],[{\"k\":3,\"tx\":130,\"ty\":150}],[{\"k\":3,\"tx\":131.5,\"ty\":149.5}],[{\"k\":3,\"tx\":133,\"ty\":149}],[{\"k\":3,\"tx\":134.5,\"ty\":148.5}],[{\"k\":3,\"tx\":136,\"ty\":148}],[{\"k\":3,\"tx\":137.5,\"ty\":147.5}],[{\"k\":3,\"tx\":139,\"ty\":147}],[{\"k\":3,\"tx\":140.5,\"ty\":146.5}],[{\"k\":3,\"tx\":142,\"ty\":146}],[{\"k\":3,\"tx\":143.5,\"ty\":145.5}],[{\"k\":3,\"tx\":145,\"ty\":145}],[{\"k\":3,\"tx\":146.5,\"ty\":144.5}],[{\"k\":3,\"tx\":148,\"ty\":144}],[{\"k\":3,\"tx\":149.5,\"ty\":143.5}],[{\"k\":3,\"tx\":151,\"ty\":143}],[{\"k\":3,\"tx\":152.5,\"ty\":142.5}],[{\"k\":3,\"tx\":154,\"ty\":142}],[{\"k\":3,\"tx\":155.5,\"ty\":141.5}],[{\"k\":3,\"tx\":157,\"ty\":141}],[{\"k\":3,\"tx\":158.5,\"ty\":140.5}],[{\"k\":3,\"tx\":160,\"ty\":140}],[{\"k\":3,\"tx\":161.5,\"ty\":139.5}],[{\"k\":3,\"tx\":163,\"ty\":139}],[{\"k\":3,\"tx\":164.5,\"ty\":138.5}],[{\"k\":3,\"tx\":166,\"ty\":138}],[{\"k\":3,\"tx\":167.5,\"ty\":137.5}],[{\"k\":3,\"tx\":169,\"ty\":137}],[{\"k\":3,\"tx\":170.5,\"ty\":136.5}],[{\"k\":3,\"tx\":172,\"ty\":136}],[{\"k\":3,\"tx\":173.5,\"ty\":135.5}],[{\"k\":3,\"tx\":175,\"ty\":135}],[{\"k\":3,\"tx\":176.5,\"ty\":134.5}],[{\"k\":3,\"tx\":178,\"ty\":134}],[{\"k\":3,\"tx\":179.5,\"ty\":133.5}],[{\"k\":3,\"tx\":181,\"ty\":133}],[{\"k\":3,\"tx\":182.5,\"ty\":132.5}],[{\"k\":3,\"tx\":184,\"ty\":132}],[{\"k\":3,\"tx\":185.5,\"ty\":131.5}],[{\"k\":3,\"tx\":187,\"ty\":131}],[{\"k\":3,\"tx\":188.5,\"ty\":130.5}],[{\"k\":3,\"tx\":190,\"ty\":130}],[{\"k\":3,\"tx\":191.5,\"ty\":129.5}],[{\"k\":3,\"tx\":193,\"ty\":129}],[{\"k\":3,\"tx\":194.5,\"ty\":128.5}],[{\"k\":3,\"tx\":196,\"ty\":128}],[{\"k\":3,\"tx\":197.5,\"ty\":127.5}],[{\"k\":3,\"tx\":199,\"ty\":127}],[{\"k\":3,\"tx\":200.5,\"ty\":126.5}],[{\"k\":3,\"tx\":202,\"ty\":126}],[{\"k\":3,\"tx\":203.5,\"ty\":125.5}],[{\"k\":3,\"tx\":205,\"ty\":125}],[{\"k\":3,\"tx\":206.5,\"ty\":124.5}],[{\"k\":3,\"tx\":208,\"ty\":124}],[{\"k\":3,\"tx\":209.5,\"ty\":123.5}],[{\"k\":3,\"tx\":211,\"ty\":123}],[{\"k\":3,\"tx\":212.5,\"ty\":122.5}],[{\"k\":3,\"tx\":214,\"ty\":122}],[{\"k\":3,\"tx\":215.5,\"ty\":121.5}],[{\"k\":3,\"tx\":217,\"ty\":121}],[{\"k\":3,\"tx\":218.5,\"ty\":120.5}],[{\"k\":3,\"tx\":220,\"ty\":120}]]}},\"outline\":\"Window \\\"Drifting\\\"\\n  Pane 360 x 240\\n    Rectangle 360 x 240 at (0, 0), fill #191970\\n    Polygon (-15, -6) (9, -6) (15, 0) (9, 6) (-15, 6), at (220, 120), fill #ffd700\\nframes run: 80\"}\n```\n\nAfter 80 frames the submarine has moved 80 times 1.5 = 120 pixels right and 40 up, to (220, 120). It drifts sideways, though: it points right but moves up as well. Its velocity should come from where it points.\n\n### Speeding up where the nose points\n\nA submarine turned by an angle `a` points in the direction (cos a, sin a): one step of length 1 that way goes `Math.cos(a)` across and `Math.sin(a)` down. At 0 degrees that's (1, 0), right; at 90 degrees (0, 1), down, since angles grow clockwise on the screen; at 180 degrees (-1, 0), left; and at -90 degrees (0, -1), up. Java's `Math.cos` and `Math.sin` take the angle in **radians**, not degrees, and `Math.toRadians(degrees)` converts it.\n\nSo to **accelerate**, a game adds a small step in that direction to the velocity:\n\n```java\ndouble angle = Math.toRadians(sub.getRotate());\nvelocity = velocity.add(Math.cos(angle) * 0.125, Math.sin(angle) * 0.125);\n```\n\nEach frame with Up held adds 0.125 pixels a frame to the speed, in the direction the submarine points. Let go of Up, and the velocity stays: the submarine keeps going. Turning changes only where the next push goes, so a turned submarine curves slowly into its new direction, as in the MOOC's game.\n\n```java\nimport java.util.HashMap;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\nimport javafx.scene.shape.Rectangle;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n    private Map<KeyCode, Boolean> held = new HashMap<>();\n    private Point2D velocity = new Point2D(0, 0);\n\n    @Override\n    public void start(Stage stage) {\n        Pane pane = new Pane();\n        pane.setPrefSize(360, 240);\n        Polygon sub = new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6);\n        sub.setFill(Color.GOLD);\n        sub.setTranslateX(60);\n        sub.setTranslateY(120);\n        pane.getChildren().addAll(new Rectangle(360, 240, Color.MIDNIGHTBLUE), sub);\n\n        Scene scene = new Scene(pane);\n        scene.setOnKeyPressed(event -> held.put(event.getCode(), true));\n        scene.setOnKeyReleased(event -> held.put(event.getCode(), false));\n\n        new AnimationTimer() {\n            @Override\n            public void handle(long now) {\n                if (held.getOrDefault(KeyCode.LEFT, false)) {\n                    sub.setRotate(sub.getRotate() - 4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.setRotate(sub.getRotate() + 4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    double angle = Math.toRadians(sub.getRotate());\n                    velocity = velocity.add(Math.cos(angle) * 0.125, Math.sin(angle) * 0.125);\n                }\n                sub.setTranslateX(sub.getTranslateX() + velocity.getX());\n                sub.setTranslateY(sub.getTranslateY() + velocity.getY());\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Engines\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[\"press key UP\",\"frames 12\",\"release key UP\",\"frames 20\",\"press key RIGHT\",\"frames 15\",\"release key RIGHT\",\"press key UP\",\"frames 10\",\"release key UP\",\"frames 40\"],\"window\":{\"windows\":[{\"title\":\"Engines\",\"scene\":{\"width\":360,\"height\":240,\"onKeyPressed\":true,\"onKeyReleased\":true,\"root\":{\"n\":1,\"type\":\"Pane\",\"f\":1,\"prefWidth\":360,\"prefHeight\":240,\"children\":[{\"n\":2,\"type\":\"Rectangle\",\"f\":2,\"x\":0,\"y\":0,\"width\":360,\"height\":240,\"fill\":\"#191970\"},{\"n\":3,\"type\":\"Polygon\",\"f\":3,\"translateX\":225.6875,\"translateY\":169.2551948402401,\"rotate\":60,\"points\":[-15,-6,9,-6,15,0,9,6,-15,6],\"fill\":\"#ffd700\"}]}}}],\"problems\":[],\"frames\":{\"count\":97,\"list\":[[{\"roots\":[1]},{\"k\":1,\"type\":\"Pane\",\"children\":[2,3]},{\"k\":2,\"type\":\"Rectangle\",\"width\":360,\"height\":240,\"fill\":\"#191970\"},{\"k\":3,\"type\":\"Polygon\",\"tx\":60.13,\"ty\":120,\"points\":[-15,-6,9,-6,15,0,9,6,-15,6],\"fill\":\"#ffd700\"}],[{\"k\":3,\"tx\":60.38}],[{\"k\":3,\"tx\":60.75}],[{\"k\":3,\"tx\":61.25}],[{\"k\":3,\"tx\":61.88}],[{\"k\":3,\"tx\":62.63}],[{\"k\":3,\"tx\":63.5}],[{\"k\":3,\"tx\":64.5}],[{\"k\":3,\"tx\":65.63}],[{\"k\":3,\"tx\":66.88}],[{\"k\":3,\"tx\":68.25}],[{\"k\":3,\"tx\":69.75}],[{\"k\":3,\"tx\":71.25}],[{\"k\":3,\"tx\":72.75}],[{\"k\":3,\"tx\":74.25}],[{\"k\":3,\"tx\":75.75}],[{\"k\":3,\"tx\":77.25}],[{\"k\":3,\"tx\":78.75}],[{\"k\":3,\"tx\":80.25}],[{\"k\":3,\"tx\":81.75}],[{\"k\":3,\"tx\":83.25}],[{\"k\":3,\"tx\":84.75}],[{\"k\":3,\"tx\":86.25}],[{\"k\":3,\"tx\":87.75}],[{\"k\":3,\"tx\":89.25}],[{\"k\":3,\"tx\":90.75}],[{\"k\":3,\"tx\":92.25}],[{\"k\":3,\"tx\":93.75}],[{\"k\":3,\"tx\":95.25}],[{\"k\":3,\"tx\":96.75}],[{\"k\":3,\"tx\":98.25}],[{\"k\":3,\"tx\":99.75}],[{\"k\":3,\"tx\":101.25,\"r\":4}],[{\"k\":3,\"tx\":102.75,\"r\":8}],[{\"k\":3,\"tx\":104.25,\"r\":12}],[{\"k\":3,\"tx\":105.75,\"r\":16}],[{\"k\":3,\"tx\":107.25,\"r\":20}],[{\"k\":3,\"tx\":108.75,\"r\":24}],[{\"k\":3,\"tx\":110.25,\"r\":28}],[{\"k\":3,\"tx\":111.75,\"r\":32}],[{\"k\":3,\"tx\":113.25,\"r\":36}],[{\"k\":3,\"tx\":114.75,\"r\":40}],[{\"k\":3,\"tx\":116.25,\"r\":44}],[{\"k\":3,\"tx\":117.75,\"r\":48}],[{\"k\":3,\"tx\":119.25,\"r\":52}],[{\"k\":3,\"tx\":120.75,\"r\":56}],[{\"k\":3,\"tx\":122.25,\"r\":60}],[{\"k\":3,\"tx\":123.81,\"ty\":120.11}],[{\"k\":3,\"tx\":125.44,\"ty\":120.32}],[{\"k\":3,\"tx\":127.13,\"ty\":120.65}],[{\"k\":3,\"tx\":128.88,\"ty\":121.08}],[{\"k\":3,\"tx\":130.69,\"ty\":121.62}],[{\"k\":3,\"tx\":132.56,\"ty\":122.27}],[{\"k\":3,\"tx\":134.5,\"ty\":123.03}],[{\"k\":3,\"tx\":136.5,\"ty\":123.9}],[{\"k\":3,\"tx\":138.56,\"ty\":124.87}],[{\"k\":3,\"tx\":140.69,\"ty\":125.95}],[{\"k\":3,\"tx\":142.81,\"ty\":127.04}],[{\"k\":3,\"tx\":144.94,\"ty\":128.12}],[{\"k\":3,\"tx\":147.06,\"ty\":129.2}],[{\"k\":3,\"tx\":149.19,\"ty\":130.28}],[{\"k\":3,\"tx\":151.31,\"ty\":131.37}],[{\"k\":3,\"tx\":153.44,\"ty\":132.45}],[{\"k\":3,\"tx\":155.56,\"ty\":133.53}],[{\"k\":3,\"tx\":157.69,\"ty\":134.61}],[{\"k\":3,\"tx\":159.81,\"ty\":135.7}],[{\"k\":3,\"tx\":161.94,\"ty\":136.78}],[{\"k\":3,\"tx\":164.06,\"ty\":137.86}],[{\"k\":3,\"tx\":166.19,\"ty\":138.94}],[{\"k\":3,\"tx\":168.31,\"ty\":140.03}],[{\"k\":3,\"tx\":170.44,\"ty\":141.11}],[{\"k\":3,\"tx\":172.56,\"ty\":142.19}],[{\"k\":3,\"tx\":174.69,\"ty\":143.27}],[{\"k\":3,\"tx\":176.81,\"ty\":144.36}],[{\"k\":3,\"tx\":178.94,\"ty\":145.44}],[{\"k\":3,\"tx\":181.06,\"ty\":146.52}],[{\"k\":3,\"tx\":183.19,\"ty\":147.6}],[{\"k\":3,\"tx\":185.31,\"ty\":148.69}],[{\"k\":3,\"tx\":187.44,\"ty\":149.77}],[{\"k\":3,\"tx\":189.56,\"ty\":150.85}],[{\"k\":3,\"tx\":191.69,\"ty\":151.93}],[{\"k\":3,\"tx\":193.81,\"ty\":153.02}],[{\"k\":3,\"tx\":195.94,\"ty\":154.1}],[{\"k\":3,\"tx\":198.06,\"ty\":155.18}],[{\"k\":3,\"tx\":200.19,\"ty\":156.26}],[{\"k\":3,\"tx\":202.31,\"ty\":157.35}],[{\"k\":3,\"tx\":204.44,\"ty\":158.43}],[{\"k\":3,\"tx\":206.56,\"ty\":159.51}],[{\"k\":3,\"tx\":208.69,\"ty\":160.59}],[{\"k\":3,\"tx\":210.81,\"ty\":161.68}],[{\"k\":3,\"tx\":212.94,\"ty\":162.76}],[{\"k\":3,\"tx\":215.06,\"ty\":163.84}],[{\"k\":3,\"tx\":217.19,\"ty\":164.93}],[{\"k\":3,\"tx\":219.31,\"ty\":166.01}],[{\"k\":3,\"tx\":221.44,\"ty\":167.09}],[{\"k\":3,\"tx\":223.56,\"ty\":168.17}],[{\"k\":3,\"tx\":225.69,\"ty\":169.26}]]}},\"outline\":\"Window \\\"Engines\\\"\\n  Pane 360 x 240\\n    Rectangle 360 x 240 at (0, 0), fill #191970\\n    Polygon (-15, -6) (9, -6) (15, 0) (9, 6) (-15, 6), at (225.6875, 169.2551948402401), rotate 60, fill #ffd700\\nframes run: 97\"}\n```\n\nThe submarine speeds up to the right for 12 frames (to 1.5 pixels a frame), coasts for 20, turns 60 degrees clockwise, and pushes for 10 frames that way: the velocity becomes the old one plus the new pushes, so it heads down and to the right, but less steeply than its nose. Let it run longer and it leaves the window: step 6 brings it back from the other side.\n\nThe numbers in the window as text get long, such as the y of `at (225.6875, 169.2551948402401)` above: cos and sin of most angles have endless decimals. That's fine, since the same calculation in the same order always gives exactly the same number. It's also why the tasks say exactly how to calculate: `Math.toRadians` and the order in which things are added. (`Math.cos(Math.toRadians(90))` isn't exactly 0 either, but a tiny number of about 0.00000000000000006, because the angle in radians can't be stored exactly.)\n","task":"The movement without a window. The class `Motion` keeps a position (x, y) and a velocity (vx, vy), all doubles that start at the given position and a velocity of (0, 0); its `toString` is written for you and rounds to two decimals. Write its two methods:\n\n- `push(double degrees, double amount)` adds to the velocity a step of length `amount` in the direction `degrees`: `Math.cos` of the angle in radians times `amount` to vx, and `Math.sin` of it times `amount` to vy.\n- `step()` adds the velocity to the position: vx to x and vy to y.\n\nThe first test runs this:\n\n```java\nMotion m = new Motion(10, 20);\nm.push(0, 2);\nm.step();\nm.step();\nSystem.out.println(m);\nm.push(90, 1);\nm.step();\nSystem.out.println(m);\n```\n\n```\nat (14.0, 20.0), moving (2.0, 0.0)\nat (16.0, 21.0), moving (2.0, 1.0)\n```\n","kind":"code","seed":"// ==== Motion.java ====\npublic class Motion {\n    private double x;\n    private double y;\n    private double vx;\n    private double vy;\n\n    public Motion(double x, double y) {\n        this.x = x;\n        this.y = y;\n    }\n\n    public void push(double degrees, double amount) {\n\n    }\n\n    public void step() {\n\n    }\n\n    @Override\n    public String toString() {\n        return \"at (\" + rounded(x) + \", \" + rounded(y) + \"), moving (\" + rounded(vx) + \", \" + rounded(vy) + \")\";\n    }\n\n    private static double rounded(double value) {\n        return Math.round(value * 100) / 100.0;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Motion m = new Motion(10, 20);\n        m.push(0, 2);\n        m.step();\n        m.step();\n        System.out.println(m);\n        m.push(90, 1);\n        m.step();\n        System.out.println(m);\n    }\n}\n","solution":"// ==== Motion.java ====\npublic class Motion {\n    private double x;\n    private double y;\n    private double vx;\n    private double vy;\n\n    public Motion(double x, double y) {\n        this.x = x;\n        this.y = y;\n    }\n\n    public void push(double degrees, double amount) {\n        double angle = Math.toRadians(degrees);\n        vx = vx + Math.cos(angle) * amount;\n        vy = vy + Math.sin(angle) * amount;\n    }\n\n    public void step() {\n        x = x + vx;\n        y = y + vy;\n    }\n\n    @Override\n    public String toString() {\n        return \"at (\" + rounded(x) + \", \" + rounded(y) + \"), moving (\" + rounded(vx) + \", \" + rounded(vy) + \")\";\n    }\n\n    private static double rounded(double value) {\n        return Math.round(value * 100) / 100.0;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Motion m = new Motion(10, 20);\n        m.push(0, 2);\n        m.step();\n        m.step();\n        System.out.println(m);\n        m.push(90, 1);\n        m.step();\n        System.out.println(m);\n    }\n}\n","hints":["`push` turns the degrees into radians with `Math.toRadians`, then adds cos times the amount to vx and sin times the amount to vy. `step` adds the velocity to the position.","In `push`: `double angle = Math.toRadians(degrees);`, then `vx = vx + Math.cos(angle) * amount;` and the same for vy with `Math.sin`. In `step`: `x = x + vx;` and `y = y + vy;`."],"tests":[{"name":"Test 1","stdin":"","call":"Motion m = new Motion(10, 20);\nm.push(0, 2);\nm.step();\nm.step();\nSystem.out.println(m);\nm.push(90, 1);\nm.step();\nSystem.out.println(m);\n","expect":"at (14.0, 20.0), moving (2.0, 0.0)\nat (16.0, 21.0), moving (2.0, 1.0)","hidden":false},{"name":"Test 2","stdin":"","call":"Motion m = new Motion(0, 0);\nm.push(45, 1);\nm.step();\nm.step();\nSystem.out.println(m);\nm.push(180, 3);\nm.step();\nSystem.out.println(m);\n","expect":"at (1.41, 1.41), moving (0.71, 0.71)\nat (-0.88, 2.12), moving (-2.29, 0.71)","hidden":true},{"name":"Test 3","stdin":"","call":"Motion m = new Motion(100, 50);\nm.push(-90, 0.5);\nm.push(-90, 0.5);\nfor (int i = 0; i < 5; i++) {\n    m.step();\n}\nSystem.out.println(m);\n","expect":"at (100.0, 45.0), moving (0.0, -1.0)","hidden":true}],"require":[],"forbid":[],"more":[{"task":"Give the submarine its engine. The starter code turns it with Left and Right (4 degrees a frame). Add a field `velocity`, a `Point2D` that starts at (0, 0), and in every frame, after the turning:\n\n1. if Up is held, add to the velocity a push of 0.125 in the direction of the submarine's rotate: `Math.cos` and `Math.sin` of `Math.toRadians(sub.getRotate())`, each times 0.125, added with `velocity.add(...)`\n2. then move the submarine by its velocity: add its x to the translate x and its y to the translate y\n\nThe test holds Up for 16 frames and then runs 24 frames more: the submarine reaches 2 pixels a frame and ends at (245, 120).\n","kind":"code","seed":"import java.util.HashMap;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\nimport javafx.scene.shape.Rectangle;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n    private Map<KeyCode, Boolean> held = new HashMap<>();\n\n    @Override\n    public void start(Stage stage) {\n        Pane pane = new Pane();\n        pane.setPrefSize(360, 240);\n        Polygon sub = new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6);\n        sub.setFill(Color.GOLD);\n        sub.setTranslateX(180);\n        sub.setTranslateY(120);\n        pane.getChildren().addAll(new Rectangle(360, 240, Color.MIDNIGHTBLUE), sub);\n\n        Scene scene = new Scene(pane);\n        scene.setOnKeyPressed(event -> held.put(event.getCode(), true));\n        scene.setOnKeyReleased(event -> held.put(event.getCode(), false));\n\n        new AnimationTimer() {\n            @Override\n            public void handle(long now) {\n                if (held.getOrDefault(KeyCode.LEFT, false)) {\n                    sub.setRotate(sub.getRotate() - 4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.setRotate(sub.getRotate() + 4);\n                }\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Engines\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import java.util.HashMap;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\nimport javafx.scene.shape.Rectangle;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n    private Map<KeyCode, Boolean> held = new HashMap<>();\n    private Point2D velocity = new Point2D(0, 0);\n\n    @Override\n    public void start(Stage stage) {\n        Pane pane = new Pane();\n        pane.setPrefSize(360, 240);\n        Polygon sub = new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6);\n        sub.setFill(Color.GOLD);\n        sub.setTranslateX(180);\n        sub.setTranslateY(120);\n        pane.getChildren().addAll(new Rectangle(360, 240, Color.MIDNIGHTBLUE), sub);\n\n        Scene scene = new Scene(pane);\n        scene.setOnKeyPressed(event -> held.put(event.getCode(), true));\n        scene.setOnKeyReleased(event -> held.put(event.getCode(), false));\n\n        new AnimationTimer() {\n            @Override\n            public void handle(long now) {\n                if (held.getOrDefault(KeyCode.LEFT, false)) {\n                    sub.setRotate(sub.getRotate() - 4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.setRotate(sub.getRotate() + 4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    double angle = Math.toRadians(sub.getRotate());\n                    velocity = velocity.add(Math.cos(angle) * 0.125, Math.sin(angle) * 0.125);\n                }\n                sub.setTranslateX(sub.getTranslateX() + velocity.getX());\n                sub.setTranslateY(sub.getTranslateY() + velocity.getY());\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Engines\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["The velocity must be a field, `private Point2D velocity = new Point2D(0, 0);`, since the timer's code changes it. Point2D is immutable: `velocity = velocity.add(...)` keeps the new point.","In `handle`, after the turning: `if (held.getOrDefault(KeyCode.UP, false)) { double angle = Math.toRadians(sub.getRotate()); velocity = velocity.add(Math.cos(angle) * 0.125, Math.sin(angle) * 0.125); }`, then `sub.setTranslateX(sub.getTranslateX() + velocity.getX());` and the same for y."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["press key UP","frames 16","release key UP","frames 24"],"window":{"windows":[{"title":"Engines","scene":{"width":360,"height":240,"onKeyPressed":true,"onKeyReleased":true,"root":{"n":1,"type":"Pane","f":1,"prefWidth":360,"prefHeight":240,"children":[{"n":2,"type":"Rectangle","f":2,"x":0,"y":0,"width":360,"height":240,"fill":"#191970"},{"n":3,"type":"Polygon","f":3,"translateX":245,"translateY":120,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"}]}}}],"problems":[],"frames":{"count":40,"list":[]}},"outline":"Window \"Engines\"\n  Pane 360 x 240\n    Rectangle 360 x 240 at (0, 0), fill #191970\n    Polygon (-15, -6) (9, -6) (15, 0) (9, 6) (-15, 6), at (245, 120), fill #ffd700\nframes run: 40"},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":["press key RIGHT","frames 10","release key RIGHT","press key UP","frames 20","release key UP","frames 30"],"window":{"windows":[{"title":"Engines","scene":{"width":360,"height":240,"onKeyPressed":true,"onKeyReleased":true,"root":{"n":1,"type":"Pane","f":1,"prefWidth":360,"prefHeight":240,"children":[{"n":2,"type":"Rectangle","f":2,"x":0,"y":0,"width":360,"height":240,"fill":"#191970"},{"n":3,"type":"Polygon","f":3,"translateX":257.5619998657968,"translateY":185.08224548076188,"rotate":40,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"}]}}}],"problems":[],"frames":{"count":60,"list":[]}},"outline":"Window \"Engines\"\n  Pane 360 x 240\n    Rectangle 360 x 240 at (0, 0), fill #191970\n    Polygon (-15, -6) (9, -6) (15, 0) (9, 6) (-15, 6), at (257.5619998657968, 185.08224548076188), rotate 40, fill #ffd700\nframes run: 60"},{"name":"Test 3","stdin":"","expect":"","hidden":true,"events":["press key LEFT","frames 45","press key UP","frames 8","release key LEFT","release key UP","frames 10"],"window":{"windows":[{"title":"Engines","scene":{"width":360,"height":240,"onKeyPressed":true,"onKeyReleased":true,"root":{"n":1,"type":"Pane","f":1,"prefWidth":360,"prefHeight":240,"children":[{"n":2,"type":"Rectangle","f":2,"x":0,"y":0,"width":360,"height":240,"fill":"#191970"},{"n":3,"type":"Polygon","f":3,"translateX":166.2730393829394,"translateY":124.07766695757442,"rotate":-212,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"}]}}}],"problems":[],"frames":{"count":63,"list":[]}},"outline":"Window \"Engines\"\n  Pane 360 x 240\n    Rectangle 360 x 240 at (0, 0), fill #191970\n    Polygon (-15, -6) (9, -6) (15, 0) (9, 6) (-15, 6), at (166.2730393829394, 124.07766695757442), rotate -212, fill #ffd700\nframes run: 63"}],"require":[],"forbid":[]},{"task":"What does this program print?\n","kind":"predict","seed":"import javafx.geometry.Point2D;\n\npublic class Main {\n    public static void main(String[] args) {\n        Point2D speed = new Point2D(2, 0);\n        speed.add(1, 1);\n        Point2D faster = speed.multiply(3);\n        System.out.println(speed.getX() + \" \" + faster.getX());\n        System.out.println(faster.add(0, 8).magnitude());\n    }\n}\n","solution":"import javafx.geometry.Point2D;\n\npublic class Main {\n    public static void main(String[] args) {\n        Point2D speed = new Point2D(2, 0);\n        speed.add(1, 1);\n        Point2D faster = speed.multiply(3);\n        System.out.println(speed.getX() + \" \" + faster.getX());\n        System.out.println(faster.add(0, 8).magnitude());\n    }\n}\n","hints":["A Point2D never changes: `speed.add(1, 1)` returns a new point, and nobody keeps it. So `speed` is still (2, 0).","`faster` is (6, 0). Adding (0, 8) gives (6, 8), and its length is the square root of 36 + 64."],"tests":[{"name":"Output","stdin":"","expect":"2.0 6.0\n10.0","hidden":false}],"require":[],"forbid":[],"lines":["2.0 6.0","10.0"]}]}