Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 412ed986a9 | |||
| 8bbeead702 | |||
| e5e20c566e |
@@ -20,5 +20,4 @@ When invoked BEFORE scala-implementer (no implementation exists yet):
|
|||||||
|
|
||||||
When invoked AFTER scala-implementer (implementation exists):
|
When invoked AFTER scala-implementer (implementation exists):
|
||||||
Run python3 jacoco-reporter/jacoco_coverage_gaps.py modules/{service-name}/build/reports/jacoco/test/jacocoTestReport.xml --output agent
|
Run python3 jacoco-reporter/jacoco_coverage_gaps.py modules/{service-name}/build/reports/jacoco/test/jacocoTestReport.xml --output agent
|
||||||
Use the jacoco-coverage-gaps skill — close coverage gaps revealed by the report.
|
|
||||||
To regenerate the report run the tests first.
|
To regenerate the report run the tests first.
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
## (2026-03-28)
|
## (2026-03-28)
|
||||||
## (2026-03-29)
|
## (2026-03-29)
|
||||||
## (2026-03-31)
|
## (2026-03-31)
|
||||||
|
## (2026-04-01)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=0
|
MINOR=0
|
||||||
PATCH=5
|
PATCH=6
|
||||||
|
|||||||
@@ -79,3 +79,23 @@
|
|||||||
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||||
|
## (2026-04-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
|
||||||
|
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
|
||||||
|
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
|
||||||
|
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
|
||||||
|
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||||
|
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||||
|
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
|
||||||
|
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
|
||||||
|
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||||
|
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||||
|
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||||
|
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ object GameController:
|
|||||||
case None => MoveResult.NoPiece
|
case None => MoveResult.NoPiece
|
||||||
case Some(piece) if piece.color != turn => MoveResult.WrongColor
|
case Some(piece) if piece.color != turn => MoveResult.WrongColor
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
if !MoveValidator.isLegal(board, history, from, to) then MoveResult.IllegalMove
|
if !GameRules.legalMoves(board, history, turn).contains(from -> to) then MoveResult.IllegalMove
|
||||||
else if MoveValidator.isPromotionMove(board, from, to) then
|
else if MoveValidator.isPromotionMove(board, from, to) then
|
||||||
MoveResult.PromotionRequired(from, to, board, history, board.pieceAt(to), turn)
|
MoveResult.PromotionRequired(from, to, board, history, board.pieceAt(to), turn)
|
||||||
else applyNormalMove(board, history, turn, from, to)
|
else applyNormalMove(board, history, turn, from, to)
|
||||||
|
|||||||
@@ -90,74 +90,65 @@ class GameEngine(
|
|||||||
notifyObservers(event)
|
notifyObservers(event)
|
||||||
|
|
||||||
case moveInput =>
|
case moveInput =>
|
||||||
// Try to parse as a move
|
|
||||||
Parser.parseMove(moveInput) match
|
Parser.parseMove(moveInput) match
|
||||||
case None =>
|
case None =>
|
||||||
val event = InvalidMoveEvent(
|
notifyObservers(InvalidMoveEvent(
|
||||||
currentBoard,
|
currentBoard, currentHistory, currentTurn,
|
||||||
currentHistory,
|
|
||||||
currentTurn,
|
|
||||||
s"Invalid move format '$moveInput'. Use coordinate notation, e.g. e2e4."
|
s"Invalid move format '$moveInput'. Use coordinate notation, e.g. e2e4."
|
||||||
)
|
))
|
||||||
notifyObservers(event)
|
|
||||||
|
|
||||||
case Some((from, to)) =>
|
case Some((from, to)) =>
|
||||||
// Create a move command with current state snapshot
|
handleParsedMove(from, to, moveInput)
|
||||||
val cmd = MoveCommand(
|
|
||||||
from = from,
|
|
||||||
to = to,
|
|
||||||
previousBoard = Some(currentBoard),
|
|
||||||
previousHistory = Some(currentHistory),
|
|
||||||
previousTurn = Some(currentTurn)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Execute the move through GameController
|
|
||||||
GameController.processMove(currentBoard, currentHistory, currentTurn, moveInput) match
|
|
||||||
case MoveResult.InvalidFormat(_) | MoveResult.NoPiece | MoveResult.WrongColor | MoveResult.IllegalMove | MoveResult.Quit =>
|
|
||||||
handleFailedMove(moveInput)
|
|
||||||
|
|
||||||
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) =>
|
|
||||||
// Move succeeded - store result and execute through invoker
|
|
||||||
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
|
||||||
invoker.execute(updatedCmd)
|
|
||||||
updateGameState(newBoard, newHistory, newTurn)
|
|
||||||
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
|
||||||
if currentHistory.halfMoveClock >= 100 then
|
|
||||||
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
|
||||||
|
|
||||||
case MoveResult.MovedInCheck(newBoard, newHistory, captured, newTurn) =>
|
|
||||||
// Move succeeded with check
|
|
||||||
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
|
||||||
invoker.execute(updatedCmd)
|
|
||||||
updateGameState(newBoard, newHistory, newTurn)
|
|
||||||
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
|
||||||
notifyObservers(CheckDetectedEvent(currentBoard, currentHistory, currentTurn))
|
|
||||||
if currentHistory.halfMoveClock >= 100 then
|
|
||||||
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
|
||||||
|
|
||||||
case MoveResult.Checkmate(winner) =>
|
|
||||||
// Move resulted in checkmate
|
|
||||||
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(Board.initial, GameHistory.empty, Color.White, None)))
|
|
||||||
invoker.execute(updatedCmd)
|
|
||||||
currentBoard = Board.initial
|
|
||||||
currentHistory = GameHistory.empty
|
|
||||||
currentTurn = Color.White
|
|
||||||
notifyObservers(CheckmateEvent(currentBoard, currentHistory, currentTurn, winner))
|
|
||||||
|
|
||||||
case MoveResult.Stalemate =>
|
|
||||||
// Move resulted in stalemate
|
|
||||||
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(Board.initial, GameHistory.empty, Color.White, None)))
|
|
||||||
invoker.execute(updatedCmd)
|
|
||||||
currentBoard = Board.initial
|
|
||||||
currentHistory = GameHistory.empty
|
|
||||||
currentTurn = Color.White
|
|
||||||
notifyObservers(StalemateEvent(currentBoard, currentHistory, currentTurn))
|
|
||||||
|
|
||||||
case MoveResult.PromotionRequired(promFrom, promTo, boardBefore, histBefore, _, promotingTurn) =>
|
|
||||||
pendingPromotion = Some(PendingPromotion(promFrom, promTo, boardBefore, histBefore, promotingTurn))
|
|
||||||
notifyObservers(PromotionRequiredEvent(currentBoard, currentHistory, currentTurn, promFrom, promTo))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def handleParsedMove(from: Square, to: Square, moveInput: String): Unit =
|
||||||
|
val cmd = MoveCommand(
|
||||||
|
from = from,
|
||||||
|
to = to,
|
||||||
|
previousBoard = Some(currentBoard),
|
||||||
|
previousHistory = Some(currentHistory),
|
||||||
|
previousTurn = Some(currentTurn)
|
||||||
|
)
|
||||||
|
GameController.processMove(currentBoard, currentHistory, currentTurn, moveInput) match
|
||||||
|
case MoveResult.InvalidFormat(_) | MoveResult.NoPiece | MoveResult.WrongColor | MoveResult.IllegalMove | MoveResult.Quit =>
|
||||||
|
handleFailedMove(moveInput)
|
||||||
|
|
||||||
|
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
updateGameState(newBoard, newHistory, newTurn)
|
||||||
|
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
||||||
|
if currentHistory.halfMoveClock >= 100 then
|
||||||
|
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
|
||||||
|
case MoveResult.MovedInCheck(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
updateGameState(newBoard, newHistory, newTurn)
|
||||||
|
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
||||||
|
notifyObservers(CheckDetectedEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
if currentHistory.halfMoveClock >= 100 then
|
||||||
|
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
|
||||||
|
case MoveResult.Checkmate(winner) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(Board.initial, GameHistory.empty, Color.White, None)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
currentBoard = Board.initial
|
||||||
|
currentHistory = GameHistory.empty
|
||||||
|
currentTurn = Color.White
|
||||||
|
notifyObservers(CheckmateEvent(currentBoard, currentHistory, currentTurn, winner))
|
||||||
|
|
||||||
|
case MoveResult.Stalemate =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(Board.initial, GameHistory.empty, Color.White, None)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
currentBoard = Board.initial
|
||||||
|
currentHistory = GameHistory.empty
|
||||||
|
currentTurn = Color.White
|
||||||
|
notifyObservers(StalemateEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
|
||||||
|
case MoveResult.PromotionRequired(promFrom, promTo, boardBefore, histBefore, _, promotingTurn) =>
|
||||||
|
pendingPromotion = Some(PendingPromotion(promFrom, promTo, boardBefore, histBefore, promotingTurn))
|
||||||
|
notifyObservers(PromotionRequiredEvent(currentBoard, currentHistory, currentTurn, promFrom, promTo))
|
||||||
|
|
||||||
/** Undo the last move. */
|
/** Undo the last move. */
|
||||||
def undo(): Unit = synchronized {
|
def undo(): Unit = synchronized {
|
||||||
performUndo()
|
performUndo()
|
||||||
|
|||||||
@@ -43,6 +43,30 @@ class GameControllerTest extends AnyFunSuite with Matchers:
|
|||||||
// White pawn at E2 cannot jump three squares to E5
|
// White pawn at E2 cannot jump three squares to E5
|
||||||
processMove(Board.initial, GameHistory.empty, Color.White, "e2e5") shouldBe MoveResult.IllegalMove
|
processMove(Board.initial, GameHistory.empty, Color.White, "e2e5") shouldBe MoveResult.IllegalMove
|
||||||
|
|
||||||
|
test("processMove: move that leaves own king in check returns IllegalMove"):
|
||||||
|
// White King E1 is in check from Black Rook E8. Moving the D2 pawn is
|
||||||
|
// geometrically legal but does not resolve the check — must be rejected.
|
||||||
|
val b = Board(Map(
|
||||||
|
sq(File.E, Rank.R1) -> Piece.WhiteKing,
|
||||||
|
sq(File.D, Rank.R2) -> Piece.WhitePawn,
|
||||||
|
sq(File.E, Rank.R8) -> Piece.BlackRook,
|
||||||
|
sq(File.A, Rank.R8) -> Piece.BlackKing
|
||||||
|
))
|
||||||
|
processMove(b, GameHistory.empty, Color.White, "d2d4") shouldBe MoveResult.IllegalMove
|
||||||
|
|
||||||
|
test("processMove: move that resolves check is allowed"):
|
||||||
|
// White King E1 is in check from Black Rook E8 along the E-file.
|
||||||
|
// White Rook A5 interposes at E5 — resolves the check, no new check on Black King A8.
|
||||||
|
val b = Board(Map(
|
||||||
|
sq(File.E, Rank.R1) -> Piece.WhiteKing,
|
||||||
|
sq(File.A, Rank.R5) -> Piece.WhiteRook,
|
||||||
|
sq(File.E, Rank.R8) -> Piece.BlackRook,
|
||||||
|
sq(File.A, Rank.R8) -> Piece.BlackKing
|
||||||
|
))
|
||||||
|
processMove(b, GameHistory.empty, Color.White, "a5e5") match
|
||||||
|
case _: MoveResult.Moved => succeed
|
||||||
|
case other => fail(s"Expected Moved, got $other")
|
||||||
|
|
||||||
test("processMove: legal pawn move returns Moved with updated board and flipped turn"):
|
test("processMove: legal pawn move returns Moved with updated board and flipped turn"):
|
||||||
processMove(Board.initial, GameHistory.empty, Color.White, "e2e4") match
|
processMove(Board.initial, GameHistory.empty, Color.White, "e2e4") match
|
||||||
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) =>
|
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=5
|
MINOR=6
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
## (2026-04-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||||
|
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
MAJOR=0
|
||||||
|
MINOR=1
|
||||||
|
PATCH=0
|
||||||
Reference in New Issue
Block a user