2 object DragonPath: def apply(startPoint : Point, direction: Direction, length: Int): DragonPath = val nextPoint = startPoint.translate(direction, amount = length) List(nextPoint, startPoint) extension (path: DragonPath) def lines: List[Line] = if path.length < 2 then Nil else path.zip(path.tail) @tailrec def grow(age: Int): DragonPath = if age == 0 || path.size < 2 then path else path.plusRotatedCopy.grow(age - 1) private def plusRotatedCopy = path.reverse.rotate(rotationCentre=path.head, angle=ninetyDegreesClockwise) ++ path case class Dragon(start: Point, age: Int, length: Int, direction: Direction): val path: DragonPath = DragonPath(start, direction, length) .grow(age) case class Point(x: Float, y: Float) type Radians = Double extension (p: Point) def deviceCoords(panelHeight: Int): (Int, Int) = (Math.round(p.x), panelHeight - Math.round(p.y)) def translate(direction: Direction, amount: Float): Point = direction match case North => Point(p.x, p.y + amount) case South => Point(p.x, p.y - amount) case East => Point(p.x + amount, p.y) case West => Point(p.x - amount, p.y) def rotate(rotationCentre: Point, angle: Radians): Point = val (c, ϕ) = (rotationCentre, angle) val (cosϕ, sinϕ) = (math.cos(ϕ).toFloat, math.sin(ϕ).toFloat) val rotationMatrix: Matrix[3,3,Float] = MatrixFactory[3, 3, Float].fromTuple( ( cosϕ, sinϕ, 0f), ( -sinϕ, cosϕ, 0f), (-c.x * cosϕ + c.y * sinϕ + c.x, -c.x * sinϕ - c.y * cosϕ + c.y, 1f) ) val rowVector: Matrix[1,3,Float] = MatrixFactory[1,3,Float].rowMajor(p.x,p.y,1f) val rotatedRowVector: Matrix[1, 3, Float] = rowVector dot rotationMatrix val (x, y) = (rotatedRowVector(0, 0), rotatedRowVector(0, 1)) Point(x, y) extension (points: List[Point]) def rotate(rotationCentre: Point, angle: Radians) : List[Point] = points.map(point => point.rotate(rotationCentre, angle)) type Line = (Point, Point) extension (line: Line) def start: Point = line(0) def end: Point = line(1) enum Direction: case North, East, South, West