feat(fsharp): m01-m06 util, model, rules, random, spawn, game, view with Expecto tests
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
183821737e
commit
9cbefeb2be
@@ -0,0 +1,34 @@
|
||||
/// 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) }
|
||||
Reference in New Issue
Block a user