Nit: Pinning and the discovered check are not really rules, but rather names of tactics.
Rule 3.9.2: No piece can be moved that will either expose the king of the same colour to check or leave that king in check.
At the "Is this move legal?" level, they don't need unique rules of its own if the lower-level rules are specified correctly.
No piece can be moved that will leave the king of the same color in check.
Also, pinning can happen with pieces that don’t include a king, which means you can just move out of the pin and expose whatever other piece.
It’s just a chess tactic, not a rule. It’s like saying a chess skewer is a rule too.
"Look, if this guys TLA+ logic struggles to model a 1,500-year-old game without crying over a French pawn-capture rule, you can't expect me to integrate Stripe billing without a few state invariant violations."
https://neuroning.com/boardgames-exercise/notebooks/walkthro...
The implementation makes it really easy to add new piece types or rules. For example, here's the full logic for rooks (sans castling):
(defn expand-pmove-for-rook [pmove]
(->> pmove
(expand-pmove-dirs [↑ ↓ ← →])
(pmoves-discard #(or (pmove-on-same-player-piece? %)
(pmove-changed-direction? %)))
(map pmoves-finish-capturing-opponent-piece)
(pmoves-finish-and-continue))))The post talks about "transition invariants" that should be somehow different from "state invariants" yet it describe them as:
> These are predicates over a <<state, next-state>> pair ...
i.e. it still is about state, but I find it much more useful to focus on behavior so instead of thinking about how state transition you focus on what the program is allowed to perform, regardless of the underlying data structure.
What I mean is that I'd like the code to tell me why a certain piece can't do such move instead of why it cannot transition it's position to another position and basically dumping its state in my head and there I have to execute the program myself.