package snake /** m03 rules: the pure game rules. Each function takes a state and returns a new one. */ object Rules: def move(p: Point, dir: Direction): Point = val d = Model.delta(dir) Point(p.x + d.x, p.y + d.y) def inBounds(state: GameState, p: Point): Boolean = p.x >= 0 && p.x < state.width && p.y >= 0 && p.y < state.height // Validate against `dir` (last moved), not `nextDir`: Up then Left within one tick must not fold the snake back on itself. def turn(state: GameState, dir: Direction): GameState = if state.status == Status.Playing && dir != Model.opposite(state.dir) then state.copy(nextDir = dir) else state def advance(state: GameState): GameState = if state.status != Status.Playing then state else val d = state.nextDir val head = move(state.snake.head, d) val eating = state.apple.contains(head) // When not eating, the tail leaves its cell this step, so the head may move into it. val body = if eating then state.snake else Util.dropLast(state.snake) if !inBounds(state, head) || Util.contains(body, head, _ == _) then state.copy(dir = d, status = Status.GameOver) else state.copy( snake = head :: body, dir = d, apple = if eating then None else state.apple, score = state.score + (if eating then 1 else 0) )