{"id":"asteroids-movers","slug":"movers","title":"Submarine and jellyfish share a class","text":"The sea needs jellyfish, and they move too: each drifts slowly in its own direction and spins a little as it goes. A jellyfish needs a shape, a velocity, turning, speeding up and moving, everything the submarine has. Instead of writing all of it twice, this step puts it in one **abstract class**, as in module 34 and the modules after it: the submarine and the jellyfish both **extend** it.\n\n### The class Mover\n\n\n<figure class=\"uml\"><div class=\"uml-scroll\" tabindex=\"0\" role=\"group\" aria-label=\"Class diagram of Mover, Sub and Jellyfish\">\n<svg class=\"uml-svg\" viewBox=\"0 0 615 298\" width=\"615\" height=\"298\" style=\"width:38.44rem;min-width:35.24rem;aspect-ratio:615/298\" font-size=\"12\" aria-hidden=\"true\" focusable=\"false\"><path class=\"u-edge\" fill=\"none\" d=\"M266.3 185L266.3 196L108 196L108 220\"/><path class=\"u-edge\" fill=\"none\" d=\"M266.3 185L266.3 196L424.5 196L424.5 220\"/><path class=\"u-tri\" d=\"M266.3 174L259.3 185L273.3 185Z\"/><rect class=\"u-bg\" fill=\"none\" x=\"106.8\" y=\"6\" width=\"319\" height=\"168\"/><rect class=\"u-head\" fill=\"none\" x=\"106.8\" y=\"6\" width=\"319\" height=\"40\"/><text x=\"266.3\" y=\"22\" text-anchor=\"middle\" class=\"u-st\">«abstract»</text><text x=\"266.3\" y=\"38\" text-anchor=\"middle\" class=\"u-name u-it\">Mover</text><text x=\"114.8\" y=\"62\">-shape: Shape</text><text x=\"114.8\" y=\"78\">-velocity: Point2D</text><text x=\"114.8\" y=\"102\">+Mover(shape: Shape, x: double, y: double)</text><text x=\"114.8\" y=\"118\">+getShape(): Shape</text><text x=\"114.8\" y=\"134\">+turn(degrees: double): void</text><text x=\"114.8\" y=\"150\">+accelerate(amount: double): void</text><text x=\"114.8\" y=\"166\">+move(): void</text><path class=\"u-sep\" fill=\"none\" d=\"M106.8 46h319M106.8 86h319\"/><rect class=\"u-frame\" fill=\"none\" x=\"106.8\" y=\"6\" width=\"319\" height=\"168\"/><rect class=\"u-bg\" fill=\"none\" x=\"6\" y=\"220\" width=\"204\" height=\"56\"/><rect class=\"u-head\" fill=\"none\" x=\"6\" y=\"220\" width=\"204\" height=\"24\"/><text x=\"108\" y=\"236\" text-anchor=\"middle\" class=\"u-name\">Sub</text><text x=\"14\" y=\"268\">+Sub(x: double, y: double)</text><path class=\"u-sep\" fill=\"none\" d=\"M6 244h204M6 252h204\"/><rect class=\"u-frame\" fill=\"none\" x=\"6\" y=\"220\" width=\"204\" height=\"56\"/><rect class=\"u-bg\" fill=\"none\" x=\"240\" y=\"220\" width=\"369\" height=\"72\"/><rect class=\"u-head\" fill=\"none\" x=\"240\" y=\"220\" width=\"369\" height=\"24\"/><text x=\"424.5\" y=\"236\" text-anchor=\"middle\" class=\"u-name\">Jellyfish</text><text x=\"248\" y=\"268\">+Jellyfish(x: double, y: double, heading: double)</text><text x=\"248\" y=\"284\">+move(): void</text><path class=\"u-sep\" fill=\"none\" d=\"M240 244h369M240 252h369\"/><rect class=\"u-frame\" fill=\"none\" x=\"240\" y=\"220\" width=\"369\" height=\"72\"/></svg>\n</div><details class=\"uml-text\"><summary>The diagram as text</summary><ul><li><strong>Mover</strong>, an abstract class<ul><li>private field <code>shape: Shape</code></li><li>private field <code>velocity: Point2D</code></li><li>public constructor <code>Mover(shape: Shape, x: double, y: double)</code></li><li>public method <code>getShape(): Shape</code></li><li>public method <code>turn(degrees: double): void</code></li><li>public method <code>accelerate(amount: double): void</code></li><li>public method <code>move(): void</code></li></ul></li><li><strong>Sub</strong>, a class that extends Mover<ul><li>public constructor <code>Sub(x: double, y: double)</code></li></ul></li><li><strong>Jellyfish</strong>, a class that extends Mover<ul><li>public constructor <code>Jellyfish(x: double, y: double, heading: double)</code></li><li>public method <code>move(): void</code></li></ul></li></ul></details></figure>\n\n\n- A `Mover` holds a **Shape**: the class `Shape` (from `javafx.scene.shape`) is what Polygon, Circle and Rectangle all extend, so a Mover can be a polygon (the submarine, a jellyfish) or a circle (the bubbles of step 7).\n- Its constructor gets the shape and where to put it, and the velocity starts at (0, 0).\n- `turn(degrees)` adds to the shape's rotate, `accelerate(amount)` pushes the velocity in the direction of the rotate (the code of step 3, with the amount as a parameter), and `move()` adds the velocity to the translate.\n- `getShape()` gives the shape to whoever adds it to the pane.\n- Mover is **abstract**: `new Mover(...)` doesn't compile. Only its subclasses are made, and each one decides what its shape looks like.\n\n```java\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\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        pane.getChildren().add(new Rectangle(360, 240, Color.MIDNIGHTBLUE));\n\n        Sub sub = new Sub(60, 120);\n        pane.getChildren().add(sub.getShape());\n        List<Jellyfish> jellyfish = new ArrayList<>();\n        jellyfish.add(new Jellyfish(250, 60, 120));\n        jellyfish.add(new Jellyfish(300, 200, 200));\n        jellyfish.forEach(j -> pane.getChildren().add(j.getShape()));\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.turn(-4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.turn(4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    sub.accelerate(0.125);\n                }\n                sub.move();\n                jellyfish.forEach(j -> j.move());\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Deep dive\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n// ==== Mover.java ====\nimport javafx.geometry.Point2D;\nimport javafx.scene.shape.Shape;\n\npublic abstract class Mover {\n    private Shape shape;\n    private Point2D velocity;\n\n    public Mover(Shape shape, double x, double y) {\n        this.shape = shape;\n        this.shape.setTranslateX(x);\n        this.shape.setTranslateY(y);\n        this.velocity = new Point2D(0, 0);\n    }\n\n    public Shape getShape() {\n        return shape;\n    }\n\n    public void turn(double degrees) {\n        shape.setRotate(shape.getRotate() + degrees);\n    }\n\n    public void accelerate(double amount) {\n        double angle = Math.toRadians(shape.getRotate());\n        velocity = velocity.add(Math.cos(angle) * amount, Math.sin(angle) * amount);\n    }\n\n    public void move() {\n        shape.setTranslateX(shape.getTranslateX() + velocity.getX());\n        shape.setTranslateY(shape.getTranslateY() + velocity.getY());\n    }\n}\n\n// ==== Sub.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Sub extends Mover {\n\n    public Sub(double x, double y) {\n        super(new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6), x, y);\n        getShape().setFill(Color.GOLD);\n    }\n}\n\n// ==== Jellyfish.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Jellyfish extends Mover {\n\n    public Jellyfish(double x, double y, double heading) {\n        super(new Polygon(-10, 0, -6, -8, 0, -10, 6, -8, 10, 0, 7, 10, 3, 2, 0, 10, -3, 2, -7, 10), x, y);\n        getShape().setFill(Color.ORCHID);\n        turn(heading);\n        accelerate(0.5);\n    }\n\n    @Override\n    public void move() {\n        super.move();\n        turn(1);\n    }\n}\n```\n\n```window\n{\"events\":[\"press key UP\",\"frames 10\",\"release key UP\",\"frames 60\"],\"window\":{\"windows\":[{\"title\":\"Deep dive\",\"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\":141.875,\"translateY\":120,\"points\":[-15,-6,9,-6,15,0,9,6,-15,6],\"fill\":\"#ffd700\"},{\"n\":4,\"type\":\"Polygon\",\"f\":4,\"translateX\":232.5,\"translateY\":90.31088913245561,\"rotate\":190,\"points\":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],\"fill\":\"#da70d6\"},{\"n\":5,\"type\":\"Polygon\",\"f\":5,\"translateX\":267.1107582724943,\"translateY\":188.02929498360157,\"rotate\":270,\"points\":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],\"fill\":\"#da70d6\"}]}}}],\"problems\":[],\"frames\":{\"count\":70,\"list\":[[{\"roots\":[1]},{\"k\":1,\"type\":\"Pane\",\"children\":[2,3,4,5]},{\"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\":4,\"type\":\"Polygon\",\"tx\":249.75,\"ty\":60.43,\"r\":121,\"points\":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],\"fill\":\"#da70d6\"},{\"k\":5,\"type\":\"Polygon\",\"tx\":299.53,\"ty\":199.83,\"r\":201,\"points\":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],\"fill\":\"#da70d6\"}],[{\"k\":3,\"tx\":60.38},{\"k\":4,\"tx\":249.5,\"ty\":60.87,\"r\":122},{\"k\":5,\"tx\":299.06,\"ty\":199.66,\"r\":202}],[{\"k\":3,\"tx\":60.75},{\"k\":4,\"tx\":249.25,\"ty\":61.3,\"r\":123},{\"k\":5,\"tx\":298.59,\"ty\":199.49,\"r\":203}],[{\"k\":3,\"tx\":61.25},{\"k\":4,\"tx\":249,\"ty\":61.73,\"r\":124},{\"k\":5,\"tx\":298.12,\"ty\":199.32,\"r\":204}],[{\"k\":3,\"tx\":61.88},{\"k\":4,\"tx\":248.75,\"ty\":62.17,\"r\":125},{\"k\":5,\"tx\":297.65,\"ty\":199.14,\"r\":205}],[{\"k\":3,\"tx\":62.63},{\"k\":4,\"tx\":248.5,\"ty\":62.6,\"r\":126},{\"k\":5,\"tx\":297.18,\"ty\":198.97,\"r\":206}],[{\"k\":3,\"tx\":63.5},{\"k\":4,\"tx\":248.25,\"ty\":63.03,\"r\":127},{\"k\":5,\"tx\":296.71,\"ty\":198.8,\"r\":207}],[{\"k\":3,\"tx\":64.5},{\"k\":4,\"tx\":248,\"ty\":63.46,\"r\":128},{\"k\":5,\"tx\":296.24,\"ty\":198.63,\"r\":208}],[{\"k\":3,\"tx\":65.63},{\"k\":4,\"tx\":247.75,\"ty\":63.9,\"r\":129},{\"k\":5,\"tx\":295.77,\"ty\":198.46,\"r\":209}],[{\"k\":3,\"tx\":66.88},{\"k\":4,\"tx\":247.5,\"ty\":64.33,\"r\":130},{\"k\":5,\"tx\":295.3,\"ty\":198.29,\"r\":210}],[{\"k\":3,\"tx\":68.13},{\"k\":4,\"tx\":247.25,\"ty\":64.76,\"r\":131},{\"k\":5,\"tx\":294.83,\"ty\":198.12,\"r\":211}],[{\"k\":3,\"tx\":69.38},{\"k\":4,\"tx\":247,\"ty\":65.2,\"r\":132},{\"k\":5,\"tx\":294.36,\"ty\":197.95,\"r\":212}],[{\"k\":3,\"tx\":70.63},{\"k\":4,\"tx\":246.75,\"ty\":65.63,\"r\":133},{\"k\":5,\"tx\":293.89,\"ty\":197.78,\"r\":213}],[{\"k\":3,\"tx\":71.88},{\"k\":4,\"tx\":246.5,\"ty\":66.06,\"r\":134},{\"k\":5,\"tx\":293.42,\"ty\":197.61,\"r\":214}],[{\"k\":3,\"tx\":73.13},{\"k\":4,\"tx\":246.25,\"ty\":66.5,\"r\":135},{\"k\":5,\"tx\":292.95,\"ty\":197.43,\"r\":215}],[{\"k\":3,\"tx\":74.38},{\"k\":4,\"tx\":246,\"ty\":66.93,\"r\":136},{\"k\":5,\"tx\":292.48,\"ty\":197.26,\"r\":216}],[{\"k\":3,\"tx\":75.63},{\"k\":4,\"tx\":245.75,\"ty\":67.36,\"r\":137},{\"k\":5,\"tx\":292.01,\"ty\":197.09,\"r\":217}],[{\"k\":3,\"tx\":76.88},{\"k\":4,\"tx\":245.5,\"ty\":67.79,\"r\":138},{\"k\":5,\"tx\":291.54,\"ty\":196.92,\"r\":218}],[{\"k\":3,\"tx\":78.13},{\"k\":4,\"tx\":245.25,\"ty\":68.23,\"r\":139},{\"k\":5,\"tx\":291.07,\"ty\":196.75,\"r\":219}],[{\"k\":3,\"tx\":79.38},{\"k\":4,\"tx\":245,\"ty\":68.66,\"r\":140},{\"k\":5,\"tx\":290.6,\"ty\":196.58,\"r\":220}],[{\"k\":3,\"tx\":80.63},{\"k\":4,\"tx\":244.75,\"ty\":69.09,\"r\":141},{\"k\":5,\"tx\":290.13,\"ty\":196.41,\"r\":221}],[{\"k\":3,\"tx\":81.88},{\"k\":4,\"tx\":244.5,\"ty\":69.53,\"r\":142},{\"k\":5,\"tx\":289.66,\"ty\":196.24,\"r\":222}],[{\"k\":3,\"tx\":83.13},{\"k\":4,\"tx\":244.25,\"ty\":69.96,\"r\":143},{\"k\":5,\"tx\":289.19,\"ty\":196.07,\"r\":223}],[{\"k\":3,\"tx\":84.38},{\"k\":4,\"tx\":244,\"ty\":70.39,\"r\":144},{\"k\":5,\"tx\":288.72,\"ty\":195.9,\"r\":224}],[{\"k\":3,\"tx\":85.63},{\"k\":4,\"tx\":243.75,\"ty\":70.83,\"r\":145},{\"k\":5,\"tx\":288.25,\"ty\":195.72,\"r\":225}],[{\"k\":3,\"tx\":86.88},{\"k\":4,\"tx\":243.5,\"ty\":71.26,\"r\":146},{\"k\":5,\"tx\":287.78,\"ty\":195.55,\"r\":226}],[{\"k\":3,\"tx\":88.13},{\"k\":4,\"tx\":243.25,\"ty\":71.69,\"r\":147},{\"k\":5,\"tx\":287.31,\"ty\":195.38,\"r\":227}],[{\"k\":3,\"tx\":89.38},{\"k\":4,\"tx\":243,\"ty\":72.12,\"r\":148},{\"k\":5,\"tx\":286.84,\"ty\":195.21,\"r\":228}],[{\"k\":3,\"tx\":90.63},{\"k\":4,\"tx\":242.75,\"ty\":72.56,\"r\":149},{\"k\":5,\"tx\":286.37,\"ty\":195.04,\"r\":229}],[{\"k\":3,\"tx\":91.88},{\"k\":4,\"tx\":242.5,\"ty\":72.99,\"r\":150},{\"k\":5,\"tx\":285.9,\"ty\":194.87,\"r\":230}],[{\"k\":3,\"tx\":93.13},{\"k\":4,\"tx\":242.25,\"ty\":73.42,\"r\":151},{\"k\":5,\"tx\":285.43,\"ty\":194.7,\"r\":231}],[{\"k\":3,\"tx\":94.38},{\"k\":4,\"tx\":242,\"ty\":73.86,\"r\":152},{\"k\":5,\"tx\":284.96,\"ty\":194.53,\"r\":232}],[{\"k\":3,\"tx\":95.63},{\"k\":4,\"tx\":241.75,\"ty\":74.29,\"r\":153},{\"k\":5,\"tx\":284.5,\"ty\":194.36,\"r\":233}],[{\"k\":3,\"tx\":96.88},{\"k\":4,\"tx\":241.5,\"ty\":74.72,\"r\":154},{\"k\":5,\"tx\":284.03,\"ty\":194.19,\"r\":234}],[{\"k\":3,\"tx\":98.13},{\"k\":4,\"tx\":241.25,\"ty\":75.16,\"r\":155},{\"k\":5,\"tx\":283.56,\"ty\":194.01,\"r\":235}],[{\"k\":3,\"tx\":99.38},{\"k\":4,\"tx\":241,\"ty\":75.59,\"r\":156},{\"k\":5,\"tx\":283.09,\"ty\":193.84,\"r\":236}],[{\"k\":3,\"tx\":100.63},{\"k\":4,\"tx\":240.75,\"ty\":76.02,\"r\":157},{\"k\":5,\"tx\":282.62,\"ty\":193.67,\"r\":237}],[{\"k\":3,\"tx\":101.88},{\"k\":4,\"tx\":240.5,\"ty\":76.45,\"r\":158},{\"k\":5,\"tx\":282.15,\"ty\":193.5,\"r\":238}],[{\"k\":3,\"tx\":103.13},{\"k\":4,\"tx\":240.25,\"ty\":76.89,\"r\":159},{\"k\":5,\"tx\":281.68,\"ty\":193.33,\"r\":239}],[{\"k\":3,\"tx\":104.38},{\"k\":4,\"tx\":240,\"ty\":77.32,\"r\":160},{\"k\":5,\"tx\":281.21,\"ty\":193.16,\"r\":240}],[{\"k\":3,\"tx\":105.63},{\"k\":4,\"tx\":239.75,\"ty\":77.75,\"r\":161},{\"k\":5,\"tx\":280.74,\"ty\":192.99,\"r\":241}],[{\"k\":3,\"tx\":106.88},{\"k\":4,\"tx\":239.5,\"ty\":78.19,\"r\":162},{\"k\":5,\"tx\":280.27,\"ty\":192.82,\"r\":242}],[{\"k\":3,\"tx\":108.13},{\"k\":4,\"tx\":239.25,\"ty\":78.62,\"r\":163},{\"k\":5,\"tx\":279.8,\"ty\":192.65,\"r\":243}],[{\"k\":3,\"tx\":109.38},{\"k\":4,\"tx\":239,\"ty\":79.05,\"r\":164},{\"k\":5,\"tx\":279.33,\"ty\":192.48,\"r\":244}],[{\"k\":3,\"tx\":110.63},{\"k\":4,\"tx\":238.75,\"ty\":79.49,\"r\":165},{\"k\":5,\"tx\":278.86,\"ty\":192.3,\"r\":245}],[{\"k\":3,\"tx\":111.88},{\"k\":4,\"tx\":238.5,\"ty\":79.92,\"r\":166},{\"k\":5,\"tx\":278.39,\"ty\":192.13,\"r\":246}],[{\"k\":3,\"tx\":113.13},{\"k\":4,\"tx\":238.25,\"ty\":80.35,\"r\":167},{\"k\":5,\"tx\":277.92,\"ty\":191.96,\"r\":247}],[{\"k\":3,\"tx\":114.38},{\"k\":4,\"tx\":238,\"ty\":80.78,\"r\":168},{\"k\":5,\"tx\":277.45,\"ty\":191.79,\"r\":248}],[{\"k\":3,\"tx\":115.63},{\"k\":4,\"tx\":237.75,\"ty\":81.22,\"r\":169},{\"k\":5,\"tx\":276.98,\"ty\":191.62,\"r\":249}],[{\"k\":3,\"tx\":116.88},{\"k\":4,\"tx\":237.5,\"ty\":81.65,\"r\":170},{\"k\":5,\"tx\":276.51,\"ty\":191.45,\"r\":250}],[{\"k\":3,\"tx\":118.13},{\"k\":4,\"tx\":237.25,\"ty\":82.08,\"r\":171},{\"k\":5,\"tx\":276.04,\"ty\":191.28,\"r\":251}],[{\"k\":3,\"tx\":119.38},{\"k\":4,\"tx\":237,\"ty\":82.52,\"r\":172},{\"k\":5,\"tx\":275.57,\"ty\":191.11,\"r\":252}],[{\"k\":3,\"tx\":120.63},{\"k\":4,\"tx\":236.75,\"ty\":82.95,\"r\":173},{\"k\":5,\"tx\":275.1,\"ty\":190.94,\"r\":253}],[{\"k\":3,\"tx\":121.88},{\"k\":4,\"tx\":236.5,\"ty\":83.38,\"r\":174},{\"k\":5,\"tx\":274.63,\"ty\":190.77,\"r\":254}],[{\"k\":3,\"tx\":123.13},{\"k\":4,\"tx\":236.25,\"ty\":83.82,\"r\":175},{\"k\":5,\"tx\":274.16,\"ty\":190.59,\"r\":255}],[{\"k\":3,\"tx\":124.38},{\"k\":4,\"tx\":236,\"ty\":84.25,\"r\":176},{\"k\":5,\"tx\":273.69,\"ty\":190.42,\"r\":256}],[{\"k\":3,\"tx\":125.63},{\"k\":4,\"tx\":235.75,\"ty\":84.68,\"r\":177},{\"k\":5,\"tx\":273.22,\"ty\":190.25,\"r\":257}],[{\"k\":3,\"tx\":126.88},{\"k\":4,\"tx\":235.5,\"ty\":85.11,\"r\":178},{\"k\":5,\"tx\":272.75,\"ty\":190.08,\"r\":258}],[{\"k\":3,\"tx\":128.13},{\"k\":4,\"tx\":235.25,\"ty\":85.55,\"r\":179},{\"k\":5,\"tx\":272.28,\"ty\":189.91,\"r\":259}],[{\"k\":3,\"tx\":129.38},{\"k\":4,\"tx\":235,\"ty\":85.98,\"r\":180},{\"k\":5,\"tx\":271.81,\"ty\":189.74,\"r\":260}],[{\"k\":3,\"tx\":130.63},{\"k\":4,\"tx\":234.75,\"ty\":86.41,\"r\":181},{\"k\":5,\"tx\":271.34,\"ty\":189.57,\"r\":261}],[{\"k\":3,\"tx\":131.88},{\"k\":4,\"tx\":234.5,\"ty\":86.85,\"r\":182},{\"k\":5,\"tx\":270.87,\"ty\":189.4,\"r\":262}],[{\"k\":3,\"tx\":133.13},{\"k\":4,\"tx\":234.25,\"ty\":87.28,\"r\":183},{\"k\":5,\"tx\":270.4,\"ty\":189.23,\"r\":263}],[{\"k\":3,\"tx\":134.38},{\"k\":4,\"tx\":234,\"ty\":87.71,\"r\":184},{\"k\":5,\"tx\":269.93,\"ty\":189.06,\"r\":264}],[{\"k\":3,\"tx\":135.63},{\"k\":4,\"tx\":233.75,\"ty\":88.15,\"r\":185},{\"k\":5,\"tx\":269.46,\"ty\":188.88,\"r\":265}],[{\"k\":3,\"tx\":136.88},{\"k\":4,\"tx\":233.5,\"ty\":88.58,\"r\":186},{\"k\":5,\"tx\":268.99,\"ty\":188.71,\"r\":266}],[{\"k\":3,\"tx\":138.13},{\"k\":4,\"tx\":233.25,\"ty\":89.01,\"r\":187},{\"k\":5,\"tx\":268.52,\"ty\":188.54,\"r\":267}],[{\"k\":3,\"tx\":139.38},{\"k\":4,\"tx\":233,\"ty\":89.44,\"r\":188},{\"k\":5,\"tx\":268.05,\"ty\":188.37,\"r\":268}],[{\"k\":3,\"tx\":140.63},{\"k\":4,\"tx\":232.75,\"ty\":89.88,\"r\":189},{\"k\":5,\"tx\":267.58,\"ty\":188.2,\"r\":269}],[{\"k\":3,\"tx\":141.88},{\"k\":4,\"tx\":232.5,\"ty\":90.31,\"r\":190},{\"k\":5,\"tx\":267.11,\"ty\":188.03,\"r\":270}]]}},\"outline\":\"Window \\\"Deep dive\\\"\\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 (141.875, 120), fill #ffd700\\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (232.5, 90.31088913245561), rotate 190, fill #da70d6\\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (267.1107582724943, 188.02929498360157), rotate 270, fill #da70d6\\nframes run: 70\"}\n```\n\n- `Sub`'s constructor calls `super(...)` with a new Polygon: the call to the parent's constructor must come first in a constructor, so the shape is made right inside it. Then it colors the shape, through `getShape()`, since `shape` is private in Mover.\n- `Jellyfish`'s constructor turns the new jellyfish to its heading and gives it a speed of 0.5 pixels a frame that way, once. It never speeds up again, so it drifts in a straight line.\n- `Jellyfish` **overrides** `move()`: `super.move()` runs Mover's own `move` (the drifting), and then the jellyfish turns 1 degree. So it spins slowly as it drifts. Turning doesn't change its direction of travel: that was set once, by `accelerate` in the constructor. The submarine doesn't override `move`, so it uses Mover's.\n- `Main` got shorter: the key handlers call `sub.turn(-4)`, `sub.turn(4)` and `sub.accelerate(0.125)`, and each frame moves the submarine and every jellyfish. `jellyfish.forEach(j -> j.move())` calls `move` on each one, as a for-each loop would.\n\nThe first jellyfish starts at (250, 60) heading 120 degrees, down and to the left; after 70 frames it has moved 35 pixels that way and turned 70 degrees. In the animation, each jellyfish spins a little in every frame while it drifts along its velocity.\n","task":"Write the class `Jellyfish`. The starter code calls `super(...)` with the jellyfish's polygon. Add to the constructor, after that: fill the shape with `Color.ORCHID`, turn it by `heading` degrees with `turn`, and accelerate it once by 0.5. Then override `move()` so that it first moves the jellyfish as a Mover does and then turns it 1 degree clockwise.\n\n`Main` makes a submarine at (180, 120) and two jellyfish, at (60, 60) heading 30 degrees and at (300, 180) heading 200 degrees, and moves them in every frame. The test runs 40 frames.\n","kind":"code","seed":"// ==== Jellyfish.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Jellyfish extends Mover {\n\n    public Jellyfish(double x, double y, double heading) {\n        super(new Polygon(-10, 0, -6, -8, 0, -10, 6, -8, 10, 0, 7, 10, 3, 2, 0, 10, -3, 2, -7, 10), x, y);\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\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        pane.getChildren().add(new Rectangle(360, 240, Color.MIDNIGHTBLUE));\n\n        Sub sub = new Sub(180, 120);\n        pane.getChildren().add(sub.getShape());\n        List<Jellyfish> jellyfish = new ArrayList<>();\n        jellyfish.add(new Jellyfish(60, 60, 30));\n        jellyfish.add(new Jellyfish(300, 180, 200));\n        jellyfish.forEach(j -> pane.getChildren().add(j.getShape()));\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.turn(-4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.turn(4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    sub.accelerate(0.125);\n                }\n                sub.move();\n                jellyfish.forEach(j -> j.move());\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Deep dive\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== Mover.java ====\nimport javafx.geometry.Point2D;\nimport javafx.scene.shape.Shape;\n\npublic abstract class Mover {\n    private Shape shape;\n    private Point2D velocity;\n\n    public Mover(Shape shape, double x, double y) {\n        this.shape = shape;\n        this.shape.setTranslateX(x);\n        this.shape.setTranslateY(y);\n        this.velocity = new Point2D(0, 0);\n    }\n\n    public Shape getShape() {\n        return shape;\n    }\n\n    public void turn(double degrees) {\n        shape.setRotate(shape.getRotate() + degrees);\n    }\n\n    public void accelerate(double amount) {\n        double angle = Math.toRadians(shape.getRotate());\n        velocity = velocity.add(Math.cos(angle) * amount, Math.sin(angle) * amount);\n    }\n\n    public void move() {\n        shape.setTranslateX(shape.getTranslateX() + velocity.getX());\n        shape.setTranslateY(shape.getTranslateY() + velocity.getY());\n    }\n}\n\n// ==== Sub.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Sub extends Mover {\n\n    public Sub(double x, double y) {\n        super(new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6), x, y);\n        getShape().setFill(Color.GOLD);\n    }\n}\n","solution":"// ==== Jellyfish.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Jellyfish extends Mover {\n\n    public Jellyfish(double x, double y, double heading) {\n        super(new Polygon(-10, 0, -6, -8, 0, -10, 6, -8, 10, 0, 7, 10, 3, 2, 0, 10, -3, 2, -7, 10), x, y);\n        getShape().setFill(Color.ORCHID);\n        turn(heading);\n        accelerate(0.5);\n    }\n\n    @Override\n    public void move() {\n        super.move();\n        turn(1);\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\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        pane.getChildren().add(new Rectangle(360, 240, Color.MIDNIGHTBLUE));\n\n        Sub sub = new Sub(180, 120);\n        pane.getChildren().add(sub.getShape());\n        List<Jellyfish> jellyfish = new ArrayList<>();\n        jellyfish.add(new Jellyfish(60, 60, 30));\n        jellyfish.add(new Jellyfish(300, 180, 200));\n        jellyfish.forEach(j -> pane.getChildren().add(j.getShape()));\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.turn(-4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.turn(4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    sub.accelerate(0.125);\n                }\n                sub.move();\n                jellyfish.forEach(j -> j.move());\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Deep dive\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== Mover.java ====\nimport javafx.geometry.Point2D;\nimport javafx.scene.shape.Shape;\n\npublic abstract class Mover {\n    private Shape shape;\n    private Point2D velocity;\n\n    public Mover(Shape shape, double x, double y) {\n        this.shape = shape;\n        this.shape.setTranslateX(x);\n        this.shape.setTranslateY(y);\n        this.velocity = new Point2D(0, 0);\n    }\n\n    public Shape getShape() {\n        return shape;\n    }\n\n    public void turn(double degrees) {\n        shape.setRotate(shape.getRotate() + degrees);\n    }\n\n    public void accelerate(double amount) {\n        double angle = Math.toRadians(shape.getRotate());\n        velocity = velocity.add(Math.cos(angle) * amount, Math.sin(angle) * amount);\n    }\n\n    public void move() {\n        shape.setTranslateX(shape.getTranslateX() + velocity.getX());\n        shape.setTranslateY(shape.getTranslateY() + velocity.getY());\n    }\n}\n\n// ==== Sub.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Sub extends Mover {\n\n    public Sub(double x, double y) {\n        super(new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6), x, y);\n        getShape().setFill(Color.GOLD);\n    }\n}\n","hints":["Mover already has `getShape`, `turn` and `accelerate`, and a Jellyfish has them too: call them in the constructor after `super(...)`. For the turning, override `move` and let Mover's own `move` do the moving first.","In the constructor: `getShape().setFill(Color.ORCHID);`, `turn(heading);` and `accelerate(0.5);`. The override: `@Override public void move() { super.move(); turn(1); }`. Without `super.`, `move()` would call itself forever."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["frames 40"],"window":{"windows":[{"title":"Deep dive","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":180,"translateY":120,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"},{"n":4,"type":"Polygon","f":4,"translateX":77.32050807568889,"translateY":70,"rotate":70,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":5,"type":"Polygon","f":5,"translateX":281.20614758428246,"translateY":173.1595971334866,"rotate":240,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"}]}}}],"problems":[],"frames":{"count":40,"list":[]}},"outline":"Window \"Deep dive\"\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 (180, 120), fill #ffd700\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (77.32050807568889, 70), rotate 70, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (281.20614758428246, 173.1595971334866), rotate 240, fill #da70d6\nframes run: 40"},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Deep dive","scene":{"width":360,"height":240,"onKeyPressed":true,"onKeyReleased":true,"root":{"n":1,"type":"Pane","prefWidth":360,"prefHeight":240,"children":[{"n":2,"type":"Rectangle","x":0,"y":0,"width":360,"height":240,"fill":"#191970"},{"n":3,"type":"Polygon","translateX":180,"translateY":120,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"},{"n":4,"type":"Polygon","translateX":60,"translateY":60,"rotate":30,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":5,"type":"Polygon","translateX":300,"translateY":180,"rotate":200,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"}]}}}],"problems":[]},"outline":"Window \"Deep dive\"\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 (180, 120), fill #ffd700\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (60, 60), rotate 30, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (300, 180), rotate 200, fill #da70d6"},{"name":"Test 3","stdin":"","expect":"","hidden":true,"events":["press key RIGHT","frames 20","release key RIGHT","press key UP","frames 30","release key UP","frames 100"],"window":{"windows":[{"title":"Deep dive","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":255.21136695198828,"translateY":546.5448580234133,"rotate":80,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"},{"n":4,"type":"Polygon","f":4,"translateX":124.95190528383354,"translateY":97.5,"rotate":180,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":5,"type":"Polygon","f":5,"translateX":229.5230534410576,"translateY":154.3484892505748,"rotate":350,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"}]}}}],"problems":[],"frames":{"count":150,"list":[]}},"outline":"Window \"Deep dive\"\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 (255.21136695198828, 546.5448580234133), rotate 80, fill #ffd700\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (124.95190528383354, 97.5), rotate 180, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (229.5230534410576, 154.3484892505748), rotate 350, fill #da70d6\nframes run: 150"}],"require":[],"forbid":[],"more":[{"task":"A crate drifts and a buoy bobs. What does this program print?\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        Drifter crate = new Drifter(\"crate\");\n        Drifter buoy = new Bobber(\"buoy\");\n        crate.move();\n        buoy.move();\n        buoy.move();\n    }\n}\n\n// ==== Drifter.java ====\npublic class Drifter {\n    private String name;\n    private int x;\n\n    public Drifter(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void move() {\n        x += 2;\n        System.out.println(name + \" at \" + x);\n    }\n}\n\n// ==== Bobber.java ====\npublic class Bobber extends Drifter {\n    private int bobs;\n\n    public Bobber(String name) {\n        super(name);\n    }\n\n    @Override\n    public void move() {\n        super.move();\n        bobs++;\n        System.out.println(getName() + \" bobbed \" + bobs + \" times\");\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        Drifter crate = new Drifter(\"crate\");\n        Drifter buoy = new Bobber(\"buoy\");\n        crate.move();\n        buoy.move();\n        buoy.move();\n    }\n}\n\n// ==== Drifter.java ====\npublic class Drifter {\n    private String name;\n    private int x;\n\n    public Drifter(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void move() {\n        x += 2;\n        System.out.println(name + \" at \" + x);\n    }\n}\n\n// ==== Bobber.java ====\npublic class Bobber extends Drifter {\n    private int bobs;\n\n    public Bobber(String name) {\n        super(name);\n    }\n\n    @Override\n    public void move() {\n        super.move();\n        bobs++;\n        System.out.println(getName() + \" bobbed \" + bobs + \" times\");\n    }\n}\n","hints":["`buoy` is a `Drifter` variable, but the object is a Bobber, so `buoy.move()` runs Bobber's `move`.","Bobber's `move` first runs Drifter's `move` with `super.move()` (which moves and prints), then counts and prints the bob. Each object has its own `x`."],"tests":[{"name":"Output","stdin":"","expect":"crate at 2\nbuoy at 2\nbuoy bobbed 1 times\nbuoy at 4\nbuoy bobbed 2 times","hidden":false}],"require":[],"forbid":[],"lines":["crate at 2","buoy at 2","buoy bobbed 1 times","buoy at 4","buoy bobbed 2 times"]},{"task":"Everything that moves, in one list. The starter code makes a submarine and three jellyfish and adds their shapes to the pane, but nothing moves yet. After the line that adds the shapes, make a `List<Mover>` called `movers` and add the submarine and the three jellyfish to it, in that order. In `handle`, after the keys, move every Mover of the list with one for-each loop.\n\nThe test holds Up for 8 frames and then runs 50 frames more.\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\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        pane.getChildren().add(new Rectangle(360, 240, Color.MIDNIGHTBLUE));\n\n        Sub sub = new Sub(180, 200);\n        Jellyfish first = new Jellyfish(40, 40, 0);\n        Jellyfish second = new Jellyfish(180, 40, 90);\n        Jellyfish third = new Jellyfish(320, 40, 180);\n        pane.getChildren().addAll(sub.getShape(), first.getShape(), second.getShape(), third.getShape());\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.turn(-4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.turn(4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    sub.accelerate(0.125);\n                }\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Everything moves\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== Mover.java ====\nimport javafx.geometry.Point2D;\nimport javafx.scene.shape.Shape;\n\npublic abstract class Mover {\n    private Shape shape;\n    private Point2D velocity;\n\n    public Mover(Shape shape, double x, double y) {\n        this.shape = shape;\n        this.shape.setTranslateX(x);\n        this.shape.setTranslateY(y);\n        this.velocity = new Point2D(0, 0);\n    }\n\n    public Shape getShape() {\n        return shape;\n    }\n\n    public void turn(double degrees) {\n        shape.setRotate(shape.getRotate() + degrees);\n    }\n\n    public void accelerate(double amount) {\n        double angle = Math.toRadians(shape.getRotate());\n        velocity = velocity.add(Math.cos(angle) * amount, Math.sin(angle) * amount);\n    }\n\n    public void move() {\n        shape.setTranslateX(shape.getTranslateX() + velocity.getX());\n        shape.setTranslateY(shape.getTranslateY() + velocity.getY());\n    }\n}\n\n// ==== Sub.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Sub extends Mover {\n\n    public Sub(double x, double y) {\n        super(new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6), x, y);\n        getShape().setFill(Color.GOLD);\n    }\n}\n\n// ==== Jellyfish.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Jellyfish extends Mover {\n\n    public Jellyfish(double x, double y, double heading) {\n        super(new Polygon(-10, 0, -6, -8, 0, -10, 6, -8, 10, 0, 7, 10, 3, 2, 0, 10, -3, 2, -7, 10), x, y);\n        getShape().setFill(Color.ORCHID);\n        turn(heading);\n        accelerate(0.5);\n    }\n\n    @Override\n    public void move() {\n        super.move();\n        turn(1);\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javafx.animation.AnimationTimer;\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\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        pane.getChildren().add(new Rectangle(360, 240, Color.MIDNIGHTBLUE));\n\n        Sub sub = new Sub(180, 200);\n        Jellyfish first = new Jellyfish(40, 40, 0);\n        Jellyfish second = new Jellyfish(180, 40, 90);\n        Jellyfish third = new Jellyfish(320, 40, 180);\n        pane.getChildren().addAll(sub.getShape(), first.getShape(), second.getShape(), third.getShape());\n        List<Mover> movers = new ArrayList<>();\n        movers.add(sub);\n        movers.add(first);\n        movers.add(second);\n        movers.add(third);\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.turn(-4);\n                }\n                if (held.getOrDefault(KeyCode.RIGHT, false)) {\n                    sub.turn(4);\n                }\n                if (held.getOrDefault(KeyCode.UP, false)) {\n                    sub.accelerate(0.125);\n                }\n                for (Mover mover : movers) {\n                    mover.move();\n                }\n            }\n        }.start();\n\n        stage.setScene(scene);\n        stage.setTitle(\"Everything moves\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== Mover.java ====\nimport javafx.geometry.Point2D;\nimport javafx.scene.shape.Shape;\n\npublic abstract class Mover {\n    private Shape shape;\n    private Point2D velocity;\n\n    public Mover(Shape shape, double x, double y) {\n        this.shape = shape;\n        this.shape.setTranslateX(x);\n        this.shape.setTranslateY(y);\n        this.velocity = new Point2D(0, 0);\n    }\n\n    public Shape getShape() {\n        return shape;\n    }\n\n    public void turn(double degrees) {\n        shape.setRotate(shape.getRotate() + degrees);\n    }\n\n    public void accelerate(double amount) {\n        double angle = Math.toRadians(shape.getRotate());\n        velocity = velocity.add(Math.cos(angle) * amount, Math.sin(angle) * amount);\n    }\n\n    public void move() {\n        shape.setTranslateX(shape.getTranslateX() + velocity.getX());\n        shape.setTranslateY(shape.getTranslateY() + velocity.getY());\n    }\n}\n\n// ==== Sub.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Sub extends Mover {\n\n    public Sub(double x, double y) {\n        super(new Polygon(-15, -6, 9, -6, 15, 0, 9, 6, -15, 6), x, y);\n        getShape().setFill(Color.GOLD);\n    }\n}\n\n// ==== Jellyfish.java ====\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Polygon;\n\npublic class Jellyfish extends Mover {\n\n    public Jellyfish(double x, double y, double heading) {\n        super(new Polygon(-10, 0, -6, -8, 0, -10, 6, -8, 10, 0, 7, 10, 3, 2, 0, 10, -3, 2, -7, 10), x, y);\n        getShape().setFill(Color.ORCHID);\n        turn(heading);\n        accelerate(0.5);\n    }\n\n    @Override\n    public void move() {\n        super.move();\n        turn(1);\n    }\n}\n","hints":["A Sub is a Mover and a Jellyfish is a Mover, so one `List<Mover> movers = new ArrayList<>();` can hold them all: `movers.add(sub);` and so on.","In `handle`, after the three key checks: `for (Mover mover : movers) { mover.move(); }`. Each object runs its own `move`: the jellyfish spin, the submarine doesn't."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["press key UP","frames 8","release key UP","frames 50"],"window":{"windows":[{"title":"Everything moves","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":234.5,"translateY":200,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"},{"n":4,"type":"Polygon","f":4,"translateX":69,"translateY":40,"rotate":58,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":5,"type":"Polygon","f":5,"translateX":180,"translateY":69,"rotate":148,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":6,"type":"Polygon","f":6,"translateX":291,"translateY":40,"rotate":238,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"}]}}}],"problems":[],"frames":{"count":58,"list":[]}},"outline":"Window \"Everything moves\"\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 (234.5, 200), fill #ffd700\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (69, 40), rotate 58, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (180, 69), rotate 148, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (291, 40), rotate 238, fill #da70d6\nframes run: 58"},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":["press key LEFT","frames 10","press key UP","frames 12","release key LEFT","release key UP","frames 90"],"window":{"windows":[{"title":"Everything moves","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":238.3005445068744,"translateY":72.0702992144124,"rotate":-88,"points":[-15,-6,9,-6,15,0,9,6,-15,6],"fill":"#ffd700"},{"n":4,"type":"Polygon","f":4,"translateX":96,"translateY":40,"rotate":112,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":5,"type":"Polygon","f":5,"translateX":180,"translateY":96,"rotate":202,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"},{"n":6,"type":"Polygon","f":6,"translateX":264,"translateY":40,"rotate":292,"points":[-10,0,-6,-8,0,-10,6,-8,10,0,7,10,3,2,0,10,-3,2,-7,10],"fill":"#da70d6"}]}}}],"problems":[],"frames":{"count":112,"list":[]}},"outline":"Window \"Everything moves\"\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 (238.3005445068744, 72.0702992144124), rotate -88, fill #ffd700\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (96, 40), rotate 112, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (180, 96), rotate 202, fill #da70d6\n    Polygon (-10, 0) (-6, -8) (0, -10) (6, -8) (10, 0) (7, 10) (3, 2) (0, 10) (-3, 2) (-7, 10), at (264, 40), rotate 292, fill #da70d6\nframes run: 112"}],"require":[{"pattern":"List\\s*<\\s*Mover\\s*>","message":"Keep the submarine and the jellyfish in a `List<Mover>`."}],"forbid":[]}]}