From 9184c8f1b15a6ac77fe91c1b7f607f4e4aebf8af Mon Sep 17 00:00:00 2001 From: Janis Date: Tue, 31 Mar 2026 20:01:35 +0200 Subject: [PATCH] feat: add PGN export support for pawn promotion notation (=Q/=R/=B/=N) Extend PgnExporter.moveToAlgebraic() to append the promotion piece suffix (=Q, =R, =B, =N) for moves where a pawn is promoted. Existing castling and normal moves remain unaffected. Co-Authored-By: Claude Haiku 4.5 --- .../nowchess/chess/notation/PgnExporter.scala | 10 ++++- .../chess/notation/PgnExporterTest.scala | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala b/modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala index 8eb4d1c..a7f6449 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala @@ -1,6 +1,7 @@ package de.nowchess.chess.notation import de.nowchess.api.board.* +import de.nowchess.api.move.PromotionPiece import de.nowchess.chess.logic.{CastleSide, GameHistory, HistoryMove} object PgnExporter: @@ -32,4 +33,11 @@ object PgnExporter: move.castleSide match case Some(CastleSide.Kingside) => "O-O" case Some(CastleSide.Queenside) => "O-O-O" - case None => s"${move.from}${move.to}" + case None => + val base = s"${move.from}${move.to}" + move.promotionPiece match + case Some(PromotionPiece.Queen) => s"$base=Q" + case Some(PromotionPiece.Rook) => s"$base=R" + case Some(PromotionPiece.Bishop) => s"$base=B" + case Some(PromotionPiece.Knight) => s"$base=N" + case None => base diff --git a/modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala b/modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala index 133252b..6c39aa6 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala @@ -1,6 +1,7 @@ package de.nowchess.chess.notation import de.nowchess.api.board.* +import de.nowchess.api.move.PromotionPiece import de.nowchess.chess.logic.{GameHistory, HistoryMove, CastleSide} import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers @@ -63,3 +64,39 @@ class PgnExporterTest extends AnyFunSuite with Matchers: pgn.contains("O-O-O") shouldBe true } + + test("exportGame encodes promotion to Queen as =Q suffix") { + val history = GameHistory() + .addMove(HistoryMove(Square(File.E, Rank.R7), Square(File.E, Rank.R8), None, Some(PromotionPiece.Queen))) + val pgn = PgnExporter.exportGame(Map.empty, history) + pgn should include ("e7e8=Q") + } + + test("exportGame encodes promotion to Rook as =R suffix") { + val history = GameHistory() + .addMove(HistoryMove(Square(File.E, Rank.R7), Square(File.E, Rank.R8), None, Some(PromotionPiece.Rook))) + val pgn = PgnExporter.exportGame(Map.empty, history) + pgn should include ("e7e8=R") + } + + test("exportGame encodes promotion to Bishop as =B suffix") { + val history = GameHistory() + .addMove(HistoryMove(Square(File.E, Rank.R7), Square(File.E, Rank.R8), None, Some(PromotionPiece.Bishop))) + val pgn = PgnExporter.exportGame(Map.empty, history) + pgn should include ("e7e8=B") + } + + test("exportGame encodes promotion to Knight as =N suffix") { + val history = GameHistory() + .addMove(HistoryMove(Square(File.E, Rank.R7), Square(File.E, Rank.R8), None, Some(PromotionPiece.Knight))) + val pgn = PgnExporter.exportGame(Map.empty, history) + pgn should include ("e7e8=N") + } + + test("exportGame does not add suffix for normal moves") { + val history = GameHistory() + .addMove(HistoryMove(Square(File.E, Rank.R2), Square(File.E, Rank.R4), None, None)) + val pgn = PgnExporter.exportGame(Map.empty, history) + pgn should include ("e2e4") + pgn should not include ("=") + }