/// 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. module Snake.Game open Snake.Model /// How many frames pass between game steps: 8 at first, one fewer every 3 points, never fewer than 2. let movesEvery (score: int) : int = max 2 (8 - score / 3) // One animation frame: count it, and every `movesEvery score`-th frame run a game step. let private frame (state: GameState) : GameState = if state.Status <> Playing then state else let counted = { state with 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. let private togglePause (state: GameState) : GameState = match state.Status with | Playing -> { state with Status = Paused } | Paused -> { state with Status = Playing } | GameOver | Won -> state // Every case is listed (no `| _ ->`): adding an Action later makes the compiler point here. let update (state: GameState) (action: Action) : GameState = match action with | Tick -> Spawn.tick state | Turn dir -> Rules.turn state dir | TogglePause -> togglePause state | Restart -> Spawn.newGame state.Seed | Frame -> frame state let replay (state: GameState) (actions: Action list) : GameState = List.fold update state actions // List.scan is List.fold that keeps every intermediate result (the start state included). let history (state: GameState) (actions: Action list) : GameState list = List.scan update state actions