All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gannacademy.cdf.turtlelogo.Turtle Maven / Gradle / Ivy

The newest version!
package org.gannacademy.cdf.turtlelogo;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * 

A turtles lives in a {@link Terrarium}. We imagine that turtles all hold a pen in their mouth. As they walk around * the terrarium, they leave a trail (a series of {@link Track} segments) behind them. The turtles can avoid leaving * a track if they pick up their pen.

* *

Turtle leaving a trail

* *

Turtles understand a limited number of instructions:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
move() examplemove( * steps) (a.k.a forward or fd and back or bk) — the turtle will step * forward, in the direction that it is currently facing, some number of steps (i.e. pixels).
turn() exampleturn(degrees) * (a.k.a. left or lt and right or rt) — the turtle will turn from its current heading * some number of degrees.
moveTo() examplemoveTo(x, y) (a.k.a. to) — the turtle will move from its current * location to the coordinates given, without changing its heading.
teleport() exampleteleport(x, y) (a.k.a. tp) — the turtle will teleport (like in Star * Trek) from its current location to the new coordinates, without changing its heading and without allowing the * pen to drag between locations.
penColor() examplepenColor(color) (a.k.a. pc) — the turtle will change the color of its pen * (initially the pen is black)
penWidth() examplepenWidth(width) (a.k.a. pw) — the turtle will change the width of its pen * stroke (measured in pixels)
hide() examplehide() (a.k.a. ht) — the turtle will hide itself (but remain at its current location and * heading)
show() exampleshow() (a.k.a. st) — the turtle, if hidden, will show itself again
 
*/ public class Turtle { /** * The parts of the turtle that are "under the shell" are not meant to be used by students. This mechanism * (inspired by this awesome Stack Overflow answer) recreates a * version of the C++ friend concept: a public method that is only available to some other * objects, rather than all other objects. * * @author Seth Battis */ public static final class UnderTheShell { private UnderTheShell() { } } protected static final UnderTheShell UNDER_THE_SHELL = new UnderTheShell(); /** * 270° */ public static final double NORTH = 270; /** * 90° */ public static final double SOUTH = 90; /** * 0° */ public static final double EAST = 0; /** * 180° */ public static final double WEST = 180; /** * {@link #EAST} */ public static final double DEFAULT_HEADING_IN_DEGREES = EAST; // degrees /** * {@link java.awt.Color#BLACK} */ public static final Color DEFAULT_PEN_COLOR = Color.BLACK; /** * 1.0 pixels */ public static final float DEFAULT_PEN_WIDTH = 1; /** * true */ public static final boolean DEFAULT_PEN_DOWN = true; /** * false */ public static final boolean DEFAULT_HIDDEN = false; private Terrarium terrarium; private static BufferedImage icon; private double x, y; private double headingInDegrees; private Color penColor; private BasicStroke penStroke; private boolean penDown; private boolean hidden; /** * Construct a turtle in the default terrarium */ public Turtle() { this(Terrarium.getInstance()); } /** * Construct a turtle in a custom terrarium * * @param terrarium to house the turtle */ public Turtle(Terrarium terrarium) { this.x = terrarium.getWidth() / 2.0; this.y = terrarium.getHeight() / 2.0; this.headingInDegrees = DEFAULT_HEADING_IN_DEGREES; this.penColor = DEFAULT_PEN_COLOR; this.penStroke = new BasicStroke(DEFAULT_PEN_WIDTH); this.penDown = DEFAULT_PEN_DOWN; this.hidden = DEFAULT_HIDDEN; this.terrarium = terrarium; this.terrarium.add(this, UNDER_THE_SHELL); } /** * @return X-coordinate of turtle */ public double getX() { return x; } /** * @return Y-coordinate of turtle */ public double getY() { return y; } /** * @return Current turtle heading in degrees */ public double getHeadingInDegrees() { return headingInDegrees; } /** * @return Current turtle heading in radians */ public double getHeadingInRadians() { return Math.toRadians(headingInDegrees); } /** * @return Current pen color */ public Color getPenColor() { return penColor; } /** * @return Current pen width */ public double getPenWidth() { return penStroke.getLineWidth(); } protected BasicStroke getPenStroke() { return penStroke; } /** * @return true if the pen is down, false otherwise */ public boolean isPenDown() { return penDown; } /** * @return true if the turtle is hidden, false otherwise */ public boolean isHidden() { return hidden; } private BufferedImage getIcon() { if (icon == null) { try { icon = ImageIO.read(getClass().getResource("/turtle.png")); } catch (IOException e) { System.err.println("The image file containing the turtle icon could not be found and/or opened."); e.printStackTrace(); } } return icon; } /** * @return Terrarium currently housing the turtle */ public Terrarium getTerrarium() { return terrarium; } /** * Move the turtle to another terrarium * * @param terrarium to house the turtle */ public void setTerrarium(Terrarium terrarium) { if (this.terrarium != null) { this.terrarium.remove(this, UNDER_THE_SHELL); } this.terrarium = terrarium; terrarium.add(this, UNDER_THE_SHELL); } /** * Alias for {@link #back(double)} * * @param steps in pixels */ public void bk(double steps) { back(steps); } /** * Alias for {@link #move(double)} * * @param steps in pixels */ public void back(double steps) { move(-1 * steps); } /** * Alias for {@link #forward(double)} * * @param steps in pixels */ public void fd(double steps) { forward(steps); } /** * Alias for {@link #move(double)} * * @param steps in pixels */ public void forward(double steps) { move(steps); } /** *

Move the turtle in the direction of its current heading

*

A positive value for steps is interpreted as forward movement and a negative value as backward * movement

* * @param steps in pixels */ public void move(double steps) { double newX = x + Math.cos(getHeadingInRadians()) * steps, newY = y + Math.sin(getHeadingInRadians()) * steps; if (penDown) { getTerrarium().add(new Track(x, y, newX, newY, penColor, penStroke, UNDER_THE_SHELL), UNDER_THE_SHELL); } x = newX; y = newY; } /** * Alias for {@link #moveTo(double, double)} * * @param x coordinate * @param y coordinate */ public void to(double x, double y) { moveTo(x, y); } /** *

Move the turtle to a particular location

*

The turtle moves directly to the window coordinates (x, y). Note that the origin of * the wind ow is in the top, left corner and that, while the X-axis increases from left to right, the Y-axis * increases from to to bottom.

*

If the turtle's pen is currently down, the move will create a track from the old location to the new location.

* * @param x coordinate * @param y coordinate */ public void moveTo(double x, double y) { if (penDown) { getTerrarium().add(new Track(this.x, this.y, x, y, penColor, penStroke, UNDER_THE_SHELL), UNDER_THE_SHELL); } this.x = x; this.y = y; } /** * Alias for {@link #teleport(double, double)} * * @param x coordinate * @param y coordinate */ public void tp(double x, double y) { teleport(x, y); } /** *

Move the turtle instantaneously to a particular location

*

The turtle moves directly to the window coordinates (x, y). Note that the origin of * the wind ow is in the top, left corner and that, while the X-axis increases from left to right, the Y-axis * increases from to to bottom.

*

No track is left by a teleportation.

* * @param x coordinate * @param y coordinate */ public void teleport(double x, double y) { this.x = x; this.y = y; } /** *

Reset the turtle to its home position

*

The turtle's home position is at the center of the window, heading {@link #EAST}

*/ public void home() { teleport(getTerrarium().getWidth() / 2.0, getTerrarium().getHeight() / 2.0); head(EAST); } /** * Alias for {@link #right(double)} * * @param angle in degrees */ public void rt(double angle) { right(angle); } /** * Alias for {@link #turn(double)} * * @param angle in degrees */ public void right(double angle) { turn(angle); } /** * Alias for {@link #left(double)} * * @param angle in degrees */ public void lt(double angle) { left(angle); } /** * Alias for {@link #turn(double)} * * @param angle in degrees */ public void left(double angle) { turn(-1 * angle); } /** *

Turn the turtle from its current heading

*

A positive angle is interpreted as a right turn and a negative angle is a left turn. This is mildly * surprising if you know about the unit circle, but is because the Y-axis of the window increases from top to bottom, * which results in a mirrored unit circle around the X-axis, with 90° at the {@link #SOUTH} and270° at the * {@link #NORTH}

* * @param angle in degrees */ public void turn(double angle) { headingInDegrees = (headingInDegrees + angle) % 360; getTerrarium().repaint(); } /** * Alias for {@link #head(double)} * * @param heading in degrees */ public void hd(double heading) { head(heading); } /** *

Turn the turtle to a particular heading

*

Note that, because the Y-axis of the window increases from top to bottom, the usual angles of the unit circle * have been mirrored around the X-axis, with 90° at the {@link #SOUTH} and 270%deg; at the {@link #NORTH}. * Convenience constants have been provided for the cardinal directions.

* * @param heading [0..360) in degrees */ public void head(double heading) { this.headingInDegrees = heading % 360; } /** * Alis for {@link #penUp()} */ public void pu() { penUp(); } /** * Lift the turtle's pen up, causing it not to leave a track */ public void penUp() { penDown = false; } /** * Alias for {@link #penDown()} */ public void pd() { penDown(); } /** * Lower the turtle's pen, causing it to leave a trail */ public void penDown() { penDown = true; } /** * Alias for {@link #penColor(Color)} * * @param color to use */ public void pc(Color color) { penColor(color); } /** *

Set the color of the turtle's pen

*

Color values are given as {@link Color} values. {@link Color} has a number of helpful constants like * {@link Color#GREEN} or {@link Color#GREEN}. Custom colors can also be constructed. Refer to the * java.awt.Color * API documentation more details (including how to create transparent colors!)

* * @param color to use */ public void penColor(Color color) { penColor = color; } /** * Alias for {@link #penWidth(double)} * * @param width in pixels */ public void pw(double width) { penWidth(width); } /** * Set the width of the turtle's pen * * @param width in pixels */ public void penWidth(double width) { penStroke = new BasicStroke((float) width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); } /** * Alias for {@link #hide()} */ public void ht() { hide(); } /** *

Hide the turtle

*

Hiding the turtle causes it to become invisible — it can still be moved and create tracks, but the turtle * itself is not visible

*/ public void hide() { hidden = true; getTerrarium().repaint(); } /** * Alias for {@link #show()} */ public void st() { show(); } /** * Show the turtle (if it was hidden) */ public void show() { hidden = false; getTerrarium().repaint(); } /** *

Draw the turtle

*

May only be called by {@link Terrarium} and its subclasses, enforced by {@link Terrarium.UnderTheSurface}

* * @param context for drawing commands * @param key to authenticate "Terrarium-iality" */ public void draw(Graphics2D context, Terrarium.UnderTheSurface key) { key.hashCode(); drawIcon(x, y, getHeadingInRadians(), context); } protected void drawIcon(double x, double y, double headingInRadians, Graphics2D context) { if (!hidden) { AffineTransform transform = new AffineTransform(); // transformations are applied in reverse order transform.translate(x, y); // move turtle to location transform.rotate(headingInRadians); // orient turtle to heading transform.translate(-1 * getIcon().getWidth(), getIcon().getHeight() / -2.0); // move icon origin to turtle nose context.drawImage(getIcon(), transform, null); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy