feat(fsharp): m11 pong capstone (model, rules, view, app) with tests

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-29 01:00:29 +01:00
co-authored by Claude Opus 5.5
parent 038a375027
commit 1d0b8c3a5c
12 changed files with 524 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
/// m11 pong model: the same ideas as the snake model, for a different game.
module Snake.Pong.Model
open Snake.Model
/// The ball's direction of travel, one cell per frame on each axis.
type Velocity = { Dx: int; Dy: int }
/// Which of the player's two keys are being held down right now.
type Held = { Up: bool; Down: bool }
type Score = { Player: int; Cpu: int }
// RequireQualifiedAccess: write `PongStatus.Playing`, `Paddle.Up`, `PongAction.Frame`, so these cases never get
// mixed up with the snake's `Playing`, `Up` and `Frame`.
[<RequireQualifiedAccess>]
type PongStatus =
| Playing
| Over
/// The two keys that move the player's paddle.
[<RequireQualifiedAccess>]
type Paddle =
| Up
| Down
/// A whole match. Positions are in cells: `Ball` is a cell of the 80x50 court; `Player` and `Cpu` are the y of the
/// top cell of each paddle. `Seed` is the unsigned 32-bit random seed used to pick the direction of each serve.
type PongState =
{ Ball: Point
Vel: Velocity
Player: int
Cpu: int
Held: Held
Score: Score
Status: PongStatus
Seed: uint32
Frame: int }
/// Everything that can happen in a match, as data.
[<RequireQualifiedAccess>]
type PongAction =
| Press of Paddle
| Release of Paddle
| Frame
| Restart
// The court, in cells.
let W = 80
let H = 50
/// Pixels per cell.
let PCELL = 8
/// Paddle height in cells.
let PADDLE = 8
/// Column of the player's paddle (left) and of the cpu's paddle (right).
let PLAYER_X = 2
let CPU_X = 77
/// Points needed to win the match.
let WIN = 5
/// A fresh match: ball at (40, 25) moving `{ Dx = 1; Dy = 1 }`, both paddles at y 21, no keys held,
/// score 0 to 0, status Playing, the given seed, frame 0.
/// (Hint: PongState also has `Player` and `Cpu` fields, so write the score as `{ Score.Player = 0; Cpu = 0 }`.)
let initialPong (seed: uint32) : PongState = failwith "TODO m11"