Friday, July 24, 2015

Draw a plain text spiral in Scala

Here is the code:

import java.io.{File, PrintWriter}
import scala.io.Source

object Element {
  private class ArrayElement(
                              val contents: Array[String]
                              ) extends Element
  private class LineElement(s: String) extends Element {
    val contents = Array(s)
  }
  private class UniformElement(
                                ch: Char,
                                override val width: Int,
                                override val height: Int
                                ) extends Element {
    private val line = ch.toString * width
    def contents = Array.fill(height)(line)
  }
  def elem(contents: Array[String]): Element = {
    new ArrayElement(contents)
  }
  def elem(s: String): Element = {
    new ArrayElement(Array(s))
  }
  def elem(chr: Char, width: Int, height: Int): Element = {
    new UniformElement(chr, width, height)
  }
}

import Element.elem

abstract class Element {
  // contents to be implemented
  def contents: Array[String]

  def width: Int = contents(0).length

  def height: Int = contents.length

  def above(that: Element): Element = {
    val this1 = this widen that.width
    val that1 = that widen this.width
    elem(this1.contents ++ that1.contents)
  }

  def beside(that: Element): Element = {
    val this1 = this heighten that.height
    val that1 = that heighten this.height
    elem(
      for ((line1, line2) <- this1.contents zip that1.contents)
        yield line1 + line2
    )
  }

  def heighten(h: Int): Element = {
    if (h <= height) this
    else {
      val top = elem(' ', width, (h - height) / 2)
      val bottom = elem(' ', width, h - height - top.height)
      top above this above bottom
    }
  }

  def widen(w: Int): Element = {
    if (w <= width) this
    else {
      val left = elem(' ', (w - width) / 2, height)
      val right = elem(' ', w - width - left.width, height)
      left beside this beside right
    }
  }

  override def toString = contents mkString "\n"
}


object Spiral {
  val space = elem(" ")
  val corner = elem("+")
  def spiral(nEdges: Int, direction: Int): Element = {
    if(nEdges == 1) elem("+")
    else {
      val sp = spiral(nEdges - 1, (direction + 3) % 4) // or (direction - 1) % 4, but we don't want negative numbers
      def verticalBar = elem('|', 1, sp.height)
      def horizontalBar = elem('-', sp.width, 1)
      if(direction == 0)
        (corner beside horizontalBar) above (sp beside space)
      else if(direction == 1)
        (sp above space) beside (corner above verticalBar)
      else if(direction == 2)
        (space beside sp) above (horizontalBar beside corner)
      else
        (verticalBar above corner) beside (space above sp)
    }
  }
  def draw(n: Int): Unit = {
    println(spiral(n, 0))
  }
}

Test it:

Spiral.draw(7)
+------
|      
| +--+ 
| |  | 
| ++ | 
|    | 
+----+ 

The crital part here is the recursive way of thinking:

I want to draw a 7-node spiral. What should I do if I already have a 6-node spiral?

0 comments: