feat: add PromotionRequired to MoveResult and detect promotion in processMove

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-31 13:30:24 +02:00
parent f0e1ee272d
commit c6ffd827cb
2 changed files with 40 additions and 0 deletions
@@ -1,6 +1,7 @@
package de.nowchess.chess.controller package de.nowchess.chess.controller
import de.nowchess.api.board.{Board, Color, File, Piece, Rank, Square} import de.nowchess.api.board.{Board, Color, File, Piece, Rank, Square}
import de.nowchess.api.move.PromotionPiece
import de.nowchess.chess.logic.* import de.nowchess.chess.logic.*
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -14,6 +15,14 @@ object MoveResult:
case object NoPiece extends MoveResult case object NoPiece extends MoveResult
case object WrongColor extends MoveResult case object WrongColor extends MoveResult
case object IllegalMove extends MoveResult case object IllegalMove extends MoveResult
case class PromotionRequired(
from: Square,
to: Square,
boardBefore: Board,
historyBefore: GameHistory,
captured: Option[Piece],
turn: Color
) extends MoveResult
case class Moved(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult case class Moved(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult
case class MovedInCheck(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult case class MovedInCheck(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult
case class Checkmate(winner: Color) extends MoveResult case class Checkmate(winner: Color) extends MoveResult
@@ -45,6 +54,9 @@ object GameController:
case Some(_) => case Some(_) =>
if !MoveValidator.isLegal(board, history, from, to) then if !MoveValidator.isLegal(board, history, from, to) then
MoveResult.IllegalMove MoveResult.IllegalMove
else if MoveValidator.isPromotionMove(board, from, to) then
val captured = board.pieceAt(to)
MoveResult.PromotionRequired(from, to, board, history, captured, turn)
else else
val castleOpt = if MoveValidator.isCastle(board, from, to) val castleOpt = if MoveValidator.isCastle(board, from, to)
then Some(MoveValidator.castleSide(from, to)) then Some(MoveValidator.castleSide(from, to))
@@ -3,6 +3,7 @@ package de.nowchess.chess.controller
import de.nowchess.api.board.* import de.nowchess.api.board.*
import de.nowchess.api.game.CastlingRights import de.nowchess.api.game.CastlingRights
import de.nowchess.chess.logic.{CastleSide, GameHistory} import de.nowchess.chess.logic.{CastleSide, GameHistory}
import de.nowchess.chess.notation.FenParser
import org.scalatest.funsuite.AnyFunSuite import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
@@ -293,3 +294,30 @@ class GameControllerTest extends AnyFunSuite with Matchers:
newBoard.pieceAt(Square(File.E, Rank.R3)) shouldBe Some(Piece.BlackPawn) // capturing pawn placed newBoard.pieceAt(Square(File.E, Rank.R3)) shouldBe Some(Piece.BlackPawn) // capturing pawn placed
captured shouldBe Some(Piece.WhitePawn) captured shouldBe Some(Piece.WhitePawn)
case other => fail(s"Expected Moved but got $other") case other => fail(s"Expected Moved but got $other")
// ──── pawn promotion detection ───────────────────────────────────────────
test("processMove detects white pawn reaching R8 and returns PromotionRequired"):
val board = FenParser.parseBoard("8/4P3/4k3/8/8/8/8/8").get
val result = GameController.processMove(board, GameHistory.empty, Color.White, "e7e8")
result should matchPattern { case _: MoveResult.PromotionRequired => }
result match
case MoveResult.PromotionRequired(from, to, _, _, _, turn) =>
from should be (sq(File.E, Rank.R7))
to should be (sq(File.E, Rank.R8))
turn should be (Color.White)
case _ => fail("Expected PromotionRequired")
test("processMove detects black pawn reaching R1 and returns PromotionRequired"):
val board = FenParser.parseBoard("8/8/8/8/4K3/8/4p3/8").get
val result = GameController.processMove(board, GameHistory.empty, Color.Black, "e2e1")
result should matchPattern { case _: MoveResult.PromotionRequired => }
test("processMove detects pawn capturing to back rank as PromotionRequired with captured piece"):
val board = FenParser.parseBoard("3q4/4P3/8/8/8/8/8/8").get
val result = GameController.processMove(board, GameHistory.empty, Color.White, "e7d8")
result should matchPattern { case _: MoveResult.PromotionRequired => }
result match
case MoveResult.PromotionRequired(_, _, _, _, captured, _) =>
captured should be (Some(Piece(Color.Black, PieceType.Queen)))
case _ => fail("Expected PromotionRequired")