diff --git a/packages/chess/src/rules/sliding.ts b/packages/chess/src/rules/sliding.ts index b94b4e5..d5f4d6d 100644 --- a/packages/chess/src/rules/sliding.ts +++ b/packages/chess/src/rules/sliding.ts @@ -9,6 +9,12 @@ * 2. Ally-occupied → stop immediately (square NOT included) * 3. Enemy-occupied → legal capture, then stop * + * RangeBonus fact (seeded by the range-bonus modifier descriptor) extends + * or limits how many squares a piece may travel per ray. The effective + * maxSteps is clamped to [0, 7] — the natural board-geometry ceiling on + * an 8×8 board. For standard chess (full range), RangeBonus=0 is the + * default and behaviour is identical to the original implementation. + * * Intentionally NOT handled here (see later phases): * - check filtering → P2.18 * - turn order → P2.13 @@ -28,9 +34,22 @@ import { } from "./board-queries.js"; import type { PieceColor, Square } from "../schema.js"; +const BASE_RANGE = 7; + +/** + * Read the RangeBonus fact for a piece and compute the effective maxSteps + * per ray. The bonus is clamped so maxSteps stays within [0, BASE_RANGE]. + * Absent fact → bonus of 0 → maxSteps = BASE_RANGE (standard full range). + */ +function getRangeMaxSteps(session: Session, pieceId: EntityId): number { + const value = session.get(pieceId, "RangeBonus"); + const rangeBonus = value !== undefined ? (value as number) : 0; + return Math.min(BASE_RANGE, Math.max(0, BASE_RANGE + rangeBonus)); +} + /** * Walk each ray in `rays` from the piece outward, emitting legal moves - * until a blocker is hit. + * until a blocker is hit or maxSteps squares have been traversed. */ function getSlidingMoves( session: Session, @@ -38,10 +57,14 @@ function getSlidingMoves( from: Square, color: PieceColor, rays: Square[][], + maxSteps: number, ): LegalMove[] { const moves: LegalMove[] = []; for (const ray of rays) { + let steps = 0; for (const to of ray) { + if (steps >= maxSteps) break; + steps++; if (isAllyAt(session, to, color)) { // Own piece blocks — do not include this square, stop the ray. break; @@ -66,7 +89,8 @@ export function getLegalBishopMoves( const from = getPiecePosition(session, pieceId); const color = getPieceColor(session, pieceId); if (from === null || color === null) return []; - return getSlidingMoves(session, pieceId, from, color, bishopCandidates(from)); + const maxSteps = getRangeMaxSteps(session, pieceId); + return getSlidingMoves(session, pieceId, from, color, bishopCandidates(from), maxSteps); } /** Get all legal rook moves for the given rook entity. */ @@ -77,7 +101,8 @@ export function getLegalRookMoves( const from = getPiecePosition(session, pieceId); const color = getPieceColor(session, pieceId); if (from === null || color === null) return []; - return getSlidingMoves(session, pieceId, from, color, rookCandidates(from)); + const maxSteps = getRangeMaxSteps(session, pieceId); + return getSlidingMoves(session, pieceId, from, color, rookCandidates(from), maxSteps); } /** Get all legal queen moves for the given queen entity. */ @@ -88,5 +113,6 @@ export function getLegalQueenMoves( const from = getPiecePosition(session, pieceId); const color = getPieceColor(session, pieceId); if (from === null || color === null) return []; - return getSlidingMoves(session, pieceId, from, color, queenCandidates(from)); + const maxSteps = getRangeMaxSteps(session, pieceId); + return getSlidingMoves(session, pieceId, from, color, queenCandidates(from), maxSteps); }