style: fix CRLF line endings introduced by rebase
Build & Test (NowChessSystems) TeamCity build failed
Build & Test (NowChessSystems) TeamCity build failed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,6 @@ import de.nowchess.rules.sets.DefaultRules
|
|||||||
object GameMapper:
|
object GameMapper:
|
||||||
private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
|
private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
|
||||||
|
|
||||||
|
|
||||||
def toGameFullJson(session: GameSession): String =
|
def toGameFullJson(session: GameSession): String =
|
||||||
mapper.writeValueAsString(toGameFull(session))
|
mapper.writeValueAsString(toGameFull(session))
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,9 @@ class GameStore:
|
|||||||
case moves =>
|
case moves =>
|
||||||
promotion match
|
promotion match
|
||||||
case Some(pp) => moves.find(_.moveType == MoveType.Promotion(pp))
|
case Some(pp) => moves.find(_.moveType == MoveType.Promotion(pp))
|
||||||
case None => moves.find(m => !m.moveType.isInstanceOf[MoveType.Promotion])
|
case None =>
|
||||||
|
moves
|
||||||
|
.find(m => !m.moveType.isInstanceOf[MoveType.Promotion])
|
||||||
.orElse(moves.headOption)
|
.orElse(moves.headOption)
|
||||||
|
|
||||||
private def detectGameOver(ctx: GameContext): Option[GameResult] =
|
private def detectGameOver(ctx: GameContext): Option[GameResult] =
|
||||||
@@ -242,7 +244,8 @@ class GameStore:
|
|||||||
case (Left(err), _) => Left(err)
|
case (Left(err), _) => Left(err)
|
||||||
case (Right(s), move) =>
|
case (Right(s), move) =>
|
||||||
val legal = DefaultRules.legalMoves(s.context)(move.from)
|
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
|
.orElse(legal.find(m => m.from == move.from && m.to == move.to)) match
|
||||||
case None => Left(s"Illegal move in PGN: $move")
|
case None => Left(s"Illegal move in PGN: $move")
|
||||||
case Some(legalMove) =>
|
case Some(legalMove) =>
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class GameResource @Inject() (store: GameStore):
|
|||||||
store.get(gameId) match
|
store.get(gameId) match
|
||||||
case Some(session) => Response.ok(GameMapper.toGameFull(session)).build()
|
case Some(session) => Response.ok(GameMapper.toGameFull(session)).build()
|
||||||
case None =>
|
case None =>
|
||||||
Response.status(404)
|
Response
|
||||||
|
.status(404)
|
||||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -34,7 +35,8 @@ class GameResource @Inject() (store: GameStore):
|
|||||||
def streamGame(@PathParam("gameId") gameId: String): Response =
|
def streamGame(@PathParam("gameId") gameId: String): Response =
|
||||||
store.get(gameId) match
|
store.get(gameId) match
|
||||||
case None =>
|
case None =>
|
||||||
Response.status(404)
|
Response
|
||||||
|
.status(404)
|
||||||
.`type`(MediaType.APPLICATION_JSON)
|
.`type`(MediaType.APPLICATION_JSON)
|
||||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||||
.build()
|
.build()
|
||||||
@@ -72,7 +74,8 @@ class GameResource @Inject() (store: GameStore):
|
|||||||
def exportFen(@PathParam("gameId") gameId: String): Response =
|
def exportFen(@PathParam("gameId") gameId: String): Response =
|
||||||
store.get(gameId) match
|
store.get(gameId) match
|
||||||
case None =>
|
case None =>
|
||||||
Response.status(404)
|
Response
|
||||||
|
.status(404)
|
||||||
.`type`(MediaType.APPLICATION_JSON)
|
.`type`(MediaType.APPLICATION_JSON)
|
||||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||||
.build()
|
.build()
|
||||||
@@ -86,7 +89,8 @@ class GameResource @Inject() (store: GameStore):
|
|||||||
def exportPgn(@PathParam("gameId") gameId: String): Response =
|
def exportPgn(@PathParam("gameId") gameId: String): Response =
|
||||||
store.get(gameId) match
|
store.get(gameId) match
|
||||||
case None =>
|
case None =>
|
||||||
Response.status(404)
|
Response
|
||||||
|
.status(404)
|
||||||
.`type`(MediaType.APPLICATION_JSON)
|
.`type`(MediaType.APPLICATION_JSON)
|
||||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
+10
-5
@@ -10,7 +10,8 @@ class GameResourceTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def createGameReturns201WithGameId(): Unit =
|
def createGameReturns201WithGameId(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -24,7 +25,8 @@ class GameResourceTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def createGameWithPlayersReturns201(): Unit =
|
def createGameWithPlayersReturns201(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("""{"white":{"id":"p1","displayName":"Alice"},"black":{"id":"p2","displayName":"Bob"}}""")
|
.body("""{"white":{"id":"p1","displayName":"Alice"},"black":{"id":"p2","displayName":"Bob"}}""")
|
||||||
.when()
|
.when()
|
||||||
@@ -36,7 +38,8 @@ class GameResourceTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def getGameReturns200ForExistingGame(): Unit =
|
def getGameReturns200ForExistingGame(): Unit =
|
||||||
val gameId = RestAssured.`given`()
|
val gameId = RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -46,7 +49,8 @@ class GameResourceTest:
|
|||||||
.extract()
|
.extract()
|
||||||
.path[String]("gameId")
|
.path[String]("gameId")
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId")
|
.get(s"/api/board/game/$gameId")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -55,7 +59,8 @@ class GameResourceTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def getGameReturns404ForUnknownId(): Unit =
|
def getGameReturns404ForUnknownId(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get("/api/board/game/XXXXXXXX")
|
.get("/api/board/game/XXXXXXXX")
|
||||||
.`then`()
|
.`then`()
|
||||||
|
|||||||
+28
-14
@@ -14,7 +14,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def importFenReturns201WithCorrectPosition(): Unit =
|
def importFenReturns201WithCorrectPosition(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body(s"""{"fen":"$startFen"}""")
|
.body(s"""{"fen":"$startFen"}""")
|
||||||
.when()
|
.when()
|
||||||
@@ -28,7 +29,8 @@ class ImportExportTest:
|
|||||||
@Test
|
@Test
|
||||||
def importFenWithCustomPositionWorks(): Unit =
|
def importFenWithCustomPositionWorks(): Unit =
|
||||||
val fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
|
val fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body(s"""{"fen":"$fen"}""")
|
.body(s"""{"fen":"$fen"}""")
|
||||||
.when()
|
.when()
|
||||||
@@ -40,7 +42,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def importFenWithInvalidFenReturns400(): Unit =
|
def importFenWithInvalidFenReturns400(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("""{"fen":"not-a-fen"}""")
|
.body("""{"fen":"not-a-fen"}""")
|
||||||
.when()
|
.when()
|
||||||
@@ -52,7 +55,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def importPgnReturns201(): Unit =
|
def importPgnReturns201(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("""{"pgn":"1. e4 e5 2. Nf3 Nc6 *"}""")
|
.body("""{"pgn":"1. e4 e5 2. Nf3 Nc6 *"}""")
|
||||||
.when()
|
.when()
|
||||||
@@ -64,7 +68,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def importPgnWithInvalidPgnReturns400(): Unit =
|
def importPgnWithInvalidPgnReturns400(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("""{"pgn":"1. z9 *"}""")
|
.body("""{"pgn":"1. z9 *"}""")
|
||||||
.when()
|
.when()
|
||||||
@@ -76,7 +81,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def exportFenReturnsStartingFen(): Unit =
|
def exportFenReturnsStartingFen(): Unit =
|
||||||
val gameId = RestAssured.`given`()
|
val gameId = RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -86,7 +92,8 @@ class ImportExportTest:
|
|||||||
.extract()
|
.extract()
|
||||||
.path[String]("gameId")
|
.path[String]("gameId")
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId/export/fen")
|
.get(s"/api/board/game/$gameId/export/fen")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -95,7 +102,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def exportFenOnUnknownGameReturns404(): Unit =
|
def exportFenOnUnknownGameReturns404(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get("/api/board/game/XXXXXXXX/export/fen")
|
.get("/api/board/game/XXXXXXXX/export/fen")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -105,7 +113,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def exportPgnReturnsText(): Unit =
|
def exportPgnReturnsText(): Unit =
|
||||||
val gameId = RestAssured.`given`()
|
val gameId = RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -115,13 +124,15 @@ class ImportExportTest:
|
|||||||
.extract()
|
.extract()
|
||||||
.path[String]("gameId")
|
.path[String]("gameId")
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId/export/pgn")
|
.get(s"/api/board/game/$gameId/export/pgn")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -132,7 +143,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def streamReturnsNdjsonSnapshot(): Unit =
|
def streamReturnsNdjsonSnapshot(): Unit =
|
||||||
val gameId = RestAssured.`given`()
|
val gameId = RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -142,7 +154,8 @@ class ImportExportTest:
|
|||||||
.extract()
|
.extract()
|
||||||
.path[String]("gameId")
|
.path[String]("gameId")
|
||||||
|
|
||||||
val body = RestAssured.`given`()
|
val body = RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId/stream")
|
.get(s"/api/board/game/$gameId/stream")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -156,7 +169,8 @@ class ImportExportTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def streamOnUnknownGameReturns404(): Unit =
|
def streamOnUnknownGameReturns404(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get("/api/board/game/XXXXXXXX/stream")
|
.get("/api/board/game/XXXXXXXX/stream")
|
||||||
.`then`()
|
.`then`()
|
||||||
|
|||||||
+14
-7
@@ -9,7 +9,8 @@ import org.junit.jupiter.api.Test
|
|||||||
class MoveResourceTest:
|
class MoveResourceTest:
|
||||||
|
|
||||||
private def createGame(): String =
|
private def createGame(): String =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -22,7 +23,8 @@ class MoveResourceTest:
|
|||||||
@Test
|
@Test
|
||||||
def makeMoveReturns200WithUpdatedFen(): Unit =
|
def makeMoveReturns200WithUpdatedFen(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -33,7 +35,8 @@ class MoveResourceTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def makeMoveOnUnknownGameReturns404(): Unit =
|
def makeMoveOnUnknownGameReturns404(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post("/api/board/game/XXXXXXXX/move/e2e4")
|
.post("/api/board/game/XXXXXXXX/move/e2e4")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -42,7 +45,8 @@ class MoveResourceTest:
|
|||||||
@Test
|
@Test
|
||||||
def illegalMoveReturns400(): Unit =
|
def illegalMoveReturns400(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.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`()
|
.`then`()
|
||||||
@@ -51,7 +55,8 @@ class MoveResourceTest:
|
|||||||
@Test
|
@Test
|
||||||
def getLegalMovesReturnsNonEmptyList(): Unit =
|
def getLegalMovesReturnsNonEmptyList(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId/moves")
|
.get(s"/api/board/game/$gameId/moves")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -61,7 +66,8 @@ class MoveResourceTest:
|
|||||||
@Test
|
@Test
|
||||||
def getLegalMovesFilteredBySquareReturnsCorrectMoves(): Unit =
|
def getLegalMovesFilteredBySquareReturnsCorrectMoves(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId/moves?square=e2")
|
.get(s"/api/board/game/$gameId/moves?square=e2")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -70,7 +76,8 @@ class MoveResourceTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def getLegalMovesOnUnknownGameReturns404(): Unit =
|
def getLegalMovesOnUnknownGameReturns404(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get("/api/board/game/XXXXXXXX/moves")
|
.get("/api/board/game/XXXXXXXX/moves")
|
||||||
.`then`()
|
.`then`()
|
||||||
|
|||||||
+32
-16
@@ -9,7 +9,8 @@ import org.junit.jupiter.api.Test
|
|||||||
class ResignDrawTest:
|
class ResignDrawTest:
|
||||||
|
|
||||||
private def createGame(): String =
|
private def createGame(): String =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -24,7 +25,8 @@ class ResignDrawTest:
|
|||||||
@Test
|
@Test
|
||||||
def resignReturns200(): Unit =
|
def resignReturns200(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/resign")
|
.post(s"/api/board/game/$gameId/resign")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -34,13 +36,15 @@ class ResignDrawTest:
|
|||||||
@Test
|
@Test
|
||||||
def afterResignGameShowsResignStatusAndWinner(): Unit =
|
def afterResignGameShowsResignStatusAndWinner(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/resign")
|
.post(s"/api/board/game/$gameId/resign")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId")
|
.get(s"/api/board/game/$gameId")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -50,7 +54,8 @@ class ResignDrawTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def resignOnUnknownGameReturns404(): Unit =
|
def resignOnUnknownGameReturns404(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post("/api/board/game/XXXXXXXX/resign")
|
.post("/api/board/game/XXXXXXXX/resign")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -61,14 +66,16 @@ class ResignDrawTest:
|
|||||||
@Test
|
@Test
|
||||||
def offerDrawSetsDrawOfferedStatus(): Unit =
|
def offerDrawSetsDrawOfferedStatus(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/draw/offer")
|
.post(s"/api/board/game/$gameId/draw/offer")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.body("ok", equalTo(true))
|
.body("ok", equalTo(true))
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId")
|
.get(s"/api/board/game/$gameId")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -79,7 +86,8 @@ class ResignDrawTest:
|
|||||||
def acceptDrawAfterOfferSetsDrawStatus(): Unit =
|
def acceptDrawAfterOfferSetsDrawStatus(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
// White offers
|
// White offers
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/draw/offer")
|
.post(s"/api/board/game/$gameId/draw/offer")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -90,21 +98,24 @@ class ResignDrawTest:
|
|||||||
// The GameStore checks drawOfferedBy != turn to allow accept.
|
// 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.
|
// 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.
|
// We need to make a move first to switch turns.
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
// Now it's black's turn and white offered the draw — black accepts
|
// Now it's black's turn and white offered the draw — black accepts
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/draw/accept")
|
.post(s"/api/board/game/$gameId/draw/accept")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.body("ok", equalTo(true))
|
.body("ok", equalTo(true))
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId")
|
.get(s"/api/board/game/$gameId")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -114,20 +125,23 @@ class ResignDrawTest:
|
|||||||
@Test
|
@Test
|
||||||
def declineDrawClearsOffer(): Unit =
|
def declineDrawClearsOffer(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/draw/offer")
|
.post(s"/api/board/game/$gameId/draw/offer")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/draw/decline")
|
.post(s"/api/board/game/$gameId/draw/decline")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.body("ok", equalTo(true))
|
.body("ok", equalTo(true))
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.get(s"/api/board/game/$gameId")
|
.get(s"/api/board/game/$gameId")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -137,7 +151,8 @@ class ResignDrawTest:
|
|||||||
@Test
|
@Test
|
||||||
def acceptWithoutOfferReturns400(): Unit =
|
def acceptWithoutOfferReturns400(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/draw/accept")
|
.post(s"/api/board/game/$gameId/draw/accept")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -145,7 +160,8 @@ class ResignDrawTest:
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
def drawOnUnknownGameReturns404(): Unit =
|
def drawOnUnknownGameReturns404(): Unit =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post("/api/board/game/XXXXXXXX/draw/offer")
|
.post("/api/board/game/XXXXXXXX/draw/offer")
|
||||||
.`then`()
|
.`then`()
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class UndoRedoTest:
|
|||||||
private val initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
private val initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||||
|
|
||||||
private def createGame(): String =
|
private def createGame(): String =
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.when()
|
.when()
|
||||||
@@ -24,13 +25,15 @@ class UndoRedoTest:
|
|||||||
@Test
|
@Test
|
||||||
def undoAfterMoveRestoresOriginalPosition(): Unit =
|
def undoAfterMoveRestoresOriginalPosition(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/undo")
|
.post(s"/api/board/game/$gameId/undo")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -41,19 +44,22 @@ class UndoRedoTest:
|
|||||||
@Test
|
@Test
|
||||||
def redoAfterUndoRestoresMovedPosition(): Unit =
|
def redoAfterUndoRestoresMovedPosition(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/undo")
|
.post(s"/api/board/game/$gameId/undo")
|
||||||
.`then`()
|
.`then`()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
|
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/redo")
|
.post(s"/api/board/game/$gameId/redo")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -64,7 +70,8 @@ class UndoRedoTest:
|
|||||||
@Test
|
@Test
|
||||||
def undoWithNoHistoryReturns400(): Unit =
|
def undoWithNoHistoryReturns400(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/undo")
|
.post(s"/api/board/game/$gameId/undo")
|
||||||
.`then`()
|
.`then`()
|
||||||
@@ -73,7 +80,8 @@ class UndoRedoTest:
|
|||||||
@Test
|
@Test
|
||||||
def redoWithNoRedoStackReturns400(): Unit =
|
def redoWithNoRedoStackReturns400(): Unit =
|
||||||
val gameId = createGame()
|
val gameId = createGame()
|
||||||
RestAssured.`given`()
|
RestAssured
|
||||||
|
.`given`()
|
||||||
.when()
|
.when()
|
||||||
.post(s"/api/board/game/$gameId/redo")
|
.post(s"/api/board/game/$gameId/redo")
|
||||||
.`then`()
|
.`then`()
|
||||||
|
|||||||
+3
-1
@@ -69,7 +69,9 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers:
|
|||||||
|
|
||||||
engine.context shouldBe target
|
engine.context shouldBe target
|
||||||
engine.commandHistory shouldBe empty
|
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"):
|
test("redo event includes captured piece description when replaying a capture"):
|
||||||
val engine = new GameEngine()
|
val engine = new GameEngine()
|
||||||
|
|||||||
@@ -70,58 +70,66 @@ class FenParserFastParseTest extends AnyFunSuite with Matchers:
|
|||||||
FenParserFastParse.parseBoard("8pp/8/8/8/8/8/8/8") shouldBe None
|
FenParserFastParse.parseBoard("8pp/8/8/8/8/8/8/8") shouldBe None
|
||||||
|
|
||||||
test("parseFen handles all individual castling rights"):
|
test("parseFen handles all individual castling rights"):
|
||||||
FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w K - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
|
.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w K - 0 1")
|
||||||
|
.fold(
|
||||||
|
_ => fail(),
|
||||||
|
ctx =>
|
||||||
ctx.castlingRights.whiteKingSide shouldBe true
|
ctx.castlingRights.whiteKingSide shouldBe true
|
||||||
ctx.castlingRights.whiteQueenSide shouldBe false
|
ctx.castlingRights.whiteQueenSide shouldBe false
|
||||||
ctx.castlingRights.blackKingSide shouldBe false
|
ctx.castlingRights.blackKingSide shouldBe false
|
||||||
ctx.castlingRights.blackQueenSide shouldBe false
|
ctx.castlingRights.blackQueenSide shouldBe false,
|
||||||
)
|
)
|
||||||
|
|
||||||
FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Q - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
|
.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Q - 0 1")
|
||||||
|
.fold(
|
||||||
|
_ => fail(),
|
||||||
|
ctx =>
|
||||||
ctx.castlingRights.whiteQueenSide shouldBe true
|
ctx.castlingRights.whiteQueenSide shouldBe true
|
||||||
ctx.castlingRights.whiteKingSide shouldBe false
|
ctx.castlingRights.whiteKingSide shouldBe false,
|
||||||
)
|
)
|
||||||
|
|
||||||
FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w k - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
|
.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w k - 0 1")
|
||||||
|
.fold(
|
||||||
|
_ => fail(),
|
||||||
|
ctx =>
|
||||||
ctx.castlingRights.blackKingSide shouldBe true
|
ctx.castlingRights.blackKingSide shouldBe true
|
||||||
ctx.castlingRights.whiteKingSide shouldBe false
|
ctx.castlingRights.whiteKingSide shouldBe false,
|
||||||
)
|
)
|
||||||
|
|
||||||
FenParserFastParse.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w q - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
|
.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w q - 0 1")
|
||||||
|
.fold(
|
||||||
|
_ => fail(),
|
||||||
|
ctx =>
|
||||||
ctx.castlingRights.blackQueenSide shouldBe true
|
ctx.castlingRights.blackQueenSide shouldBe true
|
||||||
ctx.castlingRights.whiteKingSide shouldBe false
|
ctx.castlingRights.whiteKingSide shouldBe false,
|
||||||
)
|
)
|
||||||
|
|
||||||
test("parseFen parses all en passant squares"):
|
test("parseFen parses all en passant squares"):
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - a3 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
ctx.enPassantSquare shouldBe Some(Square(File.A, Rank.R3))
|
.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 =>
|
FenParserFastParse
|
||||||
ctx.enPassantSquare shouldBe Some(Square(File.H, Rank.R6))
|
.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"):
|
test("parseFen parses different halfMove and fullMove clocks"):
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 5 10").fold(_ => fail(), ctx =>
|
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 5 10").fold(_ => fail(), ctx => ctx.halfMoveClock shouldBe 5)
|
||||||
ctx.halfMoveClock shouldBe 5
|
|
||||||
)
|
|
||||||
|
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 100").fold(_ => fail(), ctx =>
|
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 100").fold(_ => fail(), ctx => ctx.halfMoveClock shouldBe 0)
|
||||||
ctx.halfMoveClock shouldBe 0
|
|
||||||
)
|
|
||||||
|
|
||||||
test("parseBoard parses boards with mixed empty and piece tokens"):
|
test("parseBoard parses boards with mixed empty and piece tokens"):
|
||||||
val mixed = "8/1p1p1p1p/8/1P1P1P1P/8/8/8/8"
|
val mixed = "8/1p1p1p1p/8/1P1P1P1P/8/8/8/8"
|
||||||
FenParserFastParse.parseBoard(mixed) should not be empty
|
FenParserFastParse.parseBoard(mixed) should not be empty
|
||||||
|
|
||||||
test("parseFen handles turn transitions"):
|
test("parseFen handles turn transitions"):
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w - - 0 1").fold(_ => fail(), ctx => ctx.turn shouldBe Color.White)
|
||||||
ctx.turn shouldBe Color.White
|
|
||||||
)
|
|
||||||
|
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 b - - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 b - - 0 1").fold(_ => fail(), ctx => ctx.turn shouldBe Color.Black)
|
||||||
ctx.turn shouldBe Color.Black
|
|
||||||
)
|
|
||||||
|
|
||||||
test("parseFen rejects invalid piece characters"):
|
test("parseFen rejects invalid piece characters"):
|
||||||
FenParserFastParse.parseFen("8x/8/8/8/8/8/8/8 w - - 0 1").isLeft shouldBe true
|
FenParserFastParse.parseFen("8x/8/8/8/8/8/8/8 w - - 0 1").isLeft shouldBe true
|
||||||
@@ -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)
|
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"):
|
test("parseFen tests all castling combinations"):
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w KQkq - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
|
.parseFen("8/8/8/8/8/8/8/8 w KQkq - 0 1")
|
||||||
|
.fold(
|
||||||
|
_ => fail(),
|
||||||
|
ctx =>
|
||||||
ctx.castlingRights.whiteKingSide shouldBe true
|
ctx.castlingRights.whiteKingSide shouldBe true
|
||||||
ctx.castlingRights.whiteQueenSide shouldBe true
|
ctx.castlingRights.whiteQueenSide shouldBe true
|
||||||
ctx.castlingRights.blackKingSide shouldBe true
|
ctx.castlingRights.blackKingSide shouldBe true
|
||||||
ctx.castlingRights.blackQueenSide shouldBe true
|
ctx.castlingRights.blackQueenSide shouldBe true,
|
||||||
)
|
)
|
||||||
|
|
||||||
FenParserFastParse.parseFen("8/8/8/8/8/8/8/8 w Kq - 0 1").fold(_ => fail(), ctx =>
|
FenParserFastParse
|
||||||
|
.parseFen("8/8/8/8/8/8/8/8 w Kq - 0 1")
|
||||||
|
.fold(
|
||||||
|
_ => fail(),
|
||||||
|
ctx =>
|
||||||
ctx.castlingRights.whiteKingSide shouldBe true
|
ctx.castlingRights.whiteKingSide shouldBe true
|
||||||
ctx.castlingRights.whiteQueenSide shouldBe false
|
ctx.castlingRights.whiteQueenSide shouldBe false
|
||||||
ctx.castlingRights.blackKingSide shouldBe false
|
ctx.castlingRights.blackKingSide shouldBe false
|
||||||
ctx.castlingRights.blackQueenSide shouldBe true
|
ctx.castlingRights.blackQueenSide shouldBe true,
|
||||||
)
|
)
|
||||||
|
|
||||||
test("parseFen tests all en passant files"):
|
test("parseFen tests all en passant files"):
|
||||||
for file <- Seq("a", "b", "c", "d", "e", "f", "g", "h") do
|
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 =>
|
FenParserFastParse
|
||||||
ctx.enPassantSquare should not be empty
|
.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"):
|
test("parseBoard with mixed pieces and empty squares"):
|
||||||
FenParserFastParse.parseBoard("r1bqkb1r/pppppppp/2n2n2/8/8/2N2N2/PPPPPPPP/R1BQKB1R") should not be empty
|
FenParserFastParse.parseBoard("r1bqkb1r/pppppppp/2n2n2/8/8/2N2N2/PPPPPPPP/R1BQKB1R") should not be empty
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ class DefaultRulesTest extends AnyFunSuite with Matchers:
|
|||||||
val fen = "8/8/8/3p4/4P3/8/8/8 w - - 0 1"
|
val fen = "8/8/8/3p4/4P3/8/8/8 w - - 0 1"
|
||||||
val context = FenParser.parseFen(fen).fold(_ => fail(), identity)
|
val context = FenParser.parseFen(fen).fold(_ => fail(), identity)
|
||||||
val moves = rules.allLegalMoves(context)
|
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 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
|
captures.exists(m => m.to == Square(File.D, Rank.R5)) shouldBe true
|
||||||
|
|
||||||
test("pawn cannot move backward"):
|
test("pawn cannot move backward"):
|
||||||
|
|||||||
@@ -266,7 +266,8 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B
|
|||||||
case Some(piece) =>
|
case Some(piece) =>
|
||||||
Seq(bgRect) ++ PieceSprites.loadPieceImage(piece, squareSize * 0.8).toSeq
|
Seq(bgRect) ++ PieceSprites.loadPieceImage(piece, squareSize * 0.8).toSeq
|
||||||
case None =>
|
case None =>
|
||||||
Seq(bgRect)): Seq[scalafx.scene.Node]
|
Seq(bgRect)
|
||||||
|
): Seq[scalafx.scene.Node]
|
||||||
}
|
}
|
||||||
|
|
||||||
def showMessage(msg: String): Unit =
|
def showMessage(msg: String): Unit =
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ class ChessGUIApp extends JFXApplication:
|
|||||||
stage.scene = new Scene {
|
stage.scene = new Scene {
|
||||||
root = boardView
|
root = boardView
|
||||||
// Load CSS if available
|
// Load CSS if available
|
||||||
try {
|
try
|
||||||
Option(getClass.getResource("/styles.css")).foreach(url => stylesheets.add(url.toExternalForm))
|
Option(getClass.getResource("/styles.css")).foreach(url => stylesheets.add(url.toExternalForm))
|
||||||
} catch {
|
catch {
|
||||||
case _: Exception => // CSS is optional
|
case _: Exception => // CSS is optional
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user