/// m03 rules: the pure game rules. Each function takes a state and returns a new one. module Snake.Rules open Snake.Model let move (p: Point) (dir: Direction) : Point = let d = delta dir { X = p.X + d.X; Y = p.Y + d.Y } let inBounds (state: GameState) (p: Point) : bool = 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. let turn (state: GameState) (dir: Direction) : GameState = if state.Status = Playing && dir <> opposite state.Dir then { state with NextDir = dir } else state let advance (state: GameState) : GameState = if state.Status <> Playing then state else let d = state.NextDir let head = move (List.head state.Snake) d let eating = state.Apple = Some head // When not eating, the tail leaves its cell this step, so the head may move into it. let body = if eating then state.Snake else Util.dropLast state.Snake if not (inBounds state head) || Util.contains body head (=) then { state with Dir = d; Status = GameOver } else { state with Snake = head :: body Dir = d Apple = if eating then None else state.Apple Score = state.Score + (if eating then 1 else 0) }