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:
Lucas Winther
2026-10-29 00:49:42 +01:00
co-authored by Claude Opus 5.5
parent 183821737e
commit 9cbefeb2be
27 changed files with 851 additions and 6 deletions
+34
View File
@@ -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) }