package org.teachingextensions.logo; import java.awt.Color; import java.awt.Point; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import javax.swing.JFrame; import org.teachingextensions.WindowUtils.ProgramWindow; import org.teachingextensions.WindowUtils.TurtleWindow; import org.teachingextensions.approvals.lite.util.ThreadLauncher; import org.teachingextensions.approvals.lite.util.lambda.Action0; import org.teachingextensions.approvals.lite.util.persistence.Saver; import org.teachingextensions.approvals.lite.util.persistence.SavingException; import org.teachingextensions.logo.utils.AngleCalculator; import org.teachingextensions.logo.utils.DeltaCalculator; import org.teachingextensions.logo.utils.InterfaceUtils.TurtleFrame; import org.teachingextensions.logo.utils.LineAndShapeUtils.LineSegment; /** * <img src="https://lh5.googleusercontent.com/-B3Q59gpYW8o/T4tA2k_TYUI/AAAAAAAAAjo/WiqdoXjbkb0/s65/Tortoise.png" style="text-align: left" alt="A turtle drawing a line" > * The Turtle allows you to draw lines and shapes by moving it around on the window, and you can put more than one turtle on the same window... */ public class Turtle { public static final int TEST_SPEED = Integer.MIN_VALUE; private static final double MAX_MOVE_AMOUNT = 5.0; private String DEFAULT_NAME = "Grace Hopper"; public TurtleWindow panel = new TurtleWindow(); public List<LineSegment> trail = new ArrayList<LineSegment>(); private double x = 640 / 2; private double y = 480 / 2; private double angleInDegrees = 0; private TurtleFrame frame = new TurtleFrame(); private int speed = 1; private Color color = Color.black; private int width = 2; private boolean penDown = true; private boolean hidden; private Animals animal; private Sound sound = new Sound(); private String name = DEFAULT_NAME; public BufferedImage getImage() { BufferedImage image = panel.getWindowImage(); clear(); return image; } public void clear() { trail.clear(); if (panel != null) { panel.getCanvas().clear(); } } public void setPanel(TurtleWindow panel) { this.panel = panel; } public int getX() { return (int) x; } public void setX(Number x) { this.x = x.doubleValue(); } public int getY() { return (int) y; } public void setY(Number y) { this.y = y.doubleValue(); } public double getAngleInDegrees() { return angleInDegrees; } public void setAngleInDegrees(double angleInDegrees) { this.angleInDegrees = angleInDegrees; } public void turn(double amount) { double max = getTurnAmount(amount); Saver<Double> s = new Turner(); animate(amount, max, s); } private double getTurnAmount(double amount) { amount = Math.abs(amount); if (getSpeed() == TEST_SPEED) { return amount; } return amount / (11 - getSpeed()); } private void animate(double amount, double max, Saver<Double> s) { double sign = amount > 0 ? 1 : -1; amount = Math.abs(amount); while (amount > max) { s.save(max * sign); refreshPanel(); amount -= max; } s.save(amount * sign); refreshPanel(); } private void refreshPanel() { refreshPanel(panel); } private void refreshPanel(ProgramWindow panel) { long delay = getDelay(); if (delay != TEST_SPEED) { panel.repaint(); try { Thread.sleep(delay); } catch (InterruptedException e) { // do nothing } } } private long getDelay() { if (getSpeed() == 10) { return 1; } if (getSpeed() == TEST_SPEED) { return TEST_SPEED; } return 100 / getSpeed(); } public String getName() { return name; } /** * Sets the name for a turtle instance * <p><b>Example:</b> {@code myTurtle.setName(name)}</p> * * @param name * Your turtle's name */ public void setName(String name) { this.name = name; } public int getSpeed() { return speed; } /** * Sets the speed that a turtle instance moves * <p><b>Example:</b> {@code myTurtle.setSpeed(speed)}</p> * * @param speed * The speed from 1 (slowest) to 10 (fastest) */ public void setSpeed(int speed) { if (speed != TEST_SPEED) { if (speed < 1 || 10 < speed) { throw new RuntimeException(String.format( "I call shenanigans!!!\nThe speed '%s' is not between the acceptable range of [1-10]\nPerhaps you should read the documentation", speed)); } } this.speed = speed; } public double getHeadingInDegrees() { return angleInDegrees; } /** * Sets the distance that a turtle instance moves in pixels * <p><b>Example:</b> {@code myTurtle.move(100)}</p> * * @param amount * The distance that your turtle moves in pixels. Negative numbers will move your turtle backwards */ public void move(Number amount) { double max = MAX_MOVE_AMOUNT; Saver<Double> s = penDown ? new Mover(new Point(getX(), getY())) : new EmptyMover(); animate(amount.doubleValue(), max, s); } private void moveWithoutAnimation(Double save) { DeltaCalculator calculator = new DeltaCalculator(this.angleInDegrees, save); x += calculator.getX(); y += calculator.getY(); } public LineSegment[] getTrail() { return trail.toArray(new LineSegment[0]); } public Color getPenColor() { return color; } public void setPenColor(Color color) { this.color = color; } public int getPenWidth() { return width; } public void setPenWidth(int width) { this.width = width; } public void show() { this.panel.init(this, this.frame); hidden = false; this.setFrameVisible(true); this.setPanelVisible(true); refreshPanel(panel); } public TurtleWindow getBackgroundWindow() { return panel; } /** * Sets the animal * <p><b>Example:</b> {@code myTurtle.setAnimal(Animals.spider)}</p> * * @param animal * The type of animal that appears on your window */ public void setAnimal(Animals animal) { refreshPanel(); getBackgroundWindow().setAnimal(animal); this.animal = animal; } public void penUp() { penDown = false; } public void penDown() { penDown = true; } public void print(String string) { // TODO Auto-generated method stub } public void speak() { this.sound.playSound(); } public void hide() { hidden = true; } public boolean isHidden() { return hidden; } public void moveTo(final int x, final int y) { ThreadLauncher.launch(new Action0() { @Override public void call() { moveSynchronized(x, y); } }); } public void moveSynchronized(int x, int y) { AngleCalculator calculator = new AngleCalculator(getX(), getY(), x, y); double angleOfWherePointIs = calculator.getDegreesWith0North(); double direction = angleOfWherePointIs - getAngleInDegrees(); turn(direction); // move the turtle the distance to the x y point double distance = new Point(x, y).distance(getX(), getY()); move(distance); } /** * Draws a triangle of a specified size and orientation * <p><b>Example:</b> {@code myTurtle.drawTriangle(size)}</p> * * @param size * The size of a side of the triangle, negative numbers draw an upside down triangle */ public void drawTriangle(int size) { for (int i = 1; i <= 3; i++) { this.turn(360 / 3); this.move(size); } } /** * Draws a star of a specified size * <p><b>Example:</b> {@code myTurtle.drawStar(size)}</p> * * @param size * The size of a side of the star */ public void drawStar(int size) { for (int i = 1; i <= 5; i++) { this.turn(360 / 2.5); this.move(size); } } public boolean isDead() { return this.animal == Animals.ExplodedTurtle; } /** * Draws a lightning bolt of a specified length * <p><b>Example:</b> {@code myTurtle.drawLightning(length)}</p> * * @param length * The length of a lightning bolt */ public void drawLightning(int length) { this.setX(50); this.setY(350); this.setSpeed(10); for (int i = 1; i < 5; i++) { this.setPenWidth(i * 4); this.turn(65 + i); this.move(length); this.turn(-65); this.move(length); } } public void setFrameVisible(boolean b) { this.frame.setVisible(b); } public void setPanelVisible(boolean b) { panel.setVisible(b); } public void setFrame(JFrame frame2) { this.frame = new TurtleFrame(frame2); } public void setSound(Sound sound) { this.sound = sound; } /** * Current types are: ExplodedTurtle, Turtle, Spider, Squid, Unicorn */ public enum Animals { ExplodedTurtle, Turtle, Spider, Squid, Unicorn } private class Turner implements Saver<Double> { @Override public Double save(Double save) throws SavingException { smallTurn(save); return save; } private void smallTurn(double i) { angleInDegrees += i; } } private class Mover implements Saver<Double> { private final Point starting; private LineSegment line = null; public Mover(Point point) { this.starting = point; } @Override public Double save(Double save) throws SavingException { moveWithoutAnimation(save); if (line != null) { trail.remove(line); } line = new LineSegment(color, starting, new Point(getX(), getY()), width); trail.add(line); return save; } } private class EmptyMover implements Saver<Double> { @Override public Double save(Double save) throws SavingException { moveWithoutAnimation(save); return save; } } }