package snake /** * m05 game: one `update` turns every action into a new state; replaying a game is a fold over its actions. * m10 adds pause, restart and a frame clock that speeds up as the score grows. */ object Game: /** How many frames pass between game steps: 8 at first, one fewer every 3 points, never fewer than 2. */ def movesEvery(score: Int): Int = math.max(2, 8 - score / 3) // One animation frame: count it, and every `movesEvery(score)`-th frame run a game step. private def frame(state: GameState): GameState = if state.status != Status.Playing then state else val counted = state.copy(frame = state.frame + 1) if counted.frame % movesEvery(counted.score) == 0 then Spawn.tick(counted) else counted // Pause and resume only make sense in those two statuses; a finished game stays finished. private def togglePause(state: GameState): GameState = state.status match case Status.Playing => state.copy(status = Status.Paused) case Status.Paused => state.copy(status = Status.Playing) case Status.GameOver | Status.Won => state // Every case is listed (no `case _`): adding an Action later makes the compiler point here. def update(state: GameState, action: Action): GameState = action match case Action.Tick => Spawn.tick(state) case Action.Turn(dir) => Rules.turn(state, dir) case Action.TogglePause => togglePause(state) case Action.Restart => Spawn.newGame(state.seed) case Action.Frame => frame(state) def replay(state: GameState, actions: List[Action]): GameState = actions.foldLeft(state)(update) // scanLeft is foldLeft that keeps every intermediate result. def history(state: GameState, actions: List[Action]): List[GameState] = actions.scanLeft(state)(update)