Compare commits

..

11 Commits

Author SHA1 Message Date
LQ63 c8ea043ccc docs(50-move-rule): 50 move docs
Build & Test (NowChessSystems) TeamCity build failed
Removed 50 move docs from this repo
2026-04-01 10:20:44 +02:00
LQ63 466b05d3be docs(50-move-rule): 50 move docs
Removed 50 move docs from this repo
2026-04-01 10:20:44 +02:00
LQ63 e1bc4d1d25 docs(50-move-rule): 50 move docs
Removed 50 move docs from this repo
2026-04-01 10:20:44 +02:00
LQ63 a5d7743c77 fix: replace .get with pattern match in FenExporterTest halfMoveClock round-trip 2026-04-01 10:20:44 +02:00
LQ63 9ceb7cf746 feat: NCS-11 implement 50-move rule draw claim and observer events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:20:44 +02:00
LQ63 b2bfcf3953 feat: NCS-11 propagate half-move clock flags through GameController
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:20:44 +02:00
LQ63 396940da43 feat: NCS-11 derive PGN termination marker from Result header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:20:44 +02:00
LQ63 1953b69173 feat: NCS-11 add halfMoveClock to GameHistory with addMove reset flags
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:20:44 +02:00
LQ63 67ed00657b docs: add 50-move rule implementation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:20:44 +02:00
LQ63 bc5fc83c2e docs: extend 50-move rule spec with FEN and PGN integration 2026-04-01 10:20:44 +02:00
LQ63 ec1adef682 docs: add 50-move rule design spec for NCS-11 2026-04-01 10:20:44 +02:00
@@ -90,17 +90,19 @@ 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 =>
notifyObservers(InvalidMoveEvent( val event = InvalidMoveEvent(
currentBoard, currentHistory, currentTurn, currentBoard,
currentHistory,
currentTurn,
s"Invalid move format '$moveInput'. Use coordinate notation, e.g. e2e4." s"Invalid move format '$moveInput'. Use coordinate notation, e.g. e2e4."
)) )
case Some((from, to)) => notifyObservers(event)
handleParsedMove(from, to, moveInput)
}
private def handleParsedMove(from: Square, to: Square, moveInput: String): Unit = case Some((from, to)) =>
// Create a move command with current state snapshot
val cmd = MoveCommand( val cmd = MoveCommand(
from = from, from = from,
to = to, to = to,
@@ -108,11 +110,14 @@ class GameEngine(
previousHistory = Some(currentHistory), previousHistory = Some(currentHistory),
previousTurn = Some(currentTurn) previousTurn = Some(currentTurn)
) )
// Execute the move through GameController
GameController.processMove(currentBoard, currentHistory, currentTurn, moveInput) match GameController.processMove(currentBoard, currentHistory, currentTurn, moveInput) match
case MoveResult.InvalidFormat(_) | MoveResult.NoPiece | MoveResult.WrongColor | MoveResult.IllegalMove | MoveResult.Quit => case MoveResult.InvalidFormat(_) | MoveResult.NoPiece | MoveResult.WrongColor | MoveResult.IllegalMove | MoveResult.Quit =>
handleFailedMove(moveInput) handleFailedMove(moveInput)
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) => 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))) val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
invoker.execute(updatedCmd) invoker.execute(updatedCmd)
updateGameState(newBoard, newHistory, newTurn) updateGameState(newBoard, newHistory, newTurn)
@@ -121,6 +126,7 @@ class GameEngine(
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn)) notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
case MoveResult.MovedInCheck(newBoard, newHistory, captured, newTurn) => 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))) val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
invoker.execute(updatedCmd) invoker.execute(updatedCmd)
updateGameState(newBoard, newHistory, newTurn) updateGameState(newBoard, newHistory, newTurn)
@@ -130,6 +136,7 @@ class GameEngine(
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn)) notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
case MoveResult.Checkmate(winner) => 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))) val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(Board.initial, GameHistory.empty, Color.White, None)))
invoker.execute(updatedCmd) invoker.execute(updatedCmd)
currentBoard = Board.initial currentBoard = Board.initial
@@ -138,6 +145,7 @@ class GameEngine(
notifyObservers(CheckmateEvent(currentBoard, currentHistory, currentTurn, winner)) notifyObservers(CheckmateEvent(currentBoard, currentHistory, currentTurn, winner))
case MoveResult.Stalemate => 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))) val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(Board.initial, GameHistory.empty, Color.White, None)))
invoker.execute(updatedCmd) invoker.execute(updatedCmd)
currentBoard = Board.initial currentBoard = Board.initial
@@ -148,6 +156,7 @@ class GameEngine(
case MoveResult.PromotionRequired(promFrom, promTo, boardBefore, histBefore, _, promotingTurn) => case MoveResult.PromotionRequired(promFrom, promTo, boardBefore, histBefore, _, promotingTurn) =>
pendingPromotion = Some(PendingPromotion(promFrom, promTo, boardBefore, histBefore, promotingTurn)) pendingPromotion = Some(PendingPromotion(promFrom, promTo, boardBefore, histBefore, promotingTurn))
notifyObservers(PromotionRequiredEvent(currentBoard, currentHistory, currentTurn, promFrom, promTo)) notifyObservers(PromotionRequiredEvent(currentBoard, currentHistory, currentTurn, promFrom, promTo))
}
/** Undo the last move. */ /** Undo the last move. */
def undo(): Unit = synchronized { def undo(): Unit = synchronized {