Engine PR 8: pawns — pushes, captures, en passant, promotion #67
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
spec
wayfinder:grilling
wayfinder:map
wayfinder:prototype
wayfinder:research
wayfinder:task
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
amaurycoudr/chess-project#67
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem Statement
The engine can generate legal moves for kings, knights, and sliders, but pawns are still missing. Without pawns,
Game.legalMoves()cannot reproduce any realistic game, perft cannot be validated against standard positions, and the playable chess subset stays degenerate. Pawns are also the most rule-dense piece — single push, double push, diagonal capture, en passant, and promotion are all interlocked — so they cannot be added as just "one more attack mask."This is step 8 of ADR 0004's engine slice plan. Step 7 (sliders) is already merged; pawns do not depend on sliders, so this slice is independent.
Solution
Add pawn move generation to the existing piece-agnostic legality skeleton, so that
Game.legalMoves(),Game.apply(), andGame.status()handle pawns correctly — including the three special mechanics (double push setting an en passant target, en passant capture, and promotion). After this slice, a king + pawn (+ knight) game plays to completion with correct legality, and perft over pawn positions matches Stockfish.User Stories
Game.legalMoves()to include pawn single pushes, so that pawns can advance.promotionfield, so that theMoveshape stays minimal (ADR 0005).Game.status()to reportcheckmatewhen a pawn delivers mate, so that pawn checks are recognised.Game.apply()on a promotion move to place the chosen piece on the destination, so that the board reflects the promotion.Implementation Decisions
Modules to build or modify (interfaces only; no file paths or code):
pawnAttacks(square, color)— new, in the attacks module. Pure function returning aSquareSetof the (up to two) diagonal squares a pawn of the given color attacks. Color-dependent, unlikekingAttacks/knightAttacks. Models attacks only — non-capturing pushes are not attacks and are not produced here. Consumed by both move generation (capture targets) andisAttacked(king safety).pseudoLegalPawnMoves(position)— new, in the movegen module. ReturnsMove[]. Generates single push, double push (starting-rank-only, both squares clear), diagonal captures (enemy-occupied target), en passant capture (target square equalsposition.ep), and promotion expansion (a move landing on the last rank fans into fourMoves, one pern/b/r/q). Mirrors the existingpseudoLegalKnightMovesshape.applyMove— modified, in the rules module. Three interlocked changes: (a) on a double push, set the next position'septo the skipped-over square instead of the current hard-codedundefined; (b) on a move carryingpromotion, the mover lands on the destination as the promotion piece's set rather than the pawn set; (c) on an en passant capture, remove the enemy pawn from the square behind the destination rather than from the destination.isAttacked— modified, in the rules module. Add a pawn-attack scan so king safety and check detection account for pawns.legalMoves— modified, in the rules module. AddpseudoLegalPawnMovesto the pseudo-legal candidate list.No type changes:
Move.promotion, themoveOfhelper, andGame.apply's promotion-aware legality comparison already exist.Statusis unchanged.Architectural notes:
applyMove's special-case handling.applyMove(pawn mover + diagonal to emptyepsquare; pawn mover + last-rank destination) rather than tagged on theMove, consistent with ADR 0005's slimMove.Testing Decisions
A good test verifies external behavior — the moves offered, the resulting position, the reported status — never internal representation. Tests are vertical TDD slices: one test, one implementation step, repeat.
Modules under test (per the agreed default — all of them):
pawnAttacks: geometry unit tests — diagonal targets for white and black, edge-file truncation.pseudoLegalPawnMoves(vialegalMoves): single push, blocked push, double push, blocked/off-rank double push, diagonal capture, blocked diagonal, en passant availability, promotion expansion into four moves, capture-promotion.applyMove: en passant target set on double push and cleared otherwise; en passant capture removes the correct pawn; promotion places the chosen piece; halfmove clock resets on pawn moves.statusOf: pawn-delivered checkmate; king cannot step onto a pawn-attacked square; pinned pawn excluded.go perft N, chosen to exercise double pushes, en passant, and promotion.Prior art:
rules-knights.test.ts(geometry + legality +applyMove+statusOfstructure) andperft.test.ts(theit.eachdepth/expected table, FEN-annotated fixtures viamakePosition). New pawn tests follow these patterns directly.makePositionalready acceptswhitePawns/blackPawns/ep.Out of Scope
SquareSetstays Uint8Array-backed.applyMove/perft; the O(branching^depth) perft helper is unchanged.Further Notes
applyMoveremoves the correct pawn, and should be covered by at least one test or perft position.Promoted to branch
feat/engine-pr-8-1-pawns-pushes-captures-attack-mask-legality-wiring-68(PR #72), not yet merged to main. A dependent slice shouldgit fetch origin feat/engine-pr-8-1-pawns-pushes-captures-attack-mask-legality-wiring-68and branch off it.mentioned in issue #68
mentioned in issue #69
mentioned in issue #70
mentioned in issue #71