Skip to content

Instantly share code, notes, and snippets.

@waynejo
Created September 2, 2016 12:18
Show Gist options
  • Save waynejo/46a18f4603c0f3fb99cdcb42402e1a79 to your computer and use it in GitHub Desktop.
Save waynejo/46a18f4603c0f3fb99cdcb42402e1a79 to your computer and use it in GitHub Desktop.
package lascala.turtle
import lascala.turtle.common._
object BasicOO {
class Turtle(log: String => Unit) {
var currentPosition: Position = initialPosition
var currentAngle: Angle = 0.0.degree
var currentColor: PenColor = initialColor
var currentPenState: PenState = initialPenState
def move(distance: Distance) = {
log(f"Move $distance%.1f")
val newPosition = currentPosition.move(distance, currentAngle)
if (currentPenState == PenState.Down) dummyDrawLine(log, currentPosition, newPosition, currentColor)
currentPosition = newPosition
}
def turn(angle: Angle) = {
log(f"Turn ${angle.degree}%.1f")
currentAngle = (currentAngle.degree + angle.degree).degree
}
def penUp() = {
log("Pen up")
currentPenState = PenState.Up
}
def penDown() = {
log("Pen down")
currentPenState = PenState.Down
}
def setColor(color:PenColor) = {
log(s"SetColor $color")
currentColor = color
}
}
}
package lascala.turtle
package object common {
type Distance = Double
case class Angle(degree: Double) {
def toRadian: Double = degree / 180 * math.Pi
}
implicit class AngleUnit(val angle: Double) extends AnyVal {
def degree: Angle = Angle(angle)
}
sealed trait PenState
object PenState {
case object Up extends PenState
case object Down extends PenState
}
sealed trait PenColor
object PenColor {
case object Black extends PenColor
case object Red extends PenColor
case object Blue extends PenColor
}
case class Position(x: Double, y: Double) {
def move(distance: Distance, angle: Angle): Position =
Position(x + distance * (math cos angle.toRadian), y + distance * (math sin angle.toRadian))
override def toString(): String = f"($x%.1f, $y%.1f)"
}
implicit class DoubleRound2(val d: Double) extends AnyVal {
def round2: Double = (math rint d * 100) / 100.0
}
val (initialPosition, initialColor, initialPenState) = (Position(0, 0), PenColor.Black, PenState.Down)
def dummyDrawLine(log: String => Unit, oldPos: Position, newPos: Position, color: PenColor): Unit =
log(s"...Draw line from $oldPos to $newPos using $color")
object Functional {
class PipedObject[T] private[Functional] (value:T)
{
def |>[R] (f : T => R) = f(this.value)
}
implicit def toPiped[T] (value:T) = new PipedObject[T](value)
}
}
package lascala.turtle
import lascala.turtle.BasicOO.Turtle
import lascala.turtle.common.Angle
import lascala.turtle.common._
class TurtleApi {
def validateDistance(distanceStr:String): Float = {
try {
distanceStr.toFloat
} catch {
case e:Exception => {
val msg = s"Invalid distance '$distanceStr' [$e]"
throw new RuntimeException(msg)
}
}
}
def validateAngle(angleStr:String): Angle = {
try {
angleStr.toDouble.degree
} catch {
case e:Exception => {
val msg = s"Invalid angle '$angleStr' [$e]"
throw new RuntimeException(msg)
}
}
}
def validateColor(colorStr:String): PenColor = {
colorStr match {
case "Black" => PenColor.Black
case "Blue" => PenColor.Blue
case "Red" => PenColor.Red
case _ =>
val msg = s"Color '$colorStr' is not recognized"
throw new RuntimeException(msg)
}
}
var turtle = new Turtle(s => println(s))
def exec(commandStr:String) = {
val tokens = commandStr.split(' ').toList.map(_.trim)
tokens match {
case "Move" :: distanceStr :: Nil =>
val distance = validateDistance(distanceStr)
turtle.move(distance)
case "Turn" :: angleStr :: Nil =>
val angle = validateAngle(angleStr)
turtle.turn(angle)
case "Pen" :: "Up" :: Nil =>
turtle.penUp()
case "Pen" :: "Down" :: Nil =>
turtle.penDown()
case "SetColor" :: colorStr :: Nil =>
val color = validateColor(colorStr)
turtle.setColor(color)
case _ =>
val msg = s"Instruction '$commandStr' is not recognized"
throw new RuntimeException(msg)
}
}
}
package lascala.turtle
import lascala.turtle.BasicFP.TurtleState
import lascala.turtle.BasicOO._
import lascala.turtle.common._
import utest._
object TurtleAPITest extends TestSuite {
val tests = this {
'Move {
val turtleAPI = new TurtleApi()
turtleAPI.exec("Move 1.0")
val Position(x, y) = turtleAPI.turtle.currentPosition
assert((x - 1.0).abs < 0.000001)
assert((y - 0.0).abs < 0.000001)
}
'Turn {
val turtleAPI = new TurtleApi()
turtleAPI.exec("Turn 90.0")
assert((turtleAPI.turtle.currentAngle.degree - 90.0).abs < 0.000001)
}
'PenUp {
val turtleAPI = new TurtleApi()
turtleAPI.exec("Pen Up")
assert(turtleAPI.turtle.currentPenState == PenState.Up)
}
'PenDown {
val turtleAPI = new TurtleApi()
turtleAPI.exec("Pen Down")
assert(turtleAPI.turtle.currentPenState == PenState.Down)
}
'SetColor {
val turtleAPI = new TurtleApi()
turtleAPI.exec("SetColor Blue")
assert(turtleAPI.turtle.currentColor == PenColor.Blue)
}
'DrawPolygon {
def drawPolygon(n:Int) = {
val turtleAPI = new TurtleApi()
def angle = 360.0 / n.toDouble
def angleDegrees = angle.degree
// define a function that draws one side
//def oneSide(state:TurtleState, sideNumber:Int) = state |> move(100.0) |> turn(angleDegrees)
def drawOneSide() = {
turtleAPI.exec("Move 100.0")
turtleAPI.exec(s"Turn $angle")
}
(1 to n) foreach (v => drawOneSide())
}
drawPolygon(5)
}
'TriggerError {
def triggerError() = {
val turtleApi = new TurtleApi()
turtleApi.exec("Move bad")
}
intercept[RuntimeException] {
triggerError()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment