package wombat.scheme.libraries; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.util.*; import wombat.scheme.libraries.gui.*; import wombat.scheme.libraries.types.*; /** * Represent turtle graphics. */ public class TurtleAPI { static Map<String, TurtleData> LiveTurtles = new HashMap<String, TurtleData>(); static List<LineData> LiveLines = new ArrayList<LineData>(); static TurtleFrame tframe; public static double Pause = 0.1; /** * Convert a turtle to an image. * @param data Lines each with "tick x0 y0 x1 y1 r g b" * @return An image. */ public static Image turtleToImage(String data) { List<LineData> lines = new ArrayList<LineData>(); for (String line : data.split("\n")) { String[] parts = line.trim().split(" "); if (parts.length == 0) continue; if (parts.length != 8) throw new IllegalArgumentException("Malformed turtle data with line: " + line); lines.add(new LineData( Integer.parseInt(parts[0]), Double.parseDouble(parts[1]), -1.0 * Double.parseDouble(parts[2]), Double.parseDouble(parts[3]), -1.0 * Double.parseDouble(parts[4]), new Color( Integer.parseInt(parts[5]), Integer.parseInt(parts[6]), Integer.parseInt(parts[7]) ) )); } return linesToImage(lines, 10); } /** * Find the minimum and maximum x and y. * @param lines The lines to analyze. * @return Minimum and maximum. */ public static double[] linesToMinMax(List<LineData> lines) { double minX = 0; double maxX = 0; double minY = 0; double maxY = 0; for (LineData line : lines) { minX = Math.min(line.X0, Math.min(line.X1, minX)); maxX = Math.max(line.X0, Math.max(line.X1, maxX)); minY = Math.min(line.Y0, Math.min(line.Y1, minY)); maxY = Math.max(line.Y0, Math.max(line.Y1, maxY)); } return new double[]{minX, maxX, minY, maxY}; } /** * Convert a set of lines to an image. * @param lines The lines. * @return An image. */ public static BufferedImage linesToImage(List<LineData> lines, int spacing) { double[] minMax = linesToMinMax(lines); double minX = minMax[0] - spacing; double maxX = minMax[1] + spacing; double minY = minMax[2] - spacing; double maxY = minMax[3] + spacing; BufferedImage bi = new BufferedImage((int) (maxX - minX), (int) (maxY - minY), BufferedImage.TYPE_INT_RGB); Graphics2D g = (Graphics2D) bi.getGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, bi.getWidth(), bi.getHeight()); for (LineData line : lines) { g.setColor(line.C); g.drawLine( (int) (line.X0 - minX), (int) (line.Y0 - minY), (int) (line.X1 - minX), (int) (line.Y1 - minY) ); } return bi; } /** * Draw a set of turtle lines. * @param data Lines each with "tick x0 y0 x1 y1 r g b" */ public static void drawTurtle(String data) { ImageFrame iframe = new ImageFrame(turtleToImage(data)); iframe.setTitle("draw-turtle"); iframe.setVisible(true); } /** * Update a live turtle. * @param val The first part is the turtle id, the rest are parameters. */ public static void updateTurtle(String id, String fun, String[] args) { // // <DEBUG> // System.out.println("live-turtle: " + id + "::" + fun + Arrays.toString(args)); // // </DEBUG> // Initialize turtle frame if necessary. if (tframe == null) tframe = new TurtleFrame(); // Reset the display when it's closed. if (!tframe.isVisible()) { for (TurtleData t : LiveTurtles.values()) t.Live = false; LiveLines.clear(); } // Revisiblify the frame. tframe.setVisible(true); // Spawn is a special case if ("spawn".equals(fun)) { LiveTurtles.put(id, new TurtleData( Double.parseDouble(args[0]), -1.0 * Double.parseDouble(args[1]), Double.parseDouble(args[2]), "down".equals(args[3]), new Color( Integer.parseInt(args[4]), Integer.parseInt(args[5]), Integer.parseInt(args[6]) ))); } // Otherwise, verify that we've already seen that turtle. else if (!LiveTurtles.containsKey(id)) { throw new IllegalArgumentException("Unable to resume live turtle state (missing ID). Live turtles must be on before the turtle is spawned."); } // Finally, process any other sort of command. // Move one of the turtles else if ("move".equals(fun)) { double new_x = Double.parseDouble(args[0]); double new_y = -1.0 * Double.parseDouble(args[1]); TurtleData t = LiveTurtles.get(id); if (t.Pen) LiveLines.add(new LineData(0, t.X, t.Y, new_x, new_y, t.C)); t.X = new_x; t.Y = new_y; t.Live = true; } // Turn one of the turtles else if ("turn".equals(fun)) { LiveTurtles.get(id).R = Double.parseDouble(args[0]); LiveTurtles.get(id).Live = true; } // Update the pen state (up/down) else if ("pen".equals(fun)) { LiveTurtles.get(id).Pen = "down".equals(args[0]); LiveTurtles.get(id).Live = true; } // Update the pen color else if ("pen-color".equals(fun)) { LiveTurtles.get(id).C = new Color( Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2]) ); LiveTurtles.get(id).Live = true; } // Reset after a block else if ("block-reset".equals(fun)) { TurtleData t = LiveTurtles.get(id); t.X = Double.parseDouble(args[0]); t.Y = -1.0 * Double.parseDouble(args[1]); t.R = Double.parseDouble(args[2]); t.Pen = "down".equals(args[3]); t.C = new Color( Integer.parseInt(args[4]), Integer.parseInt(args[5]), Integer.parseInt(args[6]) ); t.Live = true; } // Move one of the turtles else { throw new IllegalArgumentException("Unable to record turtle state, unknown function: " + fun); } // Update the display. tframe.update(LiveTurtles, LiveLines); try { Thread.sleep((int) (1000 * Pause)); } catch(InterruptedException ex) {} } }