feat: NCS-41 Bot Platform (#33)
Build & Test (NowChessSystems) TeamCity build finished

Co-authored-by: Janis <janis@nowchess.de>
Reviewed-on: #33
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
This commit was merged in pull request #33.
This commit is contained in:
2026-04-19 15:52:08 +02:00
committed by Janis
parent 5f4d33f3ca
commit dceab0875e
117 changed files with 2531201 additions and 424 deletions
@@ -81,8 +81,7 @@ object DefaultRules extends RuleSet:
private def countPositionOccurrences(context: GameContext, targetPosition: Position): Int =
try
var count = 0
var tempCtx = GameContext(
val initialCtx = GameContext(
board = context.initialBoard,
turn = Color.White,
castlingRights = CastlingRights.Initial,
@@ -91,20 +90,24 @@ object DefaultRules extends RuleSet:
moves = List.empty,
initialBoard = context.initialBoard,
)
var tempPos = Position(tempCtx.board, tempCtx.turn, tempCtx.castlingRights, tempCtx.enPassantSquare)
if tempPos == targetPosition then count += 1
for move <- context.moves do
tempCtx = applyMove(tempCtx)(move)
tempPos = Position(
board = tempCtx.board,
turn = tempCtx.turn,
castlingRights = tempCtx.castlingRights,
enPassantSquare = tempCtx.enPassantSquare,
def positionOf(ctx: GameContext): Position =
Position(
board = ctx.board,
turn = ctx.turn,
castlingRights = ctx.castlingRights,
enPassantSquare = ctx.enPassantSquare,
)
if tempPos == targetPosition then count += 1
count
val initialCount = if positionOf(initialCtx) == targetPosition then 1 else 0
context.moves
.foldLeft((initialCtx, initialCount)) { case ((tempCtx, count), move) =>
val nextCtx = applyMove(tempCtx)(move)
val nextCount = if positionOf(nextCtx) == targetPosition then count + 1 else count
(nextCtx, nextCount)
}
._2
catch
case _: Exception =>
// If replay fails, conservatively count only the current position (never triggers a draw)
@@ -29,10 +29,12 @@ class DefaultRulesTest extends AnyFunSuite with Matchers:
test("pawn can capture diagonally"):
// FEN: white pawn e4, black pawn d5
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 match { case _: MoveType.Normal => true; case _ => false }))
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 match { case _: MoveType.Normal => true; case _ => false }),
)
captures.exists(m => m.to == Square(File.D, Rank.R5)) shouldBe true
test("pawn cannot move backward"):
@@ -208,7 +210,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers:
test("threefold repetition catch block returns false for inconsistent context"):
// A context whose moves cannot be replayed from initialBoard (forces the catch path)
val m = Move(Square(File.E, Rank.R5), Square(File.E, Rank.R6)) // e5→e6, no pawn there in initial board
val m = Move(Square(File.E, Rank.R5), Square(File.E, Rank.R6)) // e5→e6, no pawn there in initial board
val brokenCtx = GameContext(
board = Board.initial,
turn = Color.White,