feat: refactor ChessBoardView to use AtomicReference for state management
Build & Test (NowChessSystems) TeamCity build failed

This commit is contained in:
2026-04-14 15:19:09 +02:00
parent ec2ab2f365
commit 50ecc0fd4d
154 changed files with 256 additions and 12042 deletions
@@ -375,19 +375,25 @@ object DefaultRules extends RuleSet:
val blackKingsideRook = Square(File.H, Rank.R8)
val blackQueensideRook = Square(File.A, Rank.R8)
var r = rights
if isKingMove then r = r.revokeColor(color)
else if isRookMove then
if move.from == whiteKingsideRook then r = r.revokeKingSide(Color.White)
if move.from == whiteQueensideRook then r = r.revokeQueenSide(Color.White)
if move.from == blackKingsideRook then r = r.revokeKingSide(Color.Black)
if move.from == blackQueensideRook then r = r.revokeQueenSide(Color.Black)
val afterKingMove = if isKingMove then rights.revokeColor(color) else rights
val afterRookMove =
if !isRookMove then afterKingMove
else
move.from match
case `whiteKingsideRook` => afterKingMove.revokeKingSide(Color.White)
case `whiteQueensideRook` => afterKingMove.revokeQueenSide(Color.White)
case `blackKingsideRook` => afterKingMove.revokeKingSide(Color.Black)
case `blackQueensideRook` => afterKingMove.revokeQueenSide(Color.Black)
case _ => afterKingMove
// Also revoke if a rook is captured
if move.to == whiteKingsideRook then r = r.revokeKingSide(Color.White)
if move.to == whiteQueensideRook then r = r.revokeQueenSide(Color.White)
if move.to == blackKingsideRook then r = r.revokeKingSide(Color.Black)
if move.to == blackQueensideRook then r = r.revokeQueenSide(Color.Black)
r
move.to match
case `whiteKingsideRook` => afterRookMove.revokeKingSide(Color.White)
case `whiteQueensideRook` => afterRookMove.revokeQueenSide(Color.White)
case `blackKingsideRook` => afterRookMove.revokeKingSide(Color.Black)
case `blackQueensideRook` => afterRookMove.revokeQueenSide(Color.Black)
case _ => afterRookMove
private def computeEnPassantSquare(board: Board, move: Move): Option[Square] =
val piece = board.pieceAt(move.from)
@@ -401,13 +407,14 @@ object DefaultRules extends RuleSet:
// ── Insufficient material ──────────────────────────────────────────
private def squareColor(sq: Square): Int = (sq.file.ordinal + sq.rank.ordinal) % 2
private def insufficientMaterial(board: Board): Boolean =
val pieces = board.pieces.values.toList.filter(_.pieceType != PieceType.King)
pieces match
case Nil => true
case List(p) if p.pieceType == PieceType.Bishop || p.pieceType == PieceType.Knight => true
case List(p1, p2)
if p1.pieceType == PieceType.Bishop && p2.pieceType == PieceType.Bishop
&& p1.color != p2.color =>
true
val nonKings = board.pieces.toList.filter { case (_, p) => p.pieceType != PieceType.King }
nonKings match
case Nil => true
case List((_, p)) if p.pieceType == PieceType.Bishop || p.pieceType == PieceType.Knight => true
case bishops if bishops.forall { case (_, p) => p.pieceType == PieceType.Bishop } =>
// All non-king pieces are bishops: draw only if they all share the same square color
bishops.map { case (sq, _) => squareColor(sq) }.distinct.sizeIs == 1
case _ => false
@@ -40,6 +40,11 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
DefaultRules.isInsufficientMaterial(context) shouldBe true
test("isInsufficientMaterial returns true for king and knight versus king"):
val context = contextFromFen("8/8/8/8/8/8/4k3/4KN2 w - - 0 1")
DefaultRules.isInsufficientMaterial(context) shouldBe true
test("isInsufficientMaterial returns false for king and rook versus king"):
val context = contextFromFen("8/8/8/8/8/8/4k3/3RK3 w - - 0 1")
@@ -206,11 +211,30 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
afterH1Capture.castlingRights.whiteKingSide shouldBe false
afterH1Capture.castlingRights.whiteQueenSide shouldBe false
test("isInsufficientMaterial returns true for opposite color bishops only"):
test("isInsufficientMaterial returns true for two same-square-color bishops (one each side)"):
// White bishop d1 (dark), black bishop g2 (dark) — same square color → draw
val context = contextFromFen("8/8/8/8/8/8/4k1b1/3BK3 w - - 0 1")
DefaultRules.isInsufficientMaterial(context) shouldBe true
test("isInsufficientMaterial returns false for two different-square-color bishops (one each side)"):
// White bishop d1 (dark), black bishop d2 (light) — different square colors → not a draw
val context = contextFromFen("8/8/8/4k3/8/8/3b4/3BK3 w - - 0 1")
DefaultRules.isInsufficientMaterial(context) shouldBe false
test("isInsufficientMaterial returns true for two same-color bishops vs lone king"):
// White bishops on c1 (light) and e3 (light), black king only → draw
val context = contextFromFen("4k3/8/8/8/8/4B3/8/2B1K3 w - - 0 1")
DefaultRules.isInsufficientMaterial(context) shouldBe true
test("isInsufficientMaterial returns false for bishop and knight versus king"):
// K+B+N vs K is sufficient material
val context = contextFromFen("4k3/8/8/8/8/8/8/3BKN2 w - - 0 1")
DefaultRules.isInsufficientMaterial(context) shouldBe false
test("candidateMoves for rook includes enemy capture move"):
val context = contextFromFen("4k3/8/8/8/8/8/4K3/R6r w - - 0 1")
@@ -32,7 +32,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers:
val fen = "8/8/8/3p4/4P3/8/8/8 w - - 0 1"
val context = FenParser.parseFen(fen).fold(_ => fail(), identity)
val moves = rules.allLegalMoves(context)
val captures = moves.filter(m => m.from == Square(File.E, Rank.R4) && m.moveType.isInstanceOf[MoveType.Normal])
val captures = moves.filter(m => m.from == Square(File.E, Rank.R4) && (m.moveType match { case _: MoveType.Normal => true; case _ => false }))
captures.exists(m => m.to == Square(File.D, Rank.R5)) shouldBe true
test("pawn cannot move backward"):