From bb1dc77a63714446bd9db5a28a3b476044033b48 Mon Sep 17 00:00:00 2001 From: Jared Wong Date: Mon, 2 Sep 2013 20:06:58 -0400 Subject: [PATCH] Starting code. --- .classpath | 7 + .gitignore | 2 + .project | 17 +++ src/rules/RulesOf6005.java | 53 +++++++ src/rules/RulesOf6005Test.java | 18 +++ src/turtle/Action.java | 33 +++++ src/turtle/DrawableTurtle.java | 71 ++++++++++ src/turtle/LineSegment.java | 48 +++++++ src/turtle/Point.java | 14 ++ src/turtle/Turtle.java | 21 +++ src/turtle/TurtleGUI.java | 246 +++++++++++++++++++++++++++++++++ src/turtle/TurtleSoup.java | 125 +++++++++++++++++ src/turtle/TurtleSoupTest.java | 61 ++++++++ 13 files changed, 716 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100755 src/rules/RulesOf6005.java create mode 100755 src/rules/RulesOf6005Test.java create mode 100644 src/turtle/Action.java create mode 100644 src/turtle/DrawableTurtle.java create mode 100644 src/turtle/LineSegment.java create mode 100644 src/turtle/Point.java create mode 100644 src/turtle/Turtle.java create mode 100644 src/turtle/TurtleGUI.java create mode 100644 src/turtle/TurtleSoup.java create mode 100644 src/turtle/TurtleSoupTest.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..72c2ba6 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a5ba85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +bin diff --git a/.project b/.project new file mode 100644 index 0000000..5a47f18 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + ps0 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/rules/RulesOf6005.java b/src/rules/RulesOf6005.java new file mode 100755 index 0000000..a0c7bcf --- /dev/null +++ b/src/rules/RulesOf6005.java @@ -0,0 +1,53 @@ +package rules; + +/** + * RulesOf6005 represents collaboration policy of 6.005 as described by the + * general information on Stellar. + * + * the Policy is described by the mayUseCodeInAssignment method. + * + */ +public class RulesOf6005 { + + /** + * Judge whether a given piece of code may be used in an assignment (problem + * set or team project) or not, according to the 6.005 collaboration policy. + * + * @param writtenByYourself true if the code in question was written by + * yourself or, in the case of a team project, your teammates, + * otherwise false. + * @param availableToOthers if not writtenByYourself, whether or not the + * code in question is available to all other students in the class. + * Otherwise ignored. + * @param writtenAsCourseWork if not writtenByYourself, whether or not the + * code in question was written specifically as part of a solution to + * a 6.005 assignment, in the current or past semesters. Otherwise + * ignored. + * @param citingYourSource if not writtenByYourself, whether or not you + * properly cite your source. Otherwise ignored. + * @param implementationRequired whether the assignment specifically asks + * you to implement the feature in question. + * @return Whether or not, based on the information provided in the + * arguments, you are likely to be allowed to use the code in + * question in your assignment, according to the 6.005 collaboration + * policy for the current semester. + */ + public static boolean mayUseCodeInAssignment(boolean writtenByYourself, + boolean availableToOthers, boolean writtenAsCourseWork, + boolean citingYourSource, boolean implementationRequired) { + // TODO: Fill in this method, then remove the exception + throw new UnsupportedOperationException(); + } + + /** + * Main method of the class. + * + * Runs the mayUseCodeInAssignment method. + * @param args + */ + public static void main(String[] args){ + System.out.println("You may certainly use code you wrote yourself: " + + RulesOf6005.mayUseCodeInAssignment(true, false, true, true, true)); + } +} + diff --git a/src/rules/RulesOf6005Test.java b/src/rules/RulesOf6005Test.java new file mode 100755 index 0000000..ff99d4d --- /dev/null +++ b/src/rules/RulesOf6005Test.java @@ -0,0 +1,18 @@ +package rules; + +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * JUnit tests for RulesOf6005. + */ +public class RulesOf6005Test { + /** + * Tests the mayUseCodeInAssignment method. + */ + @Test + public void testMayUseCodeInAssignment() { + assertEquals(false, RulesOf6005.mayUseCodeInAssignment(false, true, false, false, false)); + assertEquals(true, RulesOf6005.mayUseCodeInAssignment(true, false, true, true, true)); + } +} diff --git a/src/turtle/Action.java b/src/turtle/Action.java new file mode 100644 index 0000000..e6f7ee4 --- /dev/null +++ b/src/turtle/Action.java @@ -0,0 +1,33 @@ +package turtle; + +/** + * Command list entries + */ +enum ActionType { + FORWARD, TURN +} + +public class Action { + ActionType type; + int intParam; + double doubleParam; + String displayString; + LineSegment lineSeg; + + public Action(ActionType type, int intParam, double doubleParam, + String displayString, LineSegment lineSeg) { + this.type = type; + this.intParam = intParam; + this.doubleParam = doubleParam; + this.displayString = displayString; + this.lineSeg = lineSeg; + } + + public String toString() { + if (displayString == null) { + return ""; + } else { + return displayString; + } + } +} diff --git a/src/turtle/DrawableTurtle.java b/src/turtle/DrawableTurtle.java new file mode 100644 index 0000000..db48c79 --- /dev/null +++ b/src/turtle/DrawableTurtle.java @@ -0,0 +1,71 @@ +package turtle; + +import java.util.ArrayList; +import java.util.List; +import java.lang.Math; + +import javax.swing.SwingUtilities; + +/** + * Turtle for drawing. + */ +public class DrawableTurtle implements Turtle { + + List actionList; + List lines; + + Point currentPosition; + double currentHeading; + + private static final int canvasWidth = 512; + private static final int canvasHeight = 512; + + public DrawableTurtle() { + this.currentPosition = new Point(0, 0); + this.currentHeading = 0.0; + this.lines = new ArrayList(); + this.actionList = new ArrayList(); + } + + /** + * Command to send the turtle forward a number of units. + * + * @param units number of pixels to go in currentHeading's direction; must be positive. + */ + public void forward(int units) { + double newX = this.currentPosition.x + Math.cos(Math.toRadians(90.0 - currentHeading)) * (double)units; + double newY = this.currentPosition.y + Math.sin(Math.toRadians(90.0 - currentHeading)) * (double)units; + + LineSegment lineSeg = new LineSegment(this.currentPosition.x, this.currentPosition.y, newX, newY); + this.lines.add(lineSeg); + this.currentPosition = new Point(newX, newY); + + this.actionList.add(new Action(ActionType.FORWARD, units, 0.0, "forward " + units + " units", lineSeg)); + } + + /** + * Change the heading by some degrees clockwise. + * + * @param degrees amount of change in angle, in degrees, with positive being clockwise. + */ + public void turn(double degrees) { + degrees = (degrees % 360 + 360) % 360; + this.currentHeading += degrees; + if (this.currentHeading >= 360.0) + this.currentHeading -= 360.0; + this.actionList.add(new Action(ActionType.TURN, 0, degrees, "turn " + degrees + " degrees", null)); + } + + /** + * Draw to the screen. + */ + public void draw() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + (new TurtleGUI(actionList, canvasWidth, canvasHeight)).setVisible(true); + } + }); + return; + } + +} diff --git a/src/turtle/LineSegment.java b/src/turtle/LineSegment.java new file mode 100644 index 0000000..74b34d6 --- /dev/null +++ b/src/turtle/LineSegment.java @@ -0,0 +1,48 @@ +package turtle; + +/** + * Class for a line segment in pixel space. + */ +public class LineSegment { + public final Point start; + public final Point end; + + /* + * Constructor that takes in 4 integers for the coordinates. + * + * @param startx x-coordinate of start point + * + * @param starty y-coordinate of start point + * + * @param endx x-coordinate of end point + * + * @param endy y-coordinate of end point + */ + public LineSegment(double startx, double starty, double endx, double endy) { + this.start = new Point(startx, starty); + this.end = new Point(endx, endy); + } + + /* + * Constructor that takes in the start point and end point. + * + * @param start one end of the line segment + * + * @param end the other end of the line segment + */ + public LineSegment(Point start, Point end) { + this.start = start; + this.end = end; + } + + /* + * Calculate the length of this segment. + * + * @return the length of the line segment + */ + public double length() { + return Math.sqrt(Math.pow(this.start.x - this.end.x, 2.0) + + Math.pow(this.start.y - this.end.y, 2.0)); + } + +} diff --git a/src/turtle/Point.java b/src/turtle/Point.java new file mode 100644 index 0000000..63ed12e --- /dev/null +++ b/src/turtle/Point.java @@ -0,0 +1,14 @@ +package turtle; + +/** + * Class for a point in floating-point pixel space. + */ +public class Point { + public final double x; + public final double y; + + public Point(double x, double y) { + this.x = x; + this.y = y; + } +} diff --git a/src/turtle/Turtle.java b/src/turtle/Turtle.java new file mode 100644 index 0000000..997a382 --- /dev/null +++ b/src/turtle/Turtle.java @@ -0,0 +1,21 @@ +package turtle; + +/** + * Turtle interface + * + * Defines the interface shared for FakeTurtle (for testing) and the + * real turtle (for displaying things on screen). Note that the + * standard directions/rotations use 'logo' semantics: initial heading + * of zero is 'up', and positive angles rotate the turtle clockwise. + * + * We implement: forward, turn right, and draw + */ +public interface Turtle { + + public void forward(int units); + + public void turn(double angle); + + public void draw(); + +} diff --git a/src/turtle/TurtleGUI.java b/src/turtle/TurtleGUI.java new file mode 100644 index 0000000..d41a46b --- /dev/null +++ b/src/turtle/TurtleGUI.java @@ -0,0 +1,246 @@ +package turtle; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Container; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.util.List; + +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingWorker; + +public class TurtleGUI extends JFrame { + + private static final long serialVersionUID = 1L; + + private static final Color canvasBGColor = new Color(255, 255, 255); + private static final Color canvasLineColor = new Color(0, 0, 0); + + private static final double LENGTH_OF_A_TURN = 20; + private static final long MILLIS_PER_DRAWING = 5000; + private static final double ROUGH_FPS = 60; + + private static final long MILLIS_PER_FRAME = (long) (1000.0 / ROUGH_FPS); + + private int canvasWidth; + private int canvasHeight; + private int actionListSize; + + private boolean isRunning; + private AnimationThread currAnimationThread; + + private JButton runButton; + private JLabel currentActionLabel; + private JLabel currentAction; + private BufferedImage canvas; + private Graphics2D graphics; + private JLabel drawLabel; + + private List actionList; + + private int originX; + private int originY; + + public TurtleGUI(List actionList, int canvasWidth, int canvasHeight) { + super("TurtleGUI"); + + this.canvasWidth = canvasWidth; + this.canvasHeight = canvasHeight; + this.actionListSize = actionList.size(); + this.originX = (canvasWidth - 1) / 2; + this.originY = (canvasHeight - 1) / 2; + + setDefaultCloseOperation(EXIT_ON_CLOSE); + Container cp = getContentPane(); + GroupLayout layout = new GroupLayout(cp); + cp.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + runButton = new JButton(); + runButton.setName("runButton"); + runButton.setText("Run!"); + + isRunning = false; + + currentActionLabel = new JLabel(); + currentActionLabel.setName("currentActionLabel"); + currentActionLabel.setText("Currently performing: "); + + currentAction = new JLabel(); + currentAction.setName("currentAction"); + currentAction.setText("STOPPED"); + + canvas = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB); + graphics = canvas.createGraphics(); + graphics.setBackground(canvasBGColor); + graphics.clearRect(0, 0, canvasWidth, canvasHeight); + graphics.setPaint(canvasLineColor); + graphics.setStroke(new BasicStroke(1.0f)); + + drawLabel = new JLabel(new ImageIcon(canvas)); + drawLabel.setName("drawLabel"); + + this.actionList = actionList; + + runButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + + if (!isRunning) { + runButton.setText("Stop"); + isRunning = true; + currAnimationThread = new AnimationThread(); + currAnimationThread.execute(); + } else { + currAnimationThread.cancel(true); + } + } + }); + + layout.setHorizontalGroup(layout.createParallelGroup() + .addComponent(drawLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(runButton) + .addComponent(currentActionLabel) + .addComponent(currentAction))); + layout.setVerticalGroup(layout.createSequentialGroup() + .addComponent(drawLabel) + .addGroup(layout.createParallelGroup(Alignment.CENTER) + .addComponent(runButton) + .addComponent(currentActionLabel) + .addComponent(currentAction))); + + pack(); + } + + public void stopAnimation() { + currentAction.setText("STOPPED"); + isRunning = false; + runButton.setText("Run!"); + } + + public List getActionList() { + return actionList; + } + + public BufferedImage getCanvas() { + return canvas; + } + + public JLabel getDrawLabel() { + return drawLabel; + } + + public void setCurrentAction(String s) { + currentAction.setText(s); + } + + private class AnimationThread extends SwingWorker { + + AnimationThread() { + super(); + } + + @Override + protected Void doInBackground() throws Exception { + animate(); + return null; + } + + private void animate() { + graphics.clearRect(0, 0, canvasWidth, canvasHeight); + drawLabel.repaint(); + + // first, calculate the total length of line segments and turns, + // in order to allocate drawtime proportionally later + + double totalLength = 0; + for (Action a : actionList) { + if (a.type == ActionType.TURN) { + totalLength += LENGTH_OF_A_TURN; + } else if (a.type == ActionType.FORWARD) { + totalLength += a.lineSeg.length(); + } + } + + // now, draw the animation + + double cumulativeLength = 0; + long initialTime = System.currentTimeMillis(); + for (int i = 0; i < actionListSize; i++) { + if (isCancelled()) { + break; + } + Action action = actionList.get(i); + setCurrentAction((i + 1) + ". " + action); + if (action.lineSeg != null) { + long startTime = (long) (initialTime + cumulativeLength / totalLength * MILLIS_PER_DRAWING); + cumulativeLength += action.lineSeg.length(); + long endTime = (long) (initialTime + cumulativeLength / totalLength * MILLIS_PER_DRAWING); + draw(action.lineSeg, startTime, endTime); + } else { + cumulativeLength += LENGTH_OF_A_TURN; + long drawTime = (long) (initialTime + cumulativeLength / totalLength * MILLIS_PER_DRAWING - System.currentTimeMillis()); + if (drawTime > 0) { + try { + Thread.sleep((long) drawTime); + } catch (InterruptedException e) { + } + } + } + } + stopAnimation(); + } + + private void draw(LineSegment lineSeg, long initialTime, long endTime) { + long drawTime = endTime - initialTime; + + double initX = originX + lineSeg.start.x; + double initY = originY - lineSeg.start.y; + + double finalX = originX + lineSeg.end.x; + double finalY = originY - lineSeg.end.y; + + int fromX = (int) initX; + int fromY = (int) initY; + + boolean abort = false; + long elapsedTime = System.currentTimeMillis() - initialTime; + + while (!abort && elapsedTime + MILLIS_PER_FRAME < drawTime) { + // while we have time remaining for this action + double fractionDone = Math.max(elapsedTime * 1.0 / drawTime, 0); + int toX = (int) Math.round(initX * (1 - fractionDone) + finalX * fractionDone); + int toY = (int) Math.round(initY * (1 - fractionDone) + finalY * fractionDone); + graphics.drawLine(fromX, fromY, toX, toY); + drawLabel.repaint(); + + try { + Thread.sleep(MILLIS_PER_FRAME); + } catch (InterruptedException e) { + abort = true; + } + + // update + fromX = toX; + fromY = toY; + + elapsedTime = System.currentTimeMillis() - initialTime; + } + + // finish the line if we're still not done + if (!abort && (fromX != finalX || fromY != finalY)) { + graphics.drawLine(fromX, fromY, (int) finalX, (int) finalY); + drawLabel.repaint(); + } + } + } +} diff --git a/src/turtle/TurtleSoup.java b/src/turtle/TurtleSoup.java new file mode 100644 index 0000000..3c0407d --- /dev/null +++ b/src/turtle/TurtleSoup.java @@ -0,0 +1,125 @@ +package turtle; + +import java.lang.Math; +import java.util.List; +import java.util.ArrayList; + +public class TurtleSoup { + + /** + * Draw a square. + * + * @param turtle the turtle context + * @param sideLength length of each side + */ + public static void drawSquare(Turtle turtle, int sideLength) { + throw new RuntimeException(); + } + + /** + * Determine inside angles of a regular polygon. + * + * There is a simple formula for calculating the inside angles of a polygon; + * you should derive it and use it here. + * + * @param sides number of sides, must be >2 + * @return angle, in degrees between 0 and 360 + */ + public static double calculateRegularPolygonAngle(int sides) { + throw new RuntimeException(); + } + + /** + * Determine number of sides given the interior angles of a regular polygon. + * + * There is a simple formula for this; you should derive it and use it here. + * Make sure you *properly round* the answer before you return it (see java.lang.Math). + * HINT: it is easier if you think about the exterior angles. + * + * @param angle interior angles, in degrees + * @return the integer number of sides + */ + public static int calculatePolygonSidesFromAngle(double angle) { + throw new RuntimeException(); + } + + /** + * Given the number of sides, draw a regular polygon. + * + * (0,0) is the lower-left corner of the polygon; use only right-hand turns to draw. + * + * @param turtle the turtle context + * @param sides number of sides of the polygon to draw + * @param sideLength length of each side + */ + public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) { + throw new RuntimeException(); + } + + /** + * Given the current direction, current location, and a target location, calculate the heading + * towards the target point. + * + * The return value is the angle input to turn() that would point the turtle in the direction of + * the target point (targetX,targetY), given that the turtle is already at the point + * (currentX,currentY) and is facing the angle currentHeading. The return angle must be expressed in + * degrees, and 0 <= angle < 360. + * + * HINT: look at http://en.wikipedia.org/wiki/Atan2 and Java's math libraries + * + * @param currentHeading current direction as clockwise from north + * @param currentX currentY current location + * @param targetX targetY target point + * @return adjustment to heading (right turn amount) to get to target point. + * Must be positive, and less than 360. + */ + public static double calculateHeadingToPoint(double currentHeading, int currentX, int currentY, + int targetX, int targetY) { + throw new RuntimeException(); + } + + /** + * Given a sequence of points, calculate the heading adjustments needed to get from each point to the next. + * + * You should use your calculateHeadingToPoint() function in this one to simplify things. Assume + * that the turtle is at the first point, facing up (i.e. 0 degrees), and for further points, + * assume that the turtle is facing the direction last specified. + * + * @param xCoords list of x-coordinates (must be same length as yCoords) + * @param yCoords list of y-coordinates (must be same length as xCoords) + * @return list of heading adjustments between points, of size #points-1. + */ + public static List calculateHeadings(List xCoords, List yCoords) { + throw new RuntimeException(); + } + + /** + * Draw your personal, custom art. + * + * Many interesting images can be drawn using the simple implementation of a turtle. For this + * function, draw something interesting; the complexity can be as little or as much as you want. + * We'll be peer-voting on the different images, and the highest-rated one will win a prize: + * free dinner with select staff members. + * + * @param turtle the turtle context + */ + public static void drawPersonalArt(Turtle turtle) { + throw new RuntimeException(); + } + + /** + * Main method + * + * This is the method that runs when you run "java TurtleSoup". Right now, it + * will run your drawSquare() method. + */ + public static void main(String args[]) { + DrawableTurtle turtle = new DrawableTurtle(); + + drawSquare(turtle, 40); + + // draw the window + turtle.draw(); + } + +} diff --git a/src/turtle/TurtleSoupTest.java b/src/turtle/TurtleSoupTest.java new file mode 100644 index 0000000..5365f90 --- /dev/null +++ b/src/turtle/TurtleSoupTest.java @@ -0,0 +1,61 @@ +package turtle; + +import org.junit.Test; +import static org.junit.Assert.*; +import java.util.List; +import java.util.ArrayList; + +public class TurtleSoupTest { + + /** + * Tests calculateRegularPolygonAngle(). + */ + @Test + public void calculateRegularPolygonAngleTest() { + assertEquals(60.0, TurtleSoup.calculateRegularPolygonAngle(3), 0.001); + assertEquals(128.57, TurtleSoup.calculateRegularPolygonAngle(7), 0.01); + assertEquals(108.0, TurtleSoup.calculateRegularPolygonAngle(5), 0.001); + } + + /** + * Tests calculatePolygonSidesFromAngle(). + */ + @Test + public void calculatePolygonSidesFromAngleTest() { + assertEquals(3, TurtleSoup.calculatePolygonSidesFromAngle(60.0)); + assertEquals(7, TurtleSoup.calculatePolygonSidesFromAngle(128.57)); + assertEquals(5, TurtleSoup.calculatePolygonSidesFromAngle(108.0)); + + } + + /** + * Test calculateHeadingToPoint() + */ + @Test + public void calculateHeadingToPointTest() { + assertEquals(0.0, TurtleSoup.calculateHeadingToPoint(0.0, 0, 0, 0, 1), 0.001); + assertEquals(90.0, TurtleSoup.calculateHeadingToPoint(0.0, 0, 0, 1, 0), 0.001); + assertEquals(359.0, TurtleSoup.calculateHeadingToPoint(1.0, 4, 5, 4, 6), 0.001); + } + + /** + * Test calculateHeadings() + */ + @Test + public void calculateHeadingsTest() { + List xpoints = new ArrayList(); + List ypoints = new ArrayList(); + xpoints.add(0); + xpoints.add(1); + xpoints.add(1); + ypoints.add(0); + ypoints.add(1); + ypoints.add(2); + + List result = TurtleSoup.calculateHeadings(xpoints, ypoints); + assertEquals(2, result.size()); + assertEquals(45.0, result.get(0), 0.001); + assertEquals(315.0, result.get(1), 0.001); + + } +}