My God, It's Full of Stars!

Let's use our new tools to draw some stars. For the purpose of this exercise let's assume that a star is a polygon with p points. However, instead of connecting each point to its neighbours, we'll connect them to the nth point around the circumference.

For example, Figure sequences:stars shows stars with p=11 and n=1 to 5. n=1 produces a regular polygon while values of n from 2 upwards produce stars with increasingly sharp points:

Stars with `p=11` and `n=1 to 5`

Write code to draw the diagram above. Start by writing a method to draw a star given p and n:

def star(p: Int, n: Int, radius: Double): Image =
  ???

Hint: use the same technique we used for polygon previously.

<div class="solution"> Here's the star method. We've renamed p and n to points and skip for clarity:

def star(sides: Int, skip: Int, radius: Double): Image = {
  import Point._
  import PathElement._

  val rotation = 360.degrees * skip / sides

  val start = moveTo(polar(radius, 0.degrees))
  val elements = (1 until sides).toList map { index =>
    val point = polar(radius, rotation * index)
    lineTo(point)
  }

  Image.path(ClosedPath(start :: elements)).strokeWidth(2)
}

</div>

Using structural recursion and beside write a method allBeside with the signature

def allBeside(images: List[Image]): Image =
  ???

We'll use allBeside to create the row of stars. To create the picture we only need to use values of skip from 1 to sides/2 rounded down. For example:

allBeside(
  (1 to 5).toList map { skip =>
    star(11, skip, 100)
  }
)

<div class="solution"> We can use the structural recursion skeleton to write this method.

We start with

def allBeside(images: List[Image]): Image =
  images match {
    case Nil => ???
    case hd :: tl => ???
  }

Remembering the recursion gives us

def allBeside(images: List[Image]): Image =
  images match {
    case Nil => ???
    case hd :: tl => /* something here */ allBeside(tl)
  }

Finally we can fill in the base and recursive cases.

def allBeside(images: List[Image]): Image =
  images match {
    case Nil => Image.empty
    case hd :: tl => hd.beside(allBeside(tl))
  }

</div>

When you've finished your row of stars, try constructing a larger image from different values of p and n. There is an example in Figure sequences:all-star. Hint: You will need to create a method allAbove similar to allBeside.

Stars with `p=3 to 33 by 2` and `n=1 to p/2`

<div class="solution"> To create the image in Figure sequences:stars2 we started by creating a method to style a star.

def style(img: Image, hue: Angle): Image = {
  img.
    strokeColor(Color.hsl(hue, 1.0, 0.25)).
    fillColor(Color.hsl(hue, 1.0, 0.75))
}

We then created allAbove, which you will notice is very similar to allBeside (wouldn't it be nice if we could abstract this pattern?)

def allAbove(imgs: List[Image]): Image =
  imgs match {
    case Nil => Image.empty
    case hd :: tl => hd above allAbove(tl)
  }

The updated scene then becomes:

allAbove((3 to 33 by 2).toList map { sides =>
  allBeside((1 to sides/2).toList map { skip =>
    style(star(sides, skip, 20), 360.degrees * skip / sides)
  })
})

</div>