package Assignment3.TurtleEDSL import Assignment3.GraphicsCanvas._ import scala.language.implicitConversions import java.awt.Color import java.util.Random object Assignment3Embedded { /**************** * Exercise 1 * ****************/ trait TurtleDSL { type TurtleGraphics // Creates a new TurtleGraphics instance val empty: TurtleGraphics // Do action tg1, then do action tg2 def append(tg1: TurtleGraphics, tg2: TurtleGraphics): TurtleGraphics // Puts the pen up: no lines will be drawn when moving def penUp(): TurtleGraphics // Puts the pen down: lines will be drawn when moving def penDown(): TurtleGraphics // Goes forward by `distance` def forward(distance: Integer): TurtleGraphics // Goes backward by `distance` def backward(distance: Integer): TurtleGraphics = {// BEGIN ANSWER forward(-distance) } // END ANSWER // Turns right by `amount` degrees def right(amount: Integer): TurtleGraphics // Turns left by `amount` degrees def left(amount: Integer): TurtleGraphics = {// BEGIN ANSWER right(-amount) } // END ANSWER // Sets the line color to `color`. def setColor(color: Color): TurtleGraphics // Sets the line colour to a colour randomly picked from `cols`. def setRandomColor(cols: List[Color]): TurtleGraphics // Calculates an angle difference modulo 360. So: // calculateAngleDiff(359, 5) = 4 // calculateAngleDiff(4, -5) = 359 def calculateAngleDiff(angle: Integer, diff: Integer): Integer = {// BEGIN ANSWER val resDiff = diff + angle val resDiff1 = if (resDiff < 0) { resDiff + 360 } else { resDiff } resDiff1 % 360 } // END ANSWER // Given initial co-ordinates `x0` and `y0`, an angle `angle`, and a distance `distance`, // returns a pair of the calculated new co-ordinates. def calculateNewCoords(x0: Integer, y0: Integer, angle: Integer, distance: Integer): (Integer, Integer) = {// BEGIN ANSWER val x1 = x0 + (distance * math.sin(math.toRadians(angle.toDouble))).toInt val y1 = y0 - (distance * math.cos(math.toRadians(angle.toDouble))).toInt (x1, y1) }// END ANSWER // Saves the turtle graphics given by `tg` to `filename`. def draw(tg: TurtleGraphics, width: Integer, height: Integer): GraphicsCanvas // Saves the turtle graphics given by `tg` to `filename`. def saveToFile(tg: TurtleGraphics, width: Integer, height: Integer, filename: String): Unit = { val canvas = draw(tg,width,height) canvas.saveToFile(filename) } // Scala magic to allow <> infix operator. No implementation needed here -- you can ignore this. class AppendAssoc(x: TurtleGraphics) { def <> (y: TurtleGraphics): TurtleGraphics = append(x,y) } implicit def tg2AppendAssoc(x: TurtleGraphics): AppendAssoc = new AppendAssoc(x) } // BEGIN ANSWER // State used to keep track of canvas, co-ords, angle, pen state final case class TurtleState(graphics: GraphicsCanvas, isPenDown : Boolean, x : Integer, y: Integer, angle: Integer) // Deep embedding object DeepTurtleDSL extends TurtleDSL { // Set the TurtleGraphics abstract type to TurtleCommand type TurtleGraphics = TurtleCommand val rand = new Random(System.currentTimeMillis()) // Create a datatype with cases illustrating all commands abstract class TurtleCommand case class TurtleEmpty() extends TurtleCommand case class TurtleAppend(tg1: TurtleGraphics, tg2: TurtleGraphics) extends TurtleCommand case class TurtlePenUp() extends TurtleCommand case class TurtlePenDown() extends TurtleCommand case class TurtleForward(distance: Integer) extends TurtleCommand case class TurtleRight(amount: Integer) extends TurtleCommand case class TurtleSetColor(col: Color) extends TurtleCommand case class TurtleSetRandomColor(cols: List[Color]) extends TurtleCommand // Boring constructors mapping functions on the DSL to each case class val empty = TurtleEmpty() def append(tg1: TurtleGraphics, tg2: TurtleGraphics) = TurtleAppend(tg1, tg2) def penUp() = TurtlePenUp() def penDown() = TurtlePenDown() def forward(distance: Integer) = TurtleForward(distance) def right(amount: Integer) = TurtleRight(amount) def setColor(col: Color) = TurtleSetColor(col) def setRandomColor(cols: List[Color]) = TurtleSetRandomColor(cols) // The saveToFile function does all the work, acting as an interpreter for // each DSL node. def draw(tg: TurtleGraphics, width: Integer, height: Integer): GraphicsCanvas = { // Firstly, create a graphics instance val canvas = new GraphicsCanvas(width, height) val endState = runTurtleGraphics(tg, TurtleState(canvas, true, width / 2, height / 2, 0)) canvas.drawTurtle(endState.x, endState.y, endState.angle) return canvas } def runTurtleGraphics(command: TurtleGraphics, state: TurtleState): TurtleState = command match { case TurtleEmpty() => state case TurtleAppend(tg1, tg2) => { val st1 = runTurtleGraphics(tg1, state) runTurtleGraphics(tg2, st1) } case TurtlePenUp() => { state.copy(isPenDown = false) } case TurtlePenDown() => { state.copy(isPenDown = true) } case TurtleForward(dist) => { // Firstly, calculate new co-ordinates val x0 = state.x val y0 = state.y val (x1, y1) = calculateNewCoords(x0, y0, state.angle, dist) // If the pen is down, draw a line if (state.isPenDown) { state.graphics.drawLine(x0, y0, x1, y1) } // Finally, update the state with the new co-ordinates state.copy (x = x1, y = y1) } case TurtleRight(amt) => state.copy (angle = calculateAngleDiff(state.angle, amt)) case TurtleSetColor(col) => { state.graphics.setLineColor(col) state } case TurtleSetRandomColor(cols) => { val rand_index = rand.nextInt(cols.length) val selected_col = cols(rand_index) state.graphics.setLineColor(selected_col) state } } } // Shallow embedding object ShallowTurtleDSL extends TurtleDSL { // Each statement is side-effecting and modifies the internal state, which we // thread through the computation type TurtleGraphics = (TurtleState => TurtleState) val rand = new Random(System.currentTimeMillis()) // Empty state is a no-op val empty = (st: TurtleState) => st // Append does the first and then the second def append(tg1: TurtleGraphics, tg2: TurtleGraphics) = (st: TurtleState) => { val st1 = tg1(st) tg2(st1) } // penUp and penDown modify the state to reflect the pen state def penUp() = (st: TurtleState) => st.copy(isPenDown = false) def penDown() = (st: TurtleState) => st.copy(isPenDown = true) // Forward draws a line if the pen is down, but modifies the x and y regardless def forward(distance: Integer) = (st: TurtleState) => { // Firstly, calculate new co-ordinates val x0 = st.x val y0 = st.y val (x1, y1) = calculateNewCoords(x0, y0, st.angle, distance) // If the pen is down, draw a line if (st.isPenDown) { st.graphics.drawLine(x0, y0, x1, y1) } // Finally, update the state with the new co-ordinates st.copy (x = x1, y = y1) } // Right and left change the angle def right(amount: Integer) = (st: TurtleState) => st.copy (angle = calculateAngleDiff(st.angle, amount)) // setColor changes the colour of the lines def setColor(col: Color) = (st: TurtleState) => { st.graphics.setLineColor(col); st } def setRandomColor(cols: List[Color]) = (st: TurtleState) => { val rand_index = rand.nextInt(cols.length) val selected_col = cols(rand_index) st.graphics.setLineColor(selected_col) st } def draw(tg: TurtleGraphics, width: Integer, height: Integer): GraphicsCanvas = { // Firstly, create a graphics instance val canvas = new GraphicsCanvas(width, height) val endState = tg(TurtleState(canvas, true, width / 2, height / 2, 0)) canvas.drawTurtle(endState.x, endState.y, endState.angle) return canvas } } // END ANSWER object Testing extends TurtleDSL { // This implementation simply prints out each command as it is encountered. // It is NOT a good model to use for implementing the EDSL, but might be useful // for debugging. type TurtleGraphics = Unit // no-ops val empty = () def append(tg1: TurtleGraphics, tg2: TurtleGraphics) = () def penUp() = println("penUp()") def penDown() = println("penDown()") def forward(distance: Integer) = println("forward(" + distance + ")") def right(amount: Integer) = println("right(" + amount + ")") def setColor(col: Color) = println("setColor(" + col + ")") def setRandomColor(cols: List[Color]) = println("setRandomColor(" + cols + ")") def draw(tg: TurtleGraphics, width: Integer, height: Integer): GraphicsCanvas = { println("draw(_, " + width + ", " + height + ")") return new GraphicsCanvas(width,height) } } /**************** * Exercise 2 * ****************/ object TurtleDSLImpl extends TurtleDSL { type TurtleGraphics = Unit // TODO: change this // no-ops val empty = () // TODO: change this def append(tg1: TurtleGraphics, tg2: TurtleGraphics) = sys.error("todo") def penUp() = sys.error("todo") def penDown() = sys.error("todo") def forward(distance: Integer) = sys.error("todo") def right(amount: Integer) = sys.error("todo") def setColor(col: Color) = sys.error("todo") def setRandomColor(cols: List[Color]) = sys.error("todo") def draw(tg: TurtleGraphics, width: Integer, height: Integer): GraphicsCanvas = sys.error("todo") } /////////////////////////////////////////////////////////////////////////// // Test code - nothing to implement below this point but you may want to // // add more tests of your own. // /////////////////////////////////////////////////////////////////////////// import ShallowTurtleDSL._ def square() = { forward(100) <> right(90) <> setColor(Color.RED) <> forward(100) <> right(90) <> forward(100) <> right(90) <> forward(100) } def squareRec() = { def squareRecInner(sides: Integer): TurtleGraphics = { if (sides <= 0) { empty } else { forward(100) <> right(90) <> squareRecInner(sides - 1) } } squareRecInner(4) } def drawSquares(numToDraw: Integer): TurtleGraphics = { if (numToDraw <= 0) { empty } else { // Firstly, offset our position and get back into the correct angle penUp() <> left(90) <> forward(80) <> left(90) <> forward(80) <> right(180) <> penDown() <> // Next, draw the square and recurse squareRec() <> drawSquares(numToDraw - 1) } } def circleyThing(): TurtleGraphics = { def circleyThingInner(n: Integer): TurtleGraphics = { if (n <= 0) { empty } else { forward(10) <> right(10) <> circleyThingInner(n-1) } } circleyThingInner(36) } def spiral(): TurtleGraphics = { def lineTurns(len: Integer): TurtleGraphics = { val colors = List(Color.RED, Color.GREEN, Color.BLUE, Color.PINK, Color.ORANGE, Color.YELLOW) if (len > 500) { empty } else { setRandomColor(colors) <> forward(len) <> right(60) <> lineTurns(len + 10) } } forward(15) <> lineTurns(20) } type Filename = String val toRun = List( (square(), "squareSimple.png"), (squareRec(), "squareRec.png"), (drawSquares(4), "fourSquares.png"), (circleyThing(), "circleyThing.png"), (spiral(), "spiral.png") ) def drawFiles(xs: List[(TurtleGraphics, Filename)]) = { xs.foreach (p => { val (tg, fn) = p print("Processing " + fn + "...\n") saveToFile(tg, 2000, 2000, fn) }) } def main(args: Array[String]): Unit = { drawFiles(toRun) } } // vim: set ts=2 sw=2 et sts=2: