package snake /** A thing to draw. The picture is plain data; drawing it is somebody else's job (m07). */ enum Shape: case Rect(x: Int, y: Int, w: Int, h: Int, color: String) case Text(x: Int, y: Int, text: String, color: String, size: Int, align: String) /** A picture: shapes drawn in order. */ type Scene = List[Shape] /** m06 view: the picture is plain data (a list of shapes); drawing it is somebody else's job (m07). */ object View: val CELL: Int = 20 private def rect(x: Int, y: Int, w: Int, h: Int, color: String): Shape = Shape.Rect(x, y, w, h, color) private def text(x: Int, y: Int, str: String, size: Int, align: String): Shape = Shape.Text(x, y, str, "#ffffff", size, align) // One cell, inset by 1px so neighbouring segments show a gap. private def cell(p: Point, color: String): Shape = rect(p.x * CELL + 1, p.y * CELL + 1, CELL - 2, CELL - 2, color) private def overlay(state: GameState): Scene = val cx = state.width * CELL / 2 val cy = state.height * CELL / 2 def banner(title: String, hint: String): Scene = List(text(cx, cy, title, 32, "center"), text(cx, cy + 30, hint, 16, "center")) state.status match case Status.Paused => banner("Paused", "Press Space to resume") case Status.GameOver => banner("Game Over", "Press R to restart") case Status.Won => banner("You Win!", "Press R to restart") case Status.Playing => Nil def view(state: GameState): Scene = List(rect(0, 0, state.width * CELL, state.height * CELL, "#111111")) ++ state.apple.map(cell(_, "#e53935")).toList ++ state.snake.zipWithIndex.map((p, i) => cell(p, if i == 0 then "#7cb342" else "#43a047")) ++ List(text(6, 18, s"Score: ${state.score}", 16, "left")) ++ overlay(state)