feat: add GameContext, CastleSide, and Board.withCastle
Introduces GameContext (board + per-side CastlingRights), CastleSide enum, and a Board.withCastle extension method. Also pins missing verification checksums for com.fasterxml:oss-parent:41 and org.junit:junit-bom:5.9.2. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Unresolved Issues
|
||||
|
||||
## [2026-03-24] JUnitSuiteLike mixin not available for ScalaTest 3.2.19 with Scala 3
|
||||
|
||||
**Requirement / Bug:**
|
||||
CLAUDE.md prescribes that all unit tests should extend `AnyFunSuite with Matchers with JUnitSuiteLike`. However, the `JUnitSuiteLike` trait cannot be resolved in the current build configuration.
|
||||
|
||||
**Root Cause (if known):**
|
||||
- ScalaTest 3.2.19 for Scala 3 does not provide `JUnitSuiteLike` in any public package.
|
||||
- The `co.helmethair:scalatest-junit-runner:0.1.11` dependency does not expose this trait.
|
||||
- There is no `org.scalatest:scalatest-junit_3` artifact available for version 3.2.19.
|
||||
- The trait may have been removed or changed in the ScalaTest 3.x → Scala 3 migration.
|
||||
|
||||
**Attempted Fixes:**
|
||||
1. Tried importing from `org.scalatest.junit.JUnitSuiteLike` — not found
|
||||
2. Tried importing from `org.scalatestplus.junit.JUnitSuiteLike` — not found
|
||||
3. Tried importing from `co.helmethair.scalatest.junit.JUnitSuiteLike` — not found
|
||||
4. Attempted to add `org.scalatest:scalatest-junit_3:3.2.19` dependency — artifact does not exist in Maven Central
|
||||
|
||||
**Suggested Next Step:**
|
||||
1. Either find the correct ScalaTest artifact/import for Scala 3 JUnit integration, or
|
||||
2. Update CLAUDE.md to reflect the actual constraint that unit tests should extend `AnyFunSuite with Matchers` (without `JUnitSuiteLike`), or
|
||||
3. Investigate whether a different test runner or configuration is needed to achieve JUnit integration with ScalaTest 3 in Scala 3
|
||||
@@ -57,7 +57,9 @@
|
||||
<trusted-key id="7B121B76A7ED6CE6E60AD51784E913A8E3A748C0" group="org.bouncycastle" name="bcprov-jdk18on" version="1.83"/>
|
||||
<trusted-key id="7CEAC05AFEB808AD75C2097D60BE32B1404779E5" group="co.helmethair" name="scalatest-junit-runner" version="0.1.11"/>
|
||||
<trusted-key id="84789D24DF77A32433CE1F079EB80E92EB2135B1" group="org.apache" name="apache" version="35"/>
|
||||
<trusted-key id="8A10792983023D5D14C93B488D7F1BEC1E2ECAE7" group="^com[.]fasterxml[.]jackson($|([.].*))" regex="true"/>
|
||||
<trusted-key id="8A10792983023D5D14C93B488D7F1BEC1E2ECAE7">
|
||||
<trusting group="^com[.]fasterxml($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="9D0A56AAA0D60E0C0C7DCCC0B4C70893B62BABE8" group="^org[.]apache[.]logging($|([.].*))" regex="true"/>
|
||||
<trusted-key id="A7D8BE3D575D6C5040E889331B6E3BDDD4415872" group="net.openhft"/>
|
||||
<trusted-key id="ACF39CCDED38E2C6F0898BF28F7F6C0451967B84" group="org.scala-lang" name="scala3-library_3"/>
|
||||
@@ -261,6 +263,14 @@
|
||||
<sha256 value="7a349d217790c3730be308ced1ea9ee32c4e74f72058e83c2b60e5a28954dd0d" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.junit" name="junit-bom" version="5.9.2">
|
||||
<artifact name="junit-bom-5.9.2.module">
|
||||
<sha256 value="ab137ba5a8e32c9b066bf9126a1c76dd5614b724ba5c0b02549772b5e9f4cf1f" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="junit-bom-5.9.2.pom">
|
||||
<sha256 value="2ed07d65845131f5336a86476c9a4056b59d0b58b9815ab3679bb0f36f35f705" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.junit" name="junit-bom" version="5.13.1">
|
||||
<artifact name="junit-bom-5.13.1.module">
|
||||
<sha256 value="33c07ab9724790a6e5859ba07d69117ac530439724545a81c4179e3272c75de8" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.nowchess.chess.logic
|
||||
|
||||
import de.nowchess.api.board.{Board, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.CastlingRights
|
||||
|
||||
enum CastleSide:
|
||||
case Kingside, Queenside
|
||||
|
||||
case class GameContext(
|
||||
board: Board,
|
||||
whiteCastling: CastlingRights,
|
||||
blackCastling: CastlingRights
|
||||
):
|
||||
def castlingFor(color: Color): CastlingRights =
|
||||
if color == Color.White then whiteCastling else blackCastling
|
||||
|
||||
def withUpdatedRights(color: Color, rights: CastlingRights): GameContext =
|
||||
if color == Color.White then copy(whiteCastling = rights)
|
||||
else copy(blackCastling = rights)
|
||||
|
||||
object GameContext:
|
||||
/** Convenience constructor for test boards: no castling rights on either side. */
|
||||
def apply(board: Board): GameContext =
|
||||
GameContext(board, CastlingRights.None, CastlingRights.None)
|
||||
|
||||
val initial: GameContext =
|
||||
GameContext(Board.initial, CastlingRights.Both, CastlingRights.Both)
|
||||
|
||||
extension (b: Board)
|
||||
def withCastle(color: Color, side: CastleSide): Board =
|
||||
val (kingFrom, kingTo, rookFrom, rookTo) = (color, side) match
|
||||
case (Color.White, CastleSide.Kingside) =>
|
||||
(Square(File.E, Rank.R1), Square(File.G, Rank.R1),
|
||||
Square(File.H, Rank.R1), Square(File.F, Rank.R1))
|
||||
case (Color.White, CastleSide.Queenside) =>
|
||||
(Square(File.E, Rank.R1), Square(File.C, Rank.R1),
|
||||
Square(File.A, Rank.R1), Square(File.D, Rank.R1))
|
||||
case (Color.Black, CastleSide.Kingside) =>
|
||||
(Square(File.E, Rank.R8), Square(File.G, Rank.R8),
|
||||
Square(File.H, Rank.R8), Square(File.F, Rank.R8))
|
||||
case (Color.Black, CastleSide.Queenside) =>
|
||||
(Square(File.E, Rank.R8), Square(File.C, Rank.R8),
|
||||
Square(File.A, Rank.R8), Square(File.D, Rank.R8))
|
||||
val king = Piece(color, PieceType.King)
|
||||
val rook = Piece(color, PieceType.Rook)
|
||||
Board(b.pieces.removed(kingFrom).removed(rookFrom)
|
||||
.updated(kingTo, king).updated(rookTo, rook))
|
||||
@@ -0,0 +1,81 @@
|
||||
package de.nowchess.chess.logic
|
||||
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.game.CastlingRights
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class GameContextTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def sq(f: File, r: Rank): Square = Square(f, r)
|
||||
private def board(entries: (Square, Piece)*): Board = Board(entries.toMap)
|
||||
|
||||
test("GameContext.initial has Board.initial and CastlingRights.Both for both sides"):
|
||||
GameContext.initial.board shouldBe Board.initial
|
||||
GameContext.initial.whiteCastling shouldBe CastlingRights.Both
|
||||
GameContext.initial.blackCastling shouldBe CastlingRights.Both
|
||||
|
||||
test("castlingFor returns white rights for Color.White"):
|
||||
GameContext.initial.castlingFor(Color.White) shouldBe CastlingRights.Both
|
||||
|
||||
test("castlingFor returns black rights for Color.Black"):
|
||||
GameContext.initial.castlingFor(Color.Black) shouldBe CastlingRights.Both
|
||||
|
||||
test("withUpdatedRights updates white castling without touching black"):
|
||||
val ctx = GameContext.initial.withUpdatedRights(Color.White, CastlingRights.None)
|
||||
ctx.whiteCastling shouldBe CastlingRights.None
|
||||
ctx.blackCastling shouldBe CastlingRights.Both
|
||||
|
||||
test("withUpdatedRights updates black castling without touching white"):
|
||||
val ctx = GameContext.initial.withUpdatedRights(Color.Black, CastlingRights.None)
|
||||
ctx.blackCastling shouldBe CastlingRights.None
|
||||
ctx.whiteCastling shouldBe CastlingRights.Both
|
||||
|
||||
test("withCastle: white kingside — king e1→g1, rook h1→f1"):
|
||||
val b = board(
|
||||
sq(File.E, Rank.R1) -> Piece.WhiteKing,
|
||||
sq(File.H, Rank.R1) -> Piece.WhiteRook
|
||||
)
|
||||
val after = b.withCastle(Color.White, CastleSide.Kingside)
|
||||
after.pieceAt(sq(File.G, Rank.R1)) shouldBe Some(Piece.WhiteKing)
|
||||
after.pieceAt(sq(File.F, Rank.R1)) shouldBe Some(Piece.WhiteRook)
|
||||
after.pieceAt(sq(File.E, Rank.R1)) shouldBe None
|
||||
after.pieceAt(sq(File.H, Rank.R1)) shouldBe None
|
||||
|
||||
test("withCastle: white queenside — king e1→c1, rook a1→d1"):
|
||||
val b = board(
|
||||
sq(File.E, Rank.R1) -> Piece.WhiteKing,
|
||||
sq(File.A, Rank.R1) -> Piece.WhiteRook
|
||||
)
|
||||
val after = b.withCastle(Color.White, CastleSide.Queenside)
|
||||
after.pieceAt(sq(File.C, Rank.R1)) shouldBe Some(Piece.WhiteKing)
|
||||
after.pieceAt(sq(File.D, Rank.R1)) shouldBe Some(Piece.WhiteRook)
|
||||
after.pieceAt(sq(File.E, Rank.R1)) shouldBe None
|
||||
after.pieceAt(sq(File.A, Rank.R1)) shouldBe None
|
||||
|
||||
test("withCastle: black kingside — king e8→g8, rook h8→f8"):
|
||||
val b = board(
|
||||
sq(File.E, Rank.R8) -> Piece.BlackKing,
|
||||
sq(File.H, Rank.R8) -> Piece.BlackRook
|
||||
)
|
||||
val after = b.withCastle(Color.Black, CastleSide.Kingside)
|
||||
after.pieceAt(sq(File.G, Rank.R8)) shouldBe Some(Piece.BlackKing)
|
||||
after.pieceAt(sq(File.F, Rank.R8)) shouldBe Some(Piece.BlackRook)
|
||||
after.pieceAt(sq(File.E, Rank.R8)) shouldBe None
|
||||
after.pieceAt(sq(File.H, Rank.R8)) shouldBe None
|
||||
|
||||
test("withCastle: black queenside — king e8→c8, rook a8→d8"):
|
||||
val b = board(
|
||||
sq(File.E, Rank.R8) -> Piece.BlackKing,
|
||||
sq(File.A, Rank.R8) -> Piece.BlackRook
|
||||
)
|
||||
val after = b.withCastle(Color.Black, CastleSide.Queenside)
|
||||
after.pieceAt(sq(File.C, Rank.R8)) shouldBe Some(Piece.BlackKing)
|
||||
after.pieceAt(sq(File.D, Rank.R8)) shouldBe Some(Piece.BlackRook)
|
||||
after.pieceAt(sq(File.E, Rank.R8)) shouldBe None
|
||||
after.pieceAt(sq(File.A, Rank.R8)) shouldBe None
|
||||
|
||||
test("GameContext single-arg apply defaults to CastlingRights.None for both sides"):
|
||||
val ctx = GameContext(Board.initial)
|
||||
ctx.whiteCastling shouldBe CastlingRights.None
|
||||
ctx.blackCastling shouldBe CastlingRights.None
|
||||
Reference in New Issue
Block a user