diff --git a/docs/superpowers/specs/2026-03-31-currying-public-api-design.md b/docs/superpowers/specs/2026-03-31-currying-public-api-design.md new file mode 100644 index 0000000..2307923 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-currying-public-api-design.md @@ -0,0 +1,82 @@ +# Currying Public API — Design Spec +**Branch:** feat/NCS-11 +**Date:** 2026-03-31 + +--- + +## Overview + +Refactor the public methods of `MoveValidator` and `GameRules` to use multiple parameter groups (currying), separating `(board[, history])` as the context group from the computation parameters. This is a pure style refactoring — no behaviour changes, no new tests required. + +--- + +## Motivation + +Currying clarifies intent: `(board, history)` is "the world being operated on"; the remaining parameters are "what varies." It also enables partial application at call sites where board/history are fixed across a loop (e.g. `isInCheck`, `legalMoves`). + +--- + +## Section 1: Methods Being Curried + +### `MoveValidator` (public API only) + +| Before | After | +|--------|-------| +| `legalTargets(board, from)` | `legalTargets(board)(from)` | +| `legalTargets(board, history, from)` | `legalTargets(board, history)(from)` | +| `isLegal(board, from, to)` | `isLegal(board)(from, to)` | +| `isLegal(board, history, from, to)` | `isLegal(board, history)(from, to)` | +| `isCastle(board, from, to)` | `isCastle(board)(from, to)` | +| `castlingTargets(board, history, color)` | `castlingTargets(board, history)(color)` | +| `castleSide(from, to)` | unchanged — no board parameter | + +Private helpers (`isOwnPiece`, `isEnemyPiece`, `slide`, `pawnTargets`, `knightTargets`, `kingTargets`) are **not** changed. + +### `GameRules` (all public methods) + +| Before | After | +|--------|-------| +| `isInCheck(board, color)` | `isInCheck(board)(color)` | +| `legalMoves(board, history, color)` | `legalMoves(board, history)(color)` | +| `gameStatus(board, history, color)` | `gameStatus(board, history)(color)` | + +--- + +## Section 2: Call Sites Updated + +| File | Methods affected | +|------|-----------------| +| `modules/core/src/main/scala/de/nowchess/chess/logic/MoveValidator.scala` | Internal calls to `legalTargets`, `castlingTargets`, `isCastle` | +| `modules/core/src/main/scala/de/nowchess/chess/logic/GameRules.scala` | `MoveValidator.legalTargets`, `MoveValidator.isCastle`, `isInCheck` | +| `modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala` | `MoveValidator.isLegal`, `MoveValidator.isCastle`, `GameRules.gameStatus` | +| `modules/core/src/test/scala/de/nowchess/chess/logic/MoveValidatorTest.scala` | `legalTargets`, `isLegal`, `castlingTargets` | +| `modules/core/src/test/scala/de/nowchess/chess/logic/GameRulesTest.scala` | `isInCheck`, `legalMoves`, `gameStatus` | + +`EnPassantCalculator`, `CastlingRightsCalculator`, and their tests are **not** touched. + +--- + +## Section 3: Concrete Style Gain + +In `GameRules.isInCheck` and `legalMoves`, `board` is passed to `legalTargets` on every loop iteration today. After currying, it is factored out as a single partial application: + +```scala +// Before +board.pieces.exists { case (sq, piece) => + piece.color != color && + MoveValidator.legalTargets(board, sq).contains(kingSq) +} + +// After +val targets = MoveValidator.legalTargets(board) +board.pieces.exists { case (sq, piece) => + piece.color != color && + targets(sq).contains(kingSq) +} +``` + +--- + +## Section 4: Testing + +Pure refactoring — no new tests. All existing tests must pass after call sites are updated. The test suite is the regression guard.