diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/dto/Dtos.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/dto/Dtos.scala index 28d17cb..929ca56 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/dto/Dtos.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/dto/Dtos.scala @@ -6,52 +6,52 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include case class PlayerInfoDto(id: String, displayName: String) case class GameStateResponse( - fen: String, - pgn: String, - turn: String, - status: String, - @JsonInclude(Include.NON_ABSENT) winner: Option[String], - moves: List[String], - undoAvailable: Boolean, - redoAvailable: Boolean, + fen: String, + pgn: String, + turn: String, + status: String, + @JsonInclude(Include.NON_ABSENT) winner: Option[String], + moves: List[String], + undoAvailable: Boolean, + redoAvailable: Boolean, ) case class GameFullResponse( - gameId: String, - white: PlayerInfoDto, - black: PlayerInfoDto, - state: GameStateResponse, + gameId: String, + white: PlayerInfoDto, + black: PlayerInfoDto, + state: GameStateResponse, ) case class OkResponse(ok: Boolean = true) @JsonInclude(Include.NON_ABSENT) case class ApiErrorResponse( - code: String, - message: String, - field: Option[String] = None, + code: String, + message: String, + field: Option[String] = None, ) // Requests case class CreateGameRequest( - white: Option[PlayerInfoDto] = None, - black: Option[PlayerInfoDto] = None, + white: Option[PlayerInfoDto] = None, + black: Option[PlayerInfoDto] = None, ) case class ImportFenRequest( - fen: String = "", - white: Option[PlayerInfoDto] = None, - black: Option[PlayerInfoDto] = None, + fen: String = "", + white: Option[PlayerInfoDto] = None, + black: Option[PlayerInfoDto] = None, ) case class ImportPgnRequest(pgn: String = "") case class LegalMoveDto( - from: String, - to: String, - uci: String, - moveType: String, - @JsonInclude(Include.NON_ABSENT) promotion: Option[String] = None, + from: String, + to: String, + uci: String, + moveType: String, + @JsonInclude(Include.NON_ABSENT) promotion: Option[String] = None, ) case class LegalMovesResponse(moves: List[LegalMoveDto]) diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameId.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameId.scala index 4f60437..34f3488 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameId.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameId.scala @@ -3,7 +3,7 @@ package de.nowchess.backcore.game import java.security.SecureRandom object GameId: - private val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + private val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" private val random = SecureRandom() def generate(): String = diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameMapper.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameMapper.scala index 96aac04..634b641 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameMapper.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameMapper.scala @@ -12,7 +12,6 @@ import de.nowchess.rules.sets.DefaultRules object GameMapper: private val mapper = new ObjectMapper().registerModule(DefaultScalaModule) - def toGameFullJson(session: GameSession): String = mapper.writeValueAsString(toGameFull(session)) diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameResult.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameResult.scala index 3b14da6..a3d5941 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameResult.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameResult.scala @@ -5,8 +5,8 @@ import de.nowchess.api.board.Color sealed trait GameResult object GameResult: case class Checkmate(winner: Color) extends GameResult - case object Stalemate extends GameResult - case class Resign(winner: Color) extends GameResult - case object AgreedDraw extends GameResult - case object FiftyMoveDraw extends GameResult - case object InsufficientMaterial extends GameResult + case object Stalemate extends GameResult + case class Resign(winner: Color) extends GameResult + case object AgreedDraw extends GameResult + case object FiftyMoveDraw extends GameResult + case object InsufficientMaterial extends GameResult diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameSession.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameSession.scala index 32025fd..8d0b437 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameSession.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameSession.scala @@ -6,11 +6,11 @@ import de.nowchess.api.player.PlayerInfo import de.nowchess.chess.command.CommandInvoker case class GameSession( - gameId: String, - white: PlayerInfo, - black: PlayerInfo, - context: GameContext, - invoker: CommandInvoker, - drawOfferedBy: Option[Color] = None, - result: Option[GameResult] = None, + gameId: String, + white: PlayerInfo, + black: PlayerInfo, + context: GameContext, + invoker: CommandInvoker, + drawOfferedBy: Option[Color] = None, + result: Option[GameResult] = None, ) diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameStore.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameStore.scala index e42a2d3..3fc33a8 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameStore.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/game/GameStore.scala @@ -41,11 +41,11 @@ class GameStore: findMatchingMove(legalCandidates, to, promotion) match case None => Left(s"$uci is not a legal move") case Some(move) => - val nextCtx = DefaultRules.applyMove(session.context)(move) - val prevCtx = session.context + val nextCtx = DefaultRules.applyMove(session.context)(move) + val prevCtx = session.context val cmd = MoveCommand( from = move.from, - to = move.to, + to = move.to, moveResult = Some(MoveResult.Successful(nextCtx, prevCtx.board.pieceAt(move.to))), previousContext = Some(prevCtx), ) @@ -163,7 +163,7 @@ class GameStore: val session = newSession(id, white, black, GameContext.initial) replayIntoSession(session, game.moves, GameContext.initial) match case Left(err) => Left(err) - case Right(s) => + case Right(s) => games(id) = s Right(s) @@ -180,15 +180,15 @@ class GameStore: id private def newSession( - id: String, - white: Option[PlayerInfoDto], - black: Option[PlayerInfoDto], - ctx: GameContext, + id: String, + white: Option[PlayerInfoDto], + black: Option[PlayerInfoDto], + ctx: GameContext, ): GameSession = GameSession( - gameId = id, - white = toPlayerInfo(white, "white", "White"), - black = toPlayerInfo(black, "black", "Black"), + gameId = id, + white = toPlayerInfo(white, "white", "White"), + black = toPlayerInfo(black, "black", "Black"), context = ctx, invoker = new CommandInvoker(), ) @@ -215,17 +215,19 @@ class GameStore: case _ => None private def findMatchingMove( - candidates: List[Move], - to: Square, - promotion: Option[PromotionPiece], + candidates: List[Move], + to: Square, + promotion: Option[PromotionPiece], ): Option[Move] = candidates.filter(_.to == to) match case Nil => None case moves => promotion match case Some(pp) => moves.find(_.moveType == MoveType.Promotion(pp)) - case None => moves.find(m => !m.moveType.isInstanceOf[MoveType.Promotion]) - .orElse(moves.headOption) + case None => + moves + .find(m => !m.moveType.isInstanceOf[MoveType.Promotion]) + .orElse(moves.headOption) private def detectGameOver(ctx: GameContext): Option[GameResult] = if DefaultRules.isCheckmate(ctx) then Some(GameResult.Checkmate(ctx.turn.opposite)) @@ -234,22 +236,23 @@ class GameStore: else None private def replayIntoSession( - session: GameSession, - moves: List[Move], - startCtx: GameContext, + session: GameSession, + moves: List[Move], + startCtx: GameContext, ): Either[String, GameSession] = moves.foldLeft[Either[String, GameSession]](Right(session)): case (Left(err), _) => Left(err) case (Right(s), move) => val legal = DefaultRules.legalMoves(s.context)(move.from) - legal.find(m => m.from == move.from && m.to == move.to && m.moveType == move.moveType) + legal + .find(m => m.from == move.from && m.to == move.to && m.moveType == move.moveType) .orElse(legal.find(m => m.from == move.from && m.to == move.to)) match case None => Left(s"Illegal move in PGN: $move") case Some(legalMove) => val nextCtx = DefaultRules.applyMove(s.context)(legalMove) val cmd = MoveCommand( from = legalMove.from, - to = legalMove.to, + to = legalMove.to, moveResult = Some(MoveResult.Successful(nextCtx, s.context.board.pieceAt(legalMove.to))), previousContext = Some(s.context), ) diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/resource/GameResource.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/resource/GameResource.scala index 61bcaf3..0c72855 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/resource/GameResource.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/resource/GameResource.scala @@ -24,7 +24,8 @@ class GameResource @Inject() (store: GameStore): store.get(gameId) match case Some(session) => Response.ok(GameMapper.toGameFull(session)).build() case None => - Response.status(404) + Response + .status(404) .entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")) .build() @@ -34,7 +35,8 @@ class GameResource @Inject() (store: GameStore): def streamGame(@PathParam("gameId") gameId: String): Response = store.get(gameId) match case None => - Response.status(404) + Response + .status(404) .`type`(MediaType.APPLICATION_JSON) .entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")) .build() @@ -56,8 +58,8 @@ class GameResource @Inject() (store: GameStore): @POST @Path("/{gameId}/draw/{action}") def drawAction( - @PathParam("gameId") gameId: String, - @PathParam("action") action: String, + @PathParam("gameId") gameId: String, + @PathParam("action") action: String, ): Response = store.drawAction(gameId, action) match case Right(_) => Response.ok(OkResponse()).build() @@ -72,7 +74,8 @@ class GameResource @Inject() (store: GameStore): def exportFen(@PathParam("gameId") gameId: String): Response = store.get(gameId) match case None => - Response.status(404) + Response + .status(404) .`type`(MediaType.APPLICATION_JSON) .entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")) .build() @@ -86,7 +89,8 @@ class GameResource @Inject() (store: GameStore): def exportPgn(@PathParam("gameId") gameId: String): Response = store.get(gameId) match case None => - Response.status(404) + Response + .status(404) .`type`(MediaType.APPLICATION_JSON) .entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")) .build() diff --git a/modules/backcore/src/main/scala/de/nowchess/backcore/resource/MoveResource.scala b/modules/backcore/src/main/scala/de/nowchess/backcore/resource/MoveResource.scala index 395af14..b95b435 100644 --- a/modules/backcore/src/main/scala/de/nowchess/backcore/resource/MoveResource.scala +++ b/modules/backcore/src/main/scala/de/nowchess/backcore/resource/MoveResource.scala @@ -17,8 +17,8 @@ class MoveResource @Inject() (store: GameStore): @POST @Path("/{gameId}/move/{uci}") def makeMove( - @PathParam("gameId") gameId: String, - @PathParam("uci") uci: String, + @PathParam("gameId") gameId: String, + @PathParam("uci") uci: String, ): Response = store.applyMove(gameId, uci) match case Right(session) => Response.ok(GameMapper.toGameState(session)).build() @@ -30,8 +30,8 @@ class MoveResource @Inject() (store: GameStore): @GET @Path("/{gameId}/moves") def getLegalMoves( - @PathParam("gameId") gameId: String, - @QueryParam("square") squareParam: String, + @PathParam("gameId") gameId: String, + @QueryParam("square") squareParam: String, ): Response = val square = Option(squareParam).flatMap(Square.fromAlgebraic) store.legalMoves(gameId, square) match @@ -66,12 +66,12 @@ class MoveResource @Inject() (store: GameStore): private def toLegalMoveDto(move: Move): LegalMoveDto = val uci = GameMapper.moveToUci(move) val (moveType, promotion) = move.moveType match - case MoveType.Normal(true) => ("capture", None) - case MoveType.Normal(false) => ("normal", None) - case MoveType.CastleKingside => ("castleKingside", None) - case MoveType.CastleQueenside => ("castleQueenside", None) - case MoveType.EnPassant => ("enPassant", None) - case MoveType.Promotion(pp) => + case MoveType.Normal(true) => ("capture", None) + case MoveType.Normal(false) => ("normal", None) + case MoveType.CastleKingside => ("castleKingside", None) + case MoveType.CastleQueenside => ("castleQueenside", None) + case MoveType.EnPassant => ("enPassant", None) + case MoveType.Promotion(pp) => val pName = pp match case PromotionPiece.Queen => "queen" case PromotionPiece.Rook => "rook" @@ -79,9 +79,9 @@ class MoveResource @Inject() (store: GameStore): case PromotionPiece.Knight => "knight" ("promotion", Some(pName)) LegalMoveDto( - from = move.from.toString, - to = move.to.toString, - uci = uci, - moveType = moveType, + from = move.from.toString, + to = move.to.toString, + uci = uci, + moveType = moveType, promotion = promotion, ) diff --git a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/GameResourceTest.scala b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/GameResourceTest.scala index 61387fa..9df2ba7 100644 --- a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/GameResourceTest.scala +++ b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/GameResourceTest.scala @@ -10,7 +10,8 @@ class GameResourceTest: @Test def createGameReturns201WithGameId(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -24,7 +25,8 @@ class GameResourceTest: @Test def createGameWithPlayersReturns201(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("""{"white":{"id":"p1","displayName":"Alice"},"black":{"id":"p2","displayName":"Bob"}}""") .when() @@ -36,7 +38,8 @@ class GameResourceTest: @Test def getGameReturns200ForExistingGame(): Unit = - val gameId = RestAssured.`given`() + val gameId = RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -46,7 +49,8 @@ class GameResourceTest: .extract() .path[String]("gameId") - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId") .`then`() @@ -55,7 +59,8 @@ class GameResourceTest: @Test def getGameReturns404ForUnknownId(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .get("/api/board/game/XXXXXXXX") .`then`() diff --git a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ImportExportTest.scala b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ImportExportTest.scala index a4a8eff..5e62c7e 100644 --- a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ImportExportTest.scala +++ b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ImportExportTest.scala @@ -14,7 +14,8 @@ class ImportExportTest: @Test def importFenReturns201WithCorrectPosition(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body(s"""{"fen":"$startFen"}""") .when() @@ -28,7 +29,8 @@ class ImportExportTest: @Test def importFenWithCustomPositionWorks(): Unit = val fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1" - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body(s"""{"fen":"$fen"}""") .when() @@ -40,7 +42,8 @@ class ImportExportTest: @Test def importFenWithInvalidFenReturns400(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("""{"fen":"not-a-fen"}""") .when() @@ -52,7 +55,8 @@ class ImportExportTest: @Test def importPgnReturns201(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("""{"pgn":"1. e4 e5 2. Nf3 Nc6 *"}""") .when() @@ -64,7 +68,8 @@ class ImportExportTest: @Test def importPgnWithInvalidPgnReturns400(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("""{"pgn":"1. z9 *"}""") .when() @@ -76,7 +81,8 @@ class ImportExportTest: @Test def exportFenReturnsStartingFen(): Unit = - val gameId = RestAssured.`given`() + val gameId = RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -86,7 +92,8 @@ class ImportExportTest: .extract() .path[String]("gameId") - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId/export/fen") .`then`() @@ -95,7 +102,8 @@ class ImportExportTest: @Test def exportFenOnUnknownGameReturns404(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .get("/api/board/game/XXXXXXXX/export/fen") .`then`() @@ -105,7 +113,8 @@ class ImportExportTest: @Test def exportPgnReturnsText(): Unit = - val gameId = RestAssured.`given`() + val gameId = RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -115,13 +124,15 @@ class ImportExportTest: .extract() .path[String]("gameId") - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/move/e2e4") .`then`() .statusCode(200) - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId/export/pgn") .`then`() @@ -132,7 +143,8 @@ class ImportExportTest: @Test def streamReturnsNdjsonSnapshot(): Unit = - val gameId = RestAssured.`given`() + val gameId = RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -142,7 +154,8 @@ class ImportExportTest: .extract() .path[String]("gameId") - val body = RestAssured.`given`() + val body = RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId/stream") .`then`() @@ -156,7 +169,8 @@ class ImportExportTest: @Test def streamOnUnknownGameReturns404(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .get("/api/board/game/XXXXXXXX/stream") .`then`() diff --git a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/MoveResourceTest.scala b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/MoveResourceTest.scala index bd04b22..7524fd4 100644 --- a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/MoveResourceTest.scala +++ b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/MoveResourceTest.scala @@ -9,7 +9,8 @@ import org.junit.jupiter.api.Test class MoveResourceTest: private def createGame(): String = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -22,18 +23,20 @@ class MoveResourceTest: @Test def makeMoveReturns200WithUpdatedFen(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/move/e2e4") .`then`() .statusCode(200) - .body("fen", containsString("4P3")) // e4 pawn present in FEN + .body("fen", containsString("4P3")) // e4 pawn present in FEN .body("turn", equalTo("black")) .body("moves", hasItem("e2e4")) @Test def makeMoveOnUnknownGameReturns404(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .post("/api/board/game/XXXXXXXX/move/e2e4") .`then`() @@ -42,16 +45,18 @@ class MoveResourceTest: @Test def illegalMoveReturns400(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() - .post(s"/api/board/game/$gameId/move/e2e5") // illegal — pawns can't jump 3 squares + .post(s"/api/board/game/$gameId/move/e2e5") // illegal — pawns can't jump 3 squares .`then`() .statusCode(400) @Test def getLegalMovesReturnsNonEmptyList(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId/moves") .`then`() @@ -61,7 +66,8 @@ class MoveResourceTest: @Test def getLegalMovesFilteredBySquareReturnsCorrectMoves(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId/moves?square=e2") .`then`() @@ -70,7 +76,8 @@ class MoveResourceTest: @Test def getLegalMovesOnUnknownGameReturns404(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .get("/api/board/game/XXXXXXXX/moves") .`then`() diff --git a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ResignDrawTest.scala b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ResignDrawTest.scala index 74f7587..997c718 100644 --- a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ResignDrawTest.scala +++ b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/ResignDrawTest.scala @@ -9,7 +9,8 @@ import org.junit.jupiter.api.Test class ResignDrawTest: private def createGame(): String = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -24,7 +25,8 @@ class ResignDrawTest: @Test def resignReturns200(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/resign") .`then`() @@ -34,13 +36,15 @@ class ResignDrawTest: @Test def afterResignGameShowsResignStatusAndWinner(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/resign") .`then`() .statusCode(200) - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId") .`then`() @@ -50,7 +54,8 @@ class ResignDrawTest: @Test def resignOnUnknownGameReturns404(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .post("/api/board/game/XXXXXXXX/resign") .`then`() @@ -61,14 +66,16 @@ class ResignDrawTest: @Test def offerDrawSetsDrawOfferedStatus(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/draw/offer") .`then`() .statusCode(200) .body("ok", equalTo(true)) - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId") .`then`() @@ -79,7 +86,8 @@ class ResignDrawTest: def acceptDrawAfterOfferSetsDrawStatus(): Unit = val gameId = createGame() // White offers - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/draw/offer") .`then`() @@ -90,21 +98,24 @@ class ResignDrawTest: // The GameStore checks drawOfferedBy != turn to allow accept. // White offered on white's turn, so black needs to accept — but current turn is still white. // We need to make a move first to switch turns. - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/move/e2e4") .`then`() .statusCode(200) // Now it's black's turn and white offered the draw — black accepts - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/draw/accept") .`then`() .statusCode(200) .body("ok", equalTo(true)) - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId") .`then`() @@ -114,20 +125,23 @@ class ResignDrawTest: @Test def declineDrawClearsOffer(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/draw/offer") .`then`() .statusCode(200) - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/draw/decline") .`then`() .statusCode(200) .body("ok", equalTo(true)) - RestAssured.`given`() + RestAssured + .`given`() .when() .get(s"/api/board/game/$gameId") .`then`() @@ -137,7 +151,8 @@ class ResignDrawTest: @Test def acceptWithoutOfferReturns400(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/draw/accept") .`then`() @@ -145,7 +160,8 @@ class ResignDrawTest: @Test def drawOnUnknownGameReturns404(): Unit = - RestAssured.`given`() + RestAssured + .`given`() .when() .post("/api/board/game/XXXXXXXX/draw/offer") .`then`() diff --git a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/UndoRedoTest.scala b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/UndoRedoTest.scala index 2e86344..b646d8b 100644 --- a/modules/backcore/src/test/scala/de/nowchess/backcore/resource/UndoRedoTest.scala +++ b/modules/backcore/src/test/scala/de/nowchess/backcore/resource/UndoRedoTest.scala @@ -11,7 +11,8 @@ class UndoRedoTest: private val initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" private def createGame(): String = - RestAssured.`given`() + RestAssured + .`given`() .contentType("application/json") .body("{}") .when() @@ -24,13 +25,15 @@ class UndoRedoTest: @Test def undoAfterMoveRestoresOriginalPosition(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/move/e2e4") .`then`() .statusCode(200) - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/undo") .`then`() @@ -41,19 +44,22 @@ class UndoRedoTest: @Test def redoAfterUndoRestoresMovedPosition(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/move/e2e4") .`then`() .statusCode(200) - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/undo") .`then`() .statusCode(200) - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/redo") .`then`() @@ -64,7 +70,8 @@ class UndoRedoTest: @Test def undoWithNoHistoryReturns400(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/undo") .`then`() @@ -73,7 +80,8 @@ class UndoRedoTest: @Test def redoWithNoRedoStackReturns400(): Unit = val gameId = createGame() - RestAssured.`given`() + RestAssured + .`given`() .when() .post(s"/api/board/game/$gameId/redo") .`then`() diff --git a/modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala b/modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala index e8158f4..337a31a 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala @@ -18,8 +18,8 @@ class CommandInvokerBranchTest extends AnyFunSuite with Matchers: initialShouldFailOnUndo: Boolean = false, initialShouldFailOnExecute: Boolean = false, ) extends Command: - val shouldFailOnUndo = new java.util.concurrent.atomic.AtomicBoolean(initialShouldFailOnUndo) - val shouldFailOnExecute = new java.util.concurrent.atomic.AtomicBoolean(initialShouldFailOnExecute) + val shouldFailOnUndo = new java.util.concurrent.atomic.AtomicBoolean(initialShouldFailOnUndo) + val shouldFailOnExecute = new java.util.concurrent.atomic.AtomicBoolean(initialShouldFailOnExecute) override def execute(): Boolean = !shouldFailOnExecute.get() override def undo(): Boolean = !shouldFailOnUndo.get() override def description: String = "Conditional fail" diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala index 902d392..897429a 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala @@ -69,7 +69,9 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: engine.context shouldBe target engine.commandHistory shouldBe empty - events.lastOption.exists { case _: de.nowchess.chess.observer.BoardResetEvent => true; case _ => false } shouldBe true + events.lastOption.exists { + case _: de.nowchess.chess.observer.BoardResetEvent => true; case _ => false + } shouldBe true test("redo event includes captured piece description when replaying a capture"): val engine = new GameEngine() diff --git a/modules/io/src/test/scala/de/nowchess/io/fen/FenParserFastParseTest.scala b/modules/io/src/test/scala/de/nowchess/io/fen/FenParserFastParseTest.scala index 9e5575a..974cfd8 100644 --- a/modules/io/src/test/scala/de/nowchess/io/fen/FenParserFastParseTest.scala +++ b/modules/io/src/test/scala/de/nowchess/io/fen/FenParserFastParseTest.scala @@ -70,58 +70,66 @@ class FenParserFastParseTest extends AnyFunSuite with Matchers: FenParserFastParse.parseBoard("8pp/8/8/8/8/8/8/8") shouldBe None test("parseFen handles all individual castling rights"): - FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w K - 0 1").fold(_ => fail(), ctx => - ctx.castlingRights.whiteKingSide shouldBe true - ctx.castlingRights.whiteQueenSide shouldBe false - ctx.castlingRights.blackKingSide shouldBe false - ctx.castlingRights.blackQueenSide shouldBe false - ) + FenParserFastParse + .parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w K - 0 1") + .fold( + _ => fail(), + ctx => + ctx.castlingRights.whiteKingSide shouldBe true + ctx.castlingRights.whiteQueenSide shouldBe false + ctx.castlingRights.blackKingSide shouldBe false + ctx.castlingRights.blackQueenSide shouldBe false, + ) - FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Q - 0 1").fold(_ => fail(), ctx => - ctx.castlingRights.whiteQueenSide shouldBe true - ctx.castlingRights.whiteKingSide shouldBe false - ) + FenParserFastParse + .parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Q - 0 1") + .fold( + _ => fail(), + ctx => + ctx.castlingRights.whiteQueenSide shouldBe true + ctx.castlingRights.whiteKingSide shouldBe false, + ) - FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w k - 0 1").fold(_ => fail(), ctx => - ctx.castlingRights.blackKingSide shouldBe true - ctx.castlingRights.whiteKingSide shouldBe false - ) + FenParserFastParse + .parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w k - 0 1") + .fold( + _ => fail(), + ctx => + ctx.castlingRights.blackKingSide shouldBe true + ctx.castlingRights.whiteKingSide shouldBe false, + ) - FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w q - 0 1").fold(_ => fail(), ctx => - ctx.castlingRights.blackQueenSide shouldBe true - ctx.castlingRights.whiteKingSide shouldBe false - ) + FenParserFastParse + .parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w q - 0 1") + .fold( + _ => fail(), + ctx => + ctx.castlingRights.blackQueenSide shouldBe true + ctx.castlingRights.whiteKingSide shouldBe false, + ) test("parseFen parses all en passant squares"): - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - a3 0 1").fold(_ => fail(), ctx => - ctx.enPassantSquare shouldBe Some(Square(File.A, Rank.R3)) - ) + FenParserFastParse + .parseFen("8/8/8/8/8/8/8/8 w - a3 0 1") + .fold(_ => fail(), ctx => ctx.enPassantSquare shouldBe Some(Square(File.A, Rank.R3))) - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - h6 0 1").fold(_ => fail(), ctx => - ctx.enPassantSquare shouldBe Some(Square(File.H, Rank.R6)) - ) + FenParserFastParse + .parseFen("8/8/8/8/8/8/8/8 w - h6 0 1") + .fold(_ => fail(), ctx => ctx.enPassantSquare shouldBe Some(Square(File.H, Rank.R6))) test("parseFen parses different halfMove and fullMove clocks"): - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 5 10").fold(_ => fail(), ctx => - ctx.halfMoveClock shouldBe 5 - ) + FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 5 10").fold(_ => fail(), ctx => ctx.halfMoveClock shouldBe 5) - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 100").fold(_ => fail(), ctx => - ctx.halfMoveClock shouldBe 0 - ) + FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 100").fold(_ => fail(), ctx => ctx.halfMoveClock shouldBe 0) test("parseBoard parses boards with mixed empty and piece tokens"): val mixed = "8/1p1p1p1p/8/1P1P1P1P/8/8/8/8" FenParserFastParse.parseBoard(mixed) should not be empty test("parseFen handles turn transitions"): - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 1").fold(_ => fail(), ctx => - ctx.turn shouldBe Color.White - ) + FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 1").fold(_ => fail(), ctx => ctx.turn shouldBe Color.White) - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 b - - 0 1").fold(_ => fail(), ctx => - ctx.turn shouldBe Color.Black - ) + FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 b - - 0 1").fold(_ => fail(), ctx => ctx.turn shouldBe Color.Black) test("parseFen rejects invalid piece characters"): FenParserFastParse.parseFen("8x/8/8/8/8/8/8/8 w - - 0 1").isLeft shouldBe true @@ -133,7 +141,7 @@ class FenParserFastParseTest extends AnyFunSuite with Matchers: test("parseBoard tests all piece types in various positions"): // Test each piece type: pawn, rook, knight, bishop, queen, king (both colors) val allPieces = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" - val parsed = FenParserFastParse.parseBoard(allPieces) + val parsed = FenParserFastParse.parseBoard(allPieces) parsed.map(_.pieces.size) shouldBe Some(32) parsed.map(_.pieceAt(Square(File.A, Rank.R8))) shouldBe Some(Some(Piece.BlackRook)) parsed.map(_.pieceAt(Square(File.B, Rank.R8))) shouldBe Some(Some(Piece.BlackKnight)) @@ -150,25 +158,33 @@ class FenParserFastParseTest extends AnyFunSuite with Matchers: FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 b - - 0 1").fold(_ => fail(), _.turn shouldBe Color.Black) test("parseFen tests all castling combinations"): - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w KQkq - 0 1").fold(_ => fail(), ctx => - ctx.castlingRights.whiteKingSide shouldBe true - ctx.castlingRights.whiteQueenSide shouldBe true - ctx.castlingRights.blackKingSide shouldBe true - ctx.castlingRights.blackQueenSide shouldBe true - ) + FenParserFastParse + .parseFen("8/8/8/8/8/8/8/8 w KQkq - 0 1") + .fold( + _ => fail(), + ctx => + ctx.castlingRights.whiteKingSide shouldBe true + ctx.castlingRights.whiteQueenSide shouldBe true + ctx.castlingRights.blackKingSide shouldBe true + ctx.castlingRights.blackQueenSide shouldBe true, + ) - FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w Kq - 0 1").fold(_ => fail(), ctx => - ctx.castlingRights.whiteKingSide shouldBe true - ctx.castlingRights.whiteQueenSide shouldBe false - ctx.castlingRights.blackKingSide shouldBe false - ctx.castlingRights.blackQueenSide shouldBe true - ) + FenParserFastParse + .parseFen("8/8/8/8/8/8/8/8 w Kq - 0 1") + .fold( + _ => fail(), + ctx => + ctx.castlingRights.whiteKingSide shouldBe true + ctx.castlingRights.whiteQueenSide shouldBe false + ctx.castlingRights.blackKingSide shouldBe false + ctx.castlingRights.blackQueenSide shouldBe true, + ) test("parseFen tests all en passant files"): for file <- Seq("a", "b", "c", "d", "e", "f", "g", "h") do - FenParserFastParse.parseFen(s"8/8/8/8/8/8/8/8 w - ${file}3 0 1").fold(_ => fail(), ctx => - ctx.enPassantSquare should not be empty - ) + FenParserFastParse + .parseFen(s"8/8/8/8/8/8/8/8 w - ${file}3 0 1") + .fold(_ => fail(), ctx => ctx.enPassantSquare should not be empty) test("parseBoard with mixed pieces and empty squares"): FenParserFastParse.parseBoard("r1bqkb1r/pppppppp/2n2n2/8/8/2N2N2/PPPPPPPP/R1BQKB1R") should not be empty diff --git a/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala b/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala index e44525a..89cad64 100644 --- a/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala +++ b/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala @@ -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"): diff --git a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala index 19ed48f..3536380 100644 --- a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala +++ b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala @@ -266,7 +266,8 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B case Some(piece) => Seq(bgRect) ++ PieceSprites.loadPieceImage(piece, squareSize * 0.8).toSeq case None => - Seq(bgRect)): Seq[scalafx.scene.Node] + Seq(bgRect) + ): Seq[scalafx.scene.Node] } def showMessage(msg: String): Unit = diff --git a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala index b3e7d54..04829df 100644 --- a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala +++ b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala @@ -30,9 +30,9 @@ class ChessGUIApp extends JFXApplication: stage.scene = new Scene { root = boardView // Load CSS if available - try { + try Option(getClass.getResource("/styles.css")).foreach(url => stylesheets.add(url.toExternalForm)) - } catch { + catch { case _: Exception => // CSS is optional } }