Created
November 15, 2011 20:23
-
-
Save ellbur/1368215 to your computer and use it in GitHub Desktop.
Yet another Turtle graphics implementation for Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.awt.*; | |
| import java.util.*; | |
| import java.awt.event.*; | |
| import javax.swing.*; | |
| import java.awt.geom.*; | |
| public class Turtle { | |
| // ---------------------------------- | |
| // UI | |
| static JFrame window; | |
| static int imageWidth = 640; | |
| static int imageHeight = 480; | |
| static int minX = 0, minY = 0, maxX = imageWidth, maxY = imageHeight; | |
| static int shiftX, shiftY; | |
| static Image baseImage; | |
| static Image activeImage; | |
| static Graphics2D baseGraphics; | |
| static Graphics2D activeGraphics; | |
| static JLabel imageLabel; | |
| // ---------------------------------- | |
| // Parameters | |
| static Color backgroundColor = Color.white; | |
| static Stroke turtleStroke = new BasicStroke(2); | |
| // ---------------------------------- | |
| // State | |
| static double x; | |
| static double y; | |
| static double theta; | |
| static Color color; | |
| static Stroke lineStroke; | |
| static double speed; | |
| static double endX; | |
| static double endY; | |
| static Object lock = new Object(); | |
| static boolean inited; | |
| // ------------------------------------------------------------- | |
| private static void init() { | |
| if (inited) return; | |
| inited = true; | |
| window = new JFrame("Turtle"); | |
| window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
| Container cp = window.getContentPane(); | |
| imageLabel = new JLabel(new DummyIcon(imageWidth, imageHeight)); | |
| cp.add(imageLabel); | |
| window.pack(); | |
| window.setVisible(true); | |
| makeImage(); | |
| clear(); | |
| } | |
| private static void ensureAround(int nx, int ny) { | |
| ensure(nx-10, ny-10); | |
| ensure(nx-10, ny+10); | |
| ensure(nx+10, ny-10); | |
| ensure(nx+10, ny+10); | |
| } | |
| private static void ensure(int nx, int ny) { | |
| if (nx < minX) reduceMinX(nx); | |
| if (nx > maxX) increaseMaxX(nx); | |
| if (ny < minY) reduceMinY(ny); | |
| if (ny > maxY) increaseMaxY(ny); | |
| if (nx < shiftX) shiftX = nx; | |
| if (nx > imageWidth+shiftX) shiftX = nx - imageWidth; | |
| if (ny < shiftY) shiftY = ny; | |
| if (ny > imageHeight+shiftY) shiftY = ny - imageHeight; | |
| } | |
| private static void reduceMinX(int nx) { | |
| Image newImage = window.createImage(maxX-nx, maxY-minY); | |
| Graphics2D newGraphics = (Graphics2D) newImage.getGraphics(); | |
| newGraphics.setColor(Color.white); | |
| newGraphics.fillRect(0, 0, maxX-nx, maxY-minY); | |
| newGraphics.drawImage(baseImage, minX-nx, 0, null); | |
| minX = nx; | |
| baseImage = newImage; | |
| baseGraphics = newGraphics; | |
| } | |
| private static void reduceMinY(int ny) { | |
| Image newImage = window.createImage(maxX-minX, maxY-ny); | |
| Graphics2D newGraphics = (Graphics2D) newImage.getGraphics(); | |
| newGraphics.setColor(Color.white); | |
| newGraphics.fillRect(0, 0, maxX-minX, maxY-ny); | |
| newGraphics.drawImage(baseImage, 0, minY-ny, null); | |
| minY = ny; | |
| baseImage = newImage; | |
| baseGraphics = newGraphics; | |
| } | |
| private static void increaseMaxX(int nx) { | |
| Image newImage = window.createImage(nx-minX, maxY-minY); | |
| Graphics2D newGraphics = (Graphics2D) newImage.getGraphics(); | |
| newGraphics.setColor(Color.white); | |
| newGraphics.fillRect(0, 0, nx-minX, maxY-minY); | |
| newGraphics.drawImage(baseImage, 0, 0, null); | |
| maxX = nx; | |
| baseImage = newImage; | |
| baseGraphics = newGraphics; | |
| } | |
| private static void increaseMaxY(int ny) { | |
| Image newImage = window.createImage(maxX-minX, ny-minY); | |
| Graphics2D newGraphics = (Graphics2D) newImage.getGraphics(); | |
| newGraphics.setColor(Color.white); | |
| newGraphics.fillRect(0, 0, maxX-minX, ny-minY); | |
| newGraphics.drawImage(baseImage, 0, 0, null); | |
| maxY = ny; | |
| baseImage = newImage; | |
| baseGraphics = newGraphics; | |
| } | |
| // ------------------------------------------------------------- | |
| /** | |
| * Sets the speed of the turtle in pixels/second. | |
| * | |
| * Speed is used for draw() and move() but not for turn(). | |
| * | |
| * @param _speed The speed in pixels/second. | |
| */ | |
| public static void setSpeed(double _speed) { | |
| init(); | |
| speed = _speed; | |
| } | |
| /** | |
| * Sets the line color. | |
| * | |
| * @param _color The line color. | |
| */ | |
| public static void setColor(Color _color) { | |
| init(); | |
| color = _color; | |
| } | |
| /** | |
| * Sets the line thickness. | |
| * | |
| * @param thick The line thickness. | |
| */ | |
| public static void setThickness(int thick) { | |
| init(); | |
| lineStroke = new BasicStroke(thick); | |
| } | |
| // ------------------------------------------------------------- | |
| /** | |
| * Clears the canvas, and resets params to their default. | |
| * | |
| * Resets speed, color, thickness. Centers the turtle and | |
| * points it to the right. | |
| */ | |
| public static void clear() { | |
| init(); | |
| x = imageWidth/2; | |
| y = imageHeight/2; | |
| endX = x; | |
| endY = y; | |
| theta = 0.0; | |
| speed = 100.0; | |
| color = Color.black; | |
| lineStroke = new BasicStroke(1); | |
| clearBase(); | |
| updateScreenWrapper(); | |
| } | |
| /** | |
| * Draw a line a given distance. | |
| * | |
| * If the speed is infinite, the line will instantly appear and the turtle | |
| * will end up in the new location. | |
| * | |
| * If the speed is finite, the turtle will traverse the line at the given | |
| * speed, ending up at the endpoint. | |
| * | |
| * The turtle always moves when drawing lines. | |
| * | |
| * @param distance The distance in pixels. | |
| * @see #setSpeed | |
| * @see #setColor | |
| * @see #setThickness | |
| */ | |
| public static void draw(double distance) { | |
| init(); | |
| double totalDelay = distance / speed; | |
| moveSlowly(totalDelay, new LineUpdater(distance)); | |
| synchronized (lock) { | |
| endX = x + distance * Math.cos(theta); | |
| endY = y + distance * Math.sin(theta); | |
| fixLine(); | |
| x = endX; | |
| y = endY; | |
| } | |
| updateScreenWrapper(); | |
| } | |
| /** | |
| * Moves the turtle forward in the direction it is facing. | |
| * | |
| * This behaves the same way as draw() except that no line appears. If the | |
| * speed is finite the turtle will move along the line in the same manner | |
| * as draw. | |
| * | |
| * @param distance The distance in pixels. | |
| * @see #setSpeed | |
| */ | |
| public static void move(double distance) { | |
| init(); | |
| double totalDelay; | |
| double startX, startY; | |
| totalDelay = distance / speed; | |
| startX = x; | |
| startY = y; | |
| moveSlowly(totalDelay, new MoveUpdater(distance)); | |
| synchronized (lock) { | |
| x = startX + distance * Math.cos(theta); | |
| y = startY + distance * Math.sin(theta); | |
| endX = x; | |
| endY = y; | |
| } | |
| updateScreenWrapper(); | |
| } | |
| /** | |
| * Jumps to an absolute location in pixels. | |
| * | |
| * 0,0 is upper left. Instant. | |
| * | |
| * @param _x X coord in pixels. | |
| * @param _y Y coord in pixels. | |
| * */ | |
| public static void jump(double _x, double _y) { | |
| init(); | |
| endX = x = _x; | |
| endY = y = _y; | |
| updateScreenWrapper(); | |
| } | |
| /** | |
| * Turns the turtle dtheta degrees to the left. | |
| * | |
| * Turning is instant. | |
| * | |
| * @param dtheta Degrees left to turn. | |
| * @see #turnRad | |
| */ | |
| public static void turn(double dtheta) { | |
| init(); | |
| theta += dtheta / 180.0 * Math.PI; | |
| updateScreenWrapper(); | |
| } | |
| /** | |
| * Turns the turtle dtheta radians to the left. | |
| * | |
| * Turning is instant. | |
| * | |
| * @param dtheta Radians left to turn. | |
| * @see #turn | |
| */ | |
| public static void turnRad(double dtheta) { | |
| init(); | |
| turn(dtheta * 180.0 / Math.PI); | |
| } | |
| public static double getAngle() { | |
| return theta; | |
| } | |
| public static void setAngle(double _theta) { | |
| theta = _theta; | |
| } | |
| public static double getX() { return x; } | |
| public static double getY() { return y; } | |
| public static void setX(double _x) { | |
| x = _x; | |
| ensureAround((int)x, (int)y); | |
| } | |
| public static void setY(double _y) { | |
| y = _y; | |
| ensureAround((int)x, (int)y); | |
| } | |
| private static class Mark { | |
| double x, y, theta; | |
| Mark(double _x, double _y, double _theta) { | |
| this.x = _x; | |
| this.y = _y; | |
| this.theta = _theta; | |
| } | |
| } | |
| private static LinkedList<Mark> marks = new LinkedList<Mark>(); | |
| public static void mark() { | |
| marks.push(new Mark(x, y, theta)); | |
| } | |
| public static void reset() { | |
| if (marks.size() == 0) { | |
| throw new IllegalStateException("No marks"); | |
| } | |
| Mark mark = marks.pop(); | |
| x = mark.x; | |
| y = mark.y; | |
| theta = mark.theta; | |
| ensureAround((int)x, (int)y); | |
| } | |
| /** | |
| * Pause for `millis` milliseconds. | |
| * | |
| * @param millis Milliseconds to pause. | |
| */ | |
| public static void pause(int millis) { | |
| init(); | |
| try { | |
| Thread.sleep(millis); | |
| } catch (InterruptedException ie) { | |
| return; | |
| } | |
| } | |
| // ------------------------------------------------------------- | |
| private static void fixLine() { | |
| draw(new LinePicture()); | |
| } | |
| private static void clearBase() { | |
| draw(new ClearPicture()); | |
| } | |
| private static void draw(final Picture pic) { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| Graphics2D g = baseGraphics; | |
| AffineTransform saveT = g.getTransform(); | |
| g.translate(-minX, -minY); | |
| pic.draw(g); | |
| g.setTransform(saveT); | |
| }}); | |
| } | |
| private static void moveSlowly(double time, Updater updater) { | |
| double startTime = System.currentTimeMillis() / 1000.0; | |
| for (;;) { | |
| double nowTime = System.currentTimeMillis() / 1000.0; | |
| double partTime = nowTime - startTime; | |
| if (partTime >= time) { | |
| break; | |
| } | |
| double fraction = partTime / time; | |
| updater.update(fraction); | |
| try { | |
| Thread.sleep(20); | |
| } | |
| catch (InterruptedException ie) { | |
| return; | |
| } | |
| } | |
| } | |
| // ------------------------------------------------------------- | |
| private static void drawActiveLine() { | |
| synchronized (lock) { | |
| Graphics2D g = activeGraphics; | |
| AffineTransform saveT = g.getTransform(); | |
| g.translate(-shiftX, -shiftY); | |
| new LinePicture().draw(g); | |
| g.setTransform(saveT); | |
| } | |
| } | |
| private static void drawTurtle() { | |
| synchronized (lock) { | |
| Graphics2D g = activeGraphics; | |
| g.setColor(color); | |
| g.setStroke(turtleStroke); | |
| AffineTransform saveT = g.getTransform(); | |
| g.translate(endX-shiftX, endY-shiftY); | |
| g.rotate(theta); | |
| g.drawLine( -5, 0, 5, 0 ); | |
| g.drawLine( 0, 5, 5, 0 ); | |
| g.drawLine( 0, -5, 5, 0 ); | |
| g.setTransform(saveT); | |
| } | |
| } | |
| private static void updateScreenWrapper() { | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| updateScreen(); | |
| }}); | |
| } | |
| private static void updateScreen() { | |
| makeImage(); | |
| if (activeGraphics == null || baseGraphics == null) { | |
| return; | |
| } | |
| synchronized (lock) { | |
| activeGraphics.setColor(Color.white); | |
| activeGraphics.fillRect(0, 0, imageWidth, imageHeight); | |
| activeGraphics.drawImage(baseImage, minX-shiftX, minY-shiftY, null); | |
| drawActiveLine(); | |
| drawTurtle(); | |
| } | |
| imageLabel.repaint(); | |
| } | |
| private static void makeImage() { | |
| if (activeGraphics != null && baseGraphics != null) { | |
| return; | |
| } | |
| activeImage = window.createImage(imageWidth, imageHeight); | |
| baseImage = window.createImage(imageWidth, imageHeight); | |
| if (activeImage != null) | |
| activeGraphics = (Graphics2D) activeImage.getGraphics(); | |
| if (baseImage != null) | |
| baseGraphics = (Graphics2D) baseImage.getGraphics(); | |
| if (activeImage != null) { | |
| imageLabel.setIcon(new ImageIcon(activeImage)); | |
| } | |
| } | |
| // ----------------------------------------------------------------- | |
| static interface Picture { | |
| public void draw(Graphics2D g); | |
| } | |
| static class LinePicture implements Picture { | |
| int x1; | |
| int y1; | |
| int x2; | |
| int y2; | |
| Color color; | |
| Stroke stroke; | |
| LinePicture() { | |
| x1 = (int) Math.round(x); | |
| y1 = (int) Math.round(y); | |
| x2 = (int) Math.round(endX); | |
| y2 = (int) Math.round(endY); | |
| color = Turtle.color; | |
| stroke = Turtle.lineStroke; | |
| } | |
| public void draw(Graphics2D g) { | |
| g.setColor(color); | |
| g.setStroke(stroke); | |
| g.drawLine(x1, y1, x2, y2); | |
| } | |
| } | |
| static class ClearPicture implements Picture { | |
| Color color; | |
| ClearPicture() { | |
| color = Turtle.backgroundColor; | |
| } | |
| public void draw(Graphics2D g) { | |
| g.setColor(color); | |
| g.fillRect(0, 0, imageWidth, imageHeight); | |
| } | |
| } | |
| static interface Updater { | |
| public void update(double progress); | |
| } | |
| static class LineUpdater implements Updater { | |
| double distance; | |
| LineUpdater(double _distance) { | |
| this.distance = _distance; | |
| } | |
| public void update(double progress) { | |
| double partDistance = progress * distance; | |
| synchronized (lock) { | |
| endX = x + partDistance * Math.cos(theta); | |
| endY = y + partDistance * Math.sin(theta); | |
| ensureAround((int)endX, (int)endY); | |
| } | |
| updateScreenWrapper(); | |
| } | |
| } | |
| static class MoveUpdater implements Updater { | |
| double distance; | |
| double startX; | |
| double startY; | |
| MoveUpdater(double _distance) { | |
| this.distance = _distance; | |
| synchronized (lock) { | |
| startX = x; | |
| startY = y; | |
| } | |
| } | |
| public void update(double progress) { | |
| double partDistance = progress * distance; | |
| synchronized (lock) { | |
| endX = startX + partDistance * Math.cos(theta); | |
| endY = startY + partDistance * Math.sin(theta); | |
| x = endX; | |
| y = endY; | |
| ensureAround((int)endX, (int)endY); | |
| } | |
| updateScreenWrapper(); | |
| } | |
| } | |
| static class DummyIcon implements Icon { | |
| int width; | |
| int height; | |
| public DummyIcon(int _width, int _height) { | |
| this.width = _width; | |
| this.height = _height; | |
| } | |
| public void paintIcon(Component comp, Graphics g, int w, int h) { } | |
| public int getIconWidth() { return width; } | |
| public int getIconHeight() { return height; } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment