Repository: jhlywa/chess.js (2025-07-08T19:34:02Z)

Issue:
.fen({ forceEnpassantSquare = true)) not working
```typescript
import {Chess} from 'chess.js'

const chess = new Chess()


chess.move("h2h4");

console.log(chess.fen({forceEnpassantSquare: true}));
// returns:
// rnbqkbnr/pppppppp/8/8/7P/8/PPPPPPP1/RNBQKBNR b KQkq - 0 1
// expected
// rnbqkbnr/pppppppp/8/8/7P/8/PPPPPPP1/RNBQKBNR b KQkq h3 0 1
```


Discussion:
Thanks for the report @Lurchfresser.

Right now it appears `forceEnPassantSquare` will only display the EP square in the rare case where the capturing pawn is pinned. I'm looking into options to fix this, but I've got to trend lightly as it impacts other areas of the code ... e.g. Zobrist hashing and `isThreefoldRepetition`.
@jhlywa  Doesn't feel great, but I'm not sure there is anything better than having a separate variable which tracks the "pseudo" ep square purely for fen generation with the force flag?
I would agree to @neofight78 . A seperate flag would be a good option, as fen implementation vary on this topic. I think the standart is to include the "pseudo-legal" square, but I have also seen libraries, that do the opposite. But for zobrist hashing I agree, that the square should only be included, if there is a neighbour enemy pawn. F.e. the [polyglot Zobrist hashes](http://hgm.nubati.net/book_format.html), include the ep-sqaure like now, when forceEnPassantSquare is true, to get more transposition matches



Patch:
diff --git a/src/chess.ts b/src/chess.ts
index 8971fadf..f0486420 100644
--- a/src/chess.ts
+++ b/src/chess.ts
@@ -115,6 +115,7 @@ interface History {
   turn: Color
   castling: Record<Color, number>
   epSquare: number
+  fenEpSquare: number
   halfMoves: number
   moveNumber: number
 }
@@ -711,6 +712,7 @@ export class Chess {
   private _header: Record<string, string | null> = {}
   private _kings: Record<Color, number> = { w: EMPTY, b: EMPTY }
   private _epSquare = -1
+  private _fenEpSquare = -1
   private _halfMoves = 0
   private _moveNumber = 0
   private _history: History[] = []
@@ -732,6 +734,7 @@ export class Chess {
     this._turn = WHITE
     this._castling = { w: 0, b: 0 }
     this._epSquare = EMPTY
+    this._fenEpSquare = EMPTY
     this._halfMoves = 0
     this._moveNumber = 1
     this._history = []
@@ -805,6 +808,7 @@ export class Chess {
     }
 
     this._epSquare = tokens[3] === '-' ? EMPTY : Ox88[tokens[3] as Square]
+    this._fenEpSquare = this._epSquare
     this._halfMoves = parseInt(tokens[4], 10)
     this._moveNumber = parseInt(tokens[5], 10)
 
@@ -868,10 +872,10 @@ export class Chess {
      * only print the ep square if en passant is a valid move (pawn is present
      * and ep capture is not pinned)
      */
-    if (this._epSquare !== EMPTY) {
+    if (this._fenEpSquare !== EMPTY) {
       if (forceEnpassantSquare) {
-        epSquare = algebraic(this._epSquare)
-      } else {
+        epSquare = algebraic(this._fenEpSquare)
+      } else if (this._epSquare !== EMPTY) {
         const bigPawnSquare = this._epSquare + (this._turn === WHITE ? 16 : -16)
         const squares = [bigPawnSquare + 1, bigPawnSquare - 1]
 
@@ -1723,6 +1727,7 @@ export class Chess {
       turn: this._turn,
       castling: { b: this._castling.b, w: this._castling.w },
       epSquare: this._epSquare,
+      fenEpSquare: this._fenEpSquare,
       halfMoves: this._halfMoves,
       moveNumber: this._moveNumber,
     })
@@ -1835,6 +1840,8 @@ export class Chess {
         epSquare = move.to + 16
       }
 
+      this._fenEpSquare = epSquare
+
       if (
         (!((move.to - 1) & 0x88) &&
           this._board[move.to - 1]?.type === PAWN &&
@@ -1850,6 +1857,7 @@ export class Chess {
       }
     } else {
       this._epSquare = EMPTY
+      this._fenEpSquare = EMPTY
     }
 
     // reset the 50 move counter if a pawn is moved or a piece is captured
@@ -1895,6 +1903,7 @@ export class Chess {
     this._turn = old.turn
     this._castling = old.castling
     this._epSquare = old.epSquare
+    this._fenEpSquare = old.fenEpSquare
     this._halfMoves = old.halfMoves
     this._moveNumber = old.moveNumber
 


Test patch:
diff --git a/__tests__/fen.test.ts b/__tests__/fen.test.ts
index 92d007ac..7cdd3ad7 100644
--- a/__tests__/fen.test.ts
+++ b/__tests__/fen.test.ts
@@ -42,7 +42,7 @@ test('fen - ep square only if en passant is legal (illegal - pinned - #2)', () =
   )
 })
 
-test('fen - allow EP square to be included by option', () => {
+test('fen - allow EP square to be included by option (pinned)', () => {
   // black queen pins the ep pawn, making ep illegal (submitted by @ajax333221)
   const chess = new Chess(
     'rnb1kbn1/p1p1pp2/PpPp2qr/5Pp1/8/R1P4p/1PK1P1PP/1NBQ1BNR b - - 0 1',
@@ -52,3 +52,13 @@ test('fen - allow EP square to be included by option', () => {
     'rnb1kbn1/p1p2p2/PpPp2qr/4pPp1/8/R1P4p/1PK1P1PP/1NBQ1BNR w - e6 0 2',
   )
 })
+
+test('fen - allow EP square to be included by option (no capturing pawn)`', () => {
+  // GitHub Issue #544
+  const chess = new Chess()
+  chess.move('h4')
+
+  expect(chess.fen({ forceEnpassantSquare: true })).toEqual(
+    'rnbqkbnr/pppppppp/8/8/7P/8/PPPPPPP1/RNBQKBNR b KQkq h3 0 1',
+  )
+})