diff --git a/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala b/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala index 120b9e9..94c95ee 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala @@ -1,6 +1,7 @@ package de.nowchess.chess.controller import de.nowchess.api.board.{Board, Color, File, Piece, Rank, Square} +import de.nowchess.api.move.PromotionPiece import de.nowchess.chess.logic.* // --------------------------------------------------------------------------- @@ -14,6 +15,14 @@ object MoveResult: case object NoPiece extends MoveResult case object WrongColor 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 MovedInCheck(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult case class Checkmate(winner: Color) extends MoveResult @@ -45,6 +54,9 @@ object GameController: case Some(_) => if !MoveValidator.isLegal(board, history, from, to) then MoveResult.IllegalMove + else if MoveValidator.isPromotionMove(board, from, to) then + val captured = board.pieceAt(to) + MoveResult.PromotionRequired(from, to, board, history, captured, turn) else val castleOpt = if MoveValidator.isCastle(board, from, to) then Some(MoveValidator.castleSide(from, to)) diff --git a/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala b/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala index f5493b0..a686b7c 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala @@ -3,6 +3,7 @@ package de.nowchess.chess.controller import de.nowchess.api.board.* import de.nowchess.api.game.CastlingRights import de.nowchess.chess.logic.{CastleSide, GameHistory} +import de.nowchess.chess.notation.FenParser import org.scalatest.funsuite.AnyFunSuite 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 captured shouldBe Some(Piece.WhitePawn) 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")