35 lines
1.4 KiB
FSharp
35 lines
1.4 KiB
FSharp
/// 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) }
|