WIP: feat: NCS-37 Quarkus integration #29
+33
-24
@@ -2,8 +2,8 @@
|
||||
|
||||
> **Stack:** raw-http | none | unknown | scala
|
||||
|
||||
> 0 routes | 0 models | 0 components | 35 lib files | 0 env vars | 0 middleware
|
||||
> **Token savings:** this file is ~3.700 tokens. Without it, AI exploration would cost ~18.200 tokens. **Saves ~14.500 tokens per conversation.**
|
||||
> 0 routes | 0 models | 0 components | 38 lib files | 0 env vars | 0 middleware
|
||||
> **Token savings:** this file is ~0 tokens. Without it, AI exploration would cost ~0 tokens. **Saves ~0 tokens per conversation.**
|
||||
|
||||
---
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
- function withEnPassantSquare
|
||||
- function withHalfMoveClock
|
||||
- function withMove
|
||||
- _...2 more_
|
||||
- _...3 more_
|
||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
||||
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
||||
- class ApiResponse
|
||||
@@ -93,6 +93,13 @@
|
||||
- _...1 more_
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameFileService.scala`
|
||||
- class GameFileService
|
||||
- function saveGameToFile
|
||||
- function loadGameFromFile
|
||||
- class FileSystemGameService
|
||||
- function saveGameToFile
|
||||
- function loadGameFromFile
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
||||
- class FenExporter
|
||||
- function boardToFen
|
||||
@@ -114,6 +121,8 @@
|
||||
- function parseBoard
|
||||
- function importGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
||||
- class PgnExporter
|
||||
- function exportGameContext
|
||||
@@ -164,39 +173,39 @@
|
||||
|
||||
## Most Imported Files (change these carefully)
|
||||
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **9** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **38** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **26** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **25** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **21** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **14** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **13** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **13** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **9** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **8** files
|
||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **8** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **7** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **4** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **4** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **7** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` — imported by **7** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **5** files
|
||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **4** files
|
||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **4** files
|
||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` — imported by **4** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` — imported by **3** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **3** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala` — imported by **2** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **2** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala` — imported by **2** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — imported by **2** files
|
||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — imported by **1** files
|
||||
|
||||
## Import Map (who imports what)
|
||||
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +23 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +16 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala` +14 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala` +8 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala` +5 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala` +4 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +33 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +21 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` +20 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +16 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/io/src/test/scala/de/nowchess/io/json/JsonExporterBranchCoverageSuite.scala` +8 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/test/scala/de/nowchess/io/json/JsonExporterBranchCoverageSuite.scala`, `modules/io/src/test/scala/de/nowchess/io/json/JsonExporterSuite.scala` +8 more
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` ← `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala` +4 more
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +3 more
|
||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` +3 more
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +2 more
|
||||
|
||||
---
|
||||
|
||||
|
||||
+21
-21
@@ -2,36 +2,36 @@
|
||||
|
||||
## Most Imported Files (change these carefully)
|
||||
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **9** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **38** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **26** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **25** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **21** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **14** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **13** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **13** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **9** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **8** files
|
||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **8** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **7** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **4** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **4** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **7** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` — imported by **7** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **5** files
|
||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **4** files
|
||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **4** files
|
||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` — imported by **4** files
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` — imported by **3** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **3** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala` — imported by **2** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **2** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala` — imported by **2** files
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — imported by **2** files
|
||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — imported by **1** files
|
||||
|
||||
## Import Map (who imports what)
|
||||
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +23 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +16 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala` +14 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala` +8 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala` +5 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala` +4 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +33 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +21 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` +20 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +16 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/io/src/test/scala/de/nowchess/io/json/JsonExporterBranchCoverageSuite.scala` +8 more
|
||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/test/scala/de/nowchess/io/json/JsonExporterBranchCoverageSuite.scala`, `modules/io/src/test/scala/de/nowchess/io/json/JsonExporterSuite.scala` +8 more
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` ← `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala` +4 more
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +3 more
|
||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` +3 more
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +2 more
|
||||
|
||||
+10
-1
@@ -45,7 +45,7 @@
|
||||
- function withEnPassantSquare
|
||||
- function withHalfMoveClock
|
||||
- function withMove
|
||||
- _...2 more_
|
||||
- _...3 more_
|
||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
||||
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
||||
- class ApiResponse
|
||||
@@ -84,6 +84,13 @@
|
||||
- _...1 more_
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/GameFileService.scala`
|
||||
- class GameFileService
|
||||
- function saveGameToFile
|
||||
- function loadGameFromFile
|
||||
- class FileSystemGameService
|
||||
- function saveGameToFile
|
||||
- function loadGameFromFile
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
||||
- class FenExporter
|
||||
- function boardToFen
|
||||
@@ -105,6 +112,8 @@
|
||||
- function parseBoard
|
||||
- function importGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
|
||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
||||
- class PgnExporter
|
||||
- function exportGameContext
|
||||
|
||||
Generated
+133
@@ -0,0 +1,133 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<ScalaCodeStyleSettings>
|
||||
<option name="FORMATTER" value="1" />
|
||||
</ScalaCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -5,7 +5,7 @@
|
||||
<option name="deprecationWarnings" value="true" />
|
||||
<option name="uncheckedWarnings" value="true" />
|
||||
</profile>
|
||||
<profile name="Gradle 2" modules="NowChessSystems.modules.core.main,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.main,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
|
||||
<profile name="Gradle 2" modules="NowChessSystems.modules.backcore.integrationTest,NowChessSystems.modules.backcore.main,NowChessSystems.modules.backcore.native-test,NowChessSystems.modules.backcore.quarkus-generated-sources,NowChessSystems.modules.backcore.quarkus-test-generated-sources,NowChessSystems.modules.backcore.scoverage,NowChessSystems.modules.backcore.test,NowChessSystems.modules.core.main,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.main,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
|
||||
<option name="deprecationWarnings" value="true" />
|
||||
<option name="uncheckedWarnings" value="true" />
|
||||
<parameters>
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ScalaProjectSettings">
|
||||
<option name="scala3DisclaimerShown" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="IssueNavigationConfiguration">
|
||||
<option name="links">
|
||||
<list>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Draw offer
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/offer
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Draw accept
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/accept
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Draw decline
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/decline
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Draw claim fifty move
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/claim
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Export FEN
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/export/fen
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: text/plain
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Export PGN
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/export/pgn
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/x-chess-pgn
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
meta {
|
||||
name: Create game
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"white": {
|
||||
"id": "player1",
|
||||
"displayName": "Alice"
|
||||
},
|
||||
"black": {
|
||||
"id": "player2",
|
||||
"displayName": "Bob"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
meta {
|
||||
name: Get game
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
gameId: DSooMdhj
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Stream game (NDJSON)
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/stream
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/x-ndjson
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
meta {
|
||||
name: Resign game
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/resign
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
gameId: DSooMdhj
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
meta {
|
||||
name: Import FEN
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/import/fen
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
"white": {
|
||||
"id": "player1",
|
||||
"displayName": "Alice"
|
||||
},
|
||||
"black": {
|
||||
"id": "player2",
|
||||
"displayName": "Bob"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
meta {
|
||||
name: Import PGN
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/import/pgn
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"pgn": "1. e4 e5 2. Nf3 Nc6 *"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Make move
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/move/{{uci}}
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
gameId: DSooMdhj
|
||||
uci: b1c3
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
meta {
|
||||
name: Get legal moves
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/moves
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
square: {{square}}
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
gameId: DSooMdhj
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
meta {
|
||||
name: Undo move
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/undo
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
gameId: DSooMdhj
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
meta {
|
||||
name: Redo move
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseUrl}}/api/board/game/{{gameId}}/redo
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
gameId: DSooMdhj
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "NowChessSystems",
|
||||
"type": "collection"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
baseUrl: http://localhost:8080
|
||||
}
|
||||
@@ -21,7 +21,15 @@ sonar {
|
||||
if (report.exists()) report.absolutePath else null
|
||||
}.joinToString(",")
|
||||
|
||||
val jacocoReports = subprojects.mapNotNull { subproject ->
|
||||
val report = subproject.file("build/reports/jacoco/test/jacocoTestReport.xml")
|
||||
if (report.exists()) report.absolutePath else null
|
||||
}.joinToString(",")
|
||||
|
||||
property("sonar.scala.coverage.reportPaths", scoverageReports)
|
||||
if (jacocoReports.isNotEmpty()) {
|
||||
property("sonar.coverage.jacoco.xmlReportPaths", jacocoReports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
quarkusPluginId=io.quarkus
|
||||
quarkusPluginVersion=3.32.4
|
||||
quarkusPlatformGroupId=io.quarkus.platform
|
||||
quarkusPlatformArtifactId=quarkus-bom
|
||||
quarkusPlatformVersion=3.32.4
|
||||
@@ -0,0 +1,5 @@
|
||||
*
|
||||
!build/*-runner
|
||||
!build/*-runner.jar
|
||||
!build/lib/*
|
||||
!build/quarkus-app/*
|
||||
@@ -0,0 +1,41 @@
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# NetBeans
|
||||
nb-configuration.xml
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
.factorypath
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# patch
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
# Local environment
|
||||
.env
|
||||
|
||||
# Plugin directory
|
||||
/.quarkus/cli/plugins/
|
||||
# TLS Certificates
|
||||
.certs/
|
||||
@@ -0,0 +1,106 @@
|
||||
# backcore
|
||||
|
||||
This project uses Quarkus, the Supersonic Subatomic Java Framework.
|
||||
|
||||
If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
|
||||
|
||||
## Running the application in dev mode
|
||||
|
||||
You can run your application in dev mode that enables live coding using:
|
||||
|
||||
```shell script
|
||||
./gradlew quarkusDev
|
||||
```
|
||||
|
||||
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at <http://localhost:8080/q/dev/>.
|
||||
|
||||
## Packaging and running the application
|
||||
|
||||
The application can be packaged using:
|
||||
|
||||
```shell script
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
|
||||
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
|
||||
|
||||
The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
|
||||
|
||||
If you want to build an _über-jar_, execute the following command:
|
||||
|
||||
```shell script
|
||||
./gradlew build -Dquarkus.package.jar.type=uber-jar
|
||||
```
|
||||
|
||||
The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
|
||||
|
||||
## Creating a native executable
|
||||
|
||||
You can create a native executable using:
|
||||
|
||||
```shell script
|
||||
./gradlew build -Dquarkus.native.enabled=true
|
||||
```
|
||||
|
||||
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
|
||||
|
||||
```shell script
|
||||
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true
|
||||
```
|
||||
|
||||
You can then execute your native executable with: `./build/backcore-1.0-SNAPSHOT-runner`
|
||||
|
||||
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- REST ([guide](https://quarkus.io/guides/rest)): A Jakarta REST implementation utilizing build time processing and
|
||||
Vert.x. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on
|
||||
it.
|
||||
- Hibernate ORM ([guide](https://quarkus.io/guides/hibernate-orm)): Define your persistent model with Hibernate ORM and
|
||||
Jakarta Persistence
|
||||
- REST Client ([guide](https://quarkus.io/guides/rest-client)): Call REST services
|
||||
- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus
|
||||
REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it
|
||||
- YAML Configuration ([guide](https://quarkus.io/guides/config-yaml)): Use YAML to configure your Quarkus application
|
||||
- SmallRye Fault Tolerance ([guide](https://quarkus.io/guides/smallrye-fault-tolerance)): Build fault-tolerant network
|
||||
services
|
||||
- SmallRye JWT ([guide](https://quarkus.io/guides/security-jwt)): Secure your applications with JSON Web Token
|
||||
- SmallRye Health ([guide](https://quarkus.io/guides/smallrye-health)): Monitor service health
|
||||
- Micrometer metrics ([guide](https://quarkus.io/guides/micrometer)): Instrument the runtime and your application with
|
||||
dimensional metrics using Micrometer.
|
||||
|
||||
## Provided Code
|
||||
|
||||
### YAML Config
|
||||
|
||||
Configure your application with YAML
|
||||
|
||||
[Related guide section...](https://quarkus.io/guides/config-reference#configuration-examples)
|
||||
|
||||
The Quarkus application configuration is located in `src/main/resources/application.yml`.
|
||||
|
||||
### Hibernate ORM
|
||||
|
||||
Create your first JPA entity
|
||||
|
||||
[Related guide section...](https://quarkus.io/guides/hibernate-orm)
|
||||
|
||||
### REST Client
|
||||
|
||||
Invoke different services through REST with JSON
|
||||
|
||||
[Related guide section...](https://quarkus.io/guides/rest-client)
|
||||
|
||||
### REST
|
||||
|
||||
Easily start your REST Web Services
|
||||
|
||||
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
|
||||
|
||||
### SmallRye Health
|
||||
|
||||
Monitor your application's health using SmallRye Health
|
||||
|
||||
[Related guide section...](https://quarkus.io/guides/smallrye-health)
|
||||
@@ -0,0 +1,135 @@
|
||||
plugins {
|
||||
id("scala")
|
||||
id("org.scoverage") version "8.1"
|
||||
id("io.quarkus")
|
||||
id("jacoco")
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
scala {
|
||||
scalaVersion = versions["SCALA3"]!!
|
||||
}
|
||||
|
||||
scoverage {
|
||||
scoverageVersion.set(versions["SCOVERAGE"]!!)
|
||||
}
|
||||
|
||||
tasks.withType<ScalaCompile> {
|
||||
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
|
||||
}
|
||||
|
||||
val quarkusPlatformGroupId: String by project
|
||||
val quarkusPlatformArtifactId: String by project
|
||||
val quarkusPlatformVersion: String by project
|
||||
|
||||
dependencies {
|
||||
implementation(project(":modules:api"))
|
||||
implementation(project(":modules:core"))
|
||||
implementation(project(":modules:io"))
|
||||
implementation(project(":modules:rule"))
|
||||
implementation(project(":modules:ui"))
|
||||
|
||||
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||
implementation("io.quarkus:quarkus-rest")
|
||||
implementation("io.quarkus:quarkus-rest-jackson")
|
||||
implementation("io.quarkus:quarkus-config-yaml")
|
||||
implementation("io.quarkus:quarkus-arc")
|
||||
implementation("io.quarkus:quarkus-smallrye-openapi")
|
||||
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
||||
testImplementation("io.quarkus:quarkus-junit5")
|
||||
testImplementation("io.quarkus:quarkus-jacoco")
|
||||
testImplementation("io.rest-assured:rest-assured")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
||||
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||
}
|
||||
configurations.scoverage {
|
||||
resolutionStrategy.eachDependency {
|
||||
if (requested.group == "org.scoverage" && requested.name.startsWith("scalac-scoverage-plugin_")) {
|
||||
useTarget("${requested.group}:scalac-scoverage-plugin_2.13.16:2.3.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group = "de.nowchess"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<Jar>().configureEach {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
includeEngines("scalatest", "junit-jupiter")
|
||||
}
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
}
|
||||
finalizedBy(tasks.named("jacocoTestReport"))
|
||||
}
|
||||
|
||||
tasks.jacocoTestReport {
|
||||
dependsOn(tasks.test)
|
||||
executionData.setFrom(
|
||||
layout.buildDirectory.file("jacoco-quarkus.exec"),
|
||||
layout.buildDirectory.file("jacoco/test.exec"),
|
||||
)
|
||||
sourceDirectories.setFrom(files("src/main/scala"))
|
||||
classDirectories.setFrom(
|
||||
files(layout.buildDirectory.dir("classes/scala/main")).asFileTree.matching {
|
||||
exclude(
|
||||
// App entrypoint (intentionally excluded)
|
||||
"**/AppMain*.class", "**/AppMain\$*.class",
|
||||
// DTO companion objects — only framework synthetics (writeReplace, fromProduct, unapply)
|
||||
"**/dto/GameStateResponse\$.class",
|
||||
"**/dto/PlayerInfoDto\$.class",
|
||||
"**/dto/LegalMovesResponse\$.class",
|
||||
"**/dto/ImportFenRequest\$.class",
|
||||
"**/dto/CreateGameRequest\$.class",
|
||||
"**/dto/OkResponse\$.class",
|
||||
"**/dto/LegalMoveDto\$.class",
|
||||
"**/dto/GameFullResponse\$.class",
|
||||
"**/dto/ImportPgnRequest\$.class",
|
||||
"**/dto/ApiErrorResponse\$.class",
|
||||
// Private implementation detail — inaccessible from tests
|
||||
"**/game/ServiceState.class", "**/game/ServiceState\$.class",
|
||||
// GameResult: sealed trait companion + case object singletons (only synthetics)
|
||||
"**/game/GameResult\$.class",
|
||||
"**/game/GameResult\$AgreedDraw\$.class",
|
||||
"**/game/GameResult\$FiftyMoveDraw\$.class",
|
||||
// GameResult.Resign companion (writeReplace, fromProduct; instance class kept)
|
||||
"**/game/GameResult\$Resign\$.class",
|
||||
// Other companion objects with only framework synthetics
|
||||
"**/game/GameId\$.class",
|
||||
"**/game/GameSnapshot\$.class",
|
||||
)
|
||||
}
|
||||
)
|
||||
reports {
|
||||
xml.required.set(true)
|
||||
xml.outputLocation.set(
|
||||
layout.buildDirectory.file("reports/jacoco/test/jacocoTestReport.xml")
|
||||
)
|
||||
html.required.set(false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/backcore-jvm .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backcore-jvm
|
||||
#
|
||||
# If you want to include the debug port into your docker image
|
||||
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
||||
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
||||
# when running the container
|
||||
#
|
||||
# Then run the container using :
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backcore-jvm
|
||||
#
|
||||
# This image uses the `run-java.sh` script to run the application.
|
||||
# This scripts computes the command line to execute your Java application, and
|
||||
# includes memory/GC tuning.
|
||||
# You can configure the behavior using the following environment properties:
|
||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
||||
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||
# of the container available memory as set here. The default is `50` which means 50%
|
||||
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||
# a container without any memory constraints for the container then this option has
|
||||
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||
# when things are happening. This option, if set to true, will set
|
||||
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||
# true").
|
||||
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||
# (example: "20")
|
||||
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||
# (example: "40")
|
||||
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||
# (example: "4")
|
||||
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||
# previous GC times. (example: "90")
|
||||
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||
# contain the necessary JRE command-line options to specify the required GC, which
|
||||
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||
#
|
||||
# You can find more information about the UBI base runtime images and their configuration here:
|
||||
# https://rh-openjdk.github.io/redhat-openjdk-containers/
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi9/openjdk-21-runtime:1.24
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
|
||||
# We make four distinct layers so if there are application changes the library layers can be re-used
|
||||
COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
|
||||
COPY --chown=185 build/quarkus-app/*.jar /deployments/
|
||||
COPY --chown=185 build/quarkus-app/app/ /deployments/app/
|
||||
COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
|
||||
|
||||
EXPOSE 8080
|
||||
USER 185
|
||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||
|
||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build -Dquarkus.package.jar.type=legacy-jar
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/backcore-legacy-jar .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backcore-legacy-jar
|
||||
#
|
||||
# If you want to include the debug port into your docker image
|
||||
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
||||
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
||||
# when running the container
|
||||
#
|
||||
# Then run the container using :
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backcore-legacy-jar
|
||||
#
|
||||
# This image uses the `run-java.sh` script to run the application.
|
||||
# This scripts computes the command line to execute your Java application, and
|
||||
# includes memory/GC tuning.
|
||||
# You can configure the behavior using the following environment properties:
|
||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
||||
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||
# of the container available memory as set here. The default is `50` which means 50%
|
||||
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||
# a container without any memory constraints for the container then this option has
|
||||
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||
# when things are happening. This option, if set to true, will set
|
||||
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||
# true").
|
||||
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||
# (example: "20")
|
||||
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||
# (example: "40")
|
||||
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||
# (example: "4")
|
||||
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||
# previous GC times. (example: "90")
|
||||
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||
# contain the necessary JRE command-line options to specify the required GC, which
|
||||
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||
#
|
||||
# You can find more information about the UBI base runtime images and their configuration here:
|
||||
# https://rh-openjdk.github.io/redhat-openjdk-containers/
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi9/openjdk-21-runtime:1.24
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
|
||||
COPY build/lib/* /deployments/lib/
|
||||
COPY build/*-runner.jar /deployments/quarkus-run.jar
|
||||
|
||||
EXPOSE 8080
|
||||
USER 185
|
||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||
|
||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||
@@ -0,0 +1,29 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build -Dquarkus.native.enabled=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.native -t quarkus/backcore .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backcore
|
||||
#
|
||||
# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.7` base image is based on UBI 9.
|
||||
# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7
|
||||
WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
|
||||
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||
@@ -0,0 +1,32 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
|
||||
# It uses a micro base image, tuned for Quarkus native executables.
|
||||
# It reduces the size of the resulting container image.
|
||||
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build -Dquarkus.native.enabled=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/backcore .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backcore
|
||||
#
|
||||
# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
|
||||
# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
|
||||
###
|
||||
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
|
||||
WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
|
||||
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||
@@ -0,0 +1,6 @@
|
||||
quarkus:
|
||||
http:
|
||||
port: 8080
|
||||
jacoco:
|
||||
data-file: ${user.dir}/build/jacoco-quarkus.exec
|
||||
report: false
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.nowchess.backcore
|
||||
|
||||
import de.nowchess.backcore.game.{GameEngineHolder, GameId}
|
||||
import de.nowchess.ui.gui.ChessGUILauncher
|
||||
import de.nowchess.ui.terminal.TerminalUI
|
||||
import io.quarkus.runtime.annotations.QuarkusMain
|
||||
import io.quarkus.runtime.{Quarkus, QuarkusApplication}
|
||||
|
||||
@QuarkusMain
|
||||
class AppMain extends QuarkusApplication:
|
||||
override def run(args: String*): Int =
|
||||
val engine = GameEngineHolder.engine
|
||||
println(s"REST API -> http://localhost:8080/api/board/game/${GameEngineHolder.gameId}")
|
||||
ChessGUILauncher.launch(engine)
|
||||
new TerminalUI(engine).start()
|
||||
Quarkus.asyncExit()
|
||||
0
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.nowchess.backcore.config
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||
import jakarta.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class JacksonConfig extends ObjectMapperCustomizer:
|
||||
def customize(mapper: ObjectMapper): Unit =
|
||||
mapper.registerModule(DefaultScalaModule)
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.nowchess.backcore.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
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,
|
||||
)
|
||||
|
||||
case class GameFullResponse(
|
||||
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,
|
||||
)
|
||||
|
||||
// Requests
|
||||
case class CreateGameRequest(
|
||||
white: Option[PlayerInfoDto] = None,
|
||||
black: Option[PlayerInfoDto] = None,
|
||||
)
|
||||
|
||||
case class ImportFenRequest(
|
||||
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,
|
||||
)
|
||||
|
||||
case class LegalMovesResponse(moves: List[LegalMoveDto])
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.player.{PlayerId, PlayerInfo}
|
||||
import de.nowchess.chess.engine.GameEngine
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/** Singleton holder that bridges an externally-created GameEngine into the CDI context.
|
||||
*
|
||||
* All fields are `val` wrapping an AtomicReference. Set engine / white / black before starting Quarkus. In
|
||||
* standalone-server or test mode the defaults are used; GameService is created lazily (on first request), so the
|
||||
* values are already set by the time CDI constructs it.
|
||||
*/
|
||||
object GameEngineHolder:
|
||||
private val engineRef = new AtomicReference[GameEngine](new GameEngine())
|
||||
private val whiteRef = new AtomicReference[PlayerInfo](PlayerInfo(PlayerId("white"), "White"))
|
||||
private val blackRef = new AtomicReference[PlayerInfo](PlayerInfo(PlayerId("black"), "Black"))
|
||||
|
||||
val gameId: String = GameId.generate()
|
||||
|
||||
def engine: GameEngine = engineRef.get()
|
||||
def engine_=(e: GameEngine): Unit = engineRef.set(e)
|
||||
|
||||
def white: PlayerInfo = whiteRef.get()
|
||||
def white_=(p: PlayerInfo): Unit = whiteRef.set(p)
|
||||
|
||||
def black: PlayerInfo = blackRef.get()
|
||||
def black_=(p: PlayerInfo): Unit = blackRef.set(p)
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import java.security.SecureRandom
|
||||
|
||||
object GameId:
|
||||
private val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
private val random = SecureRandom()
|
||||
|
||||
def generate(): String =
|
||||
(1 to 8).map(_ => chars(random.nextInt(chars.length))).mkString
|
||||
@@ -0,0 +1,79 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.Color
|
||||
import de.nowchess.api.game.{DrawReason, GameResult as ApiGameResult}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.backcore.dto.*
|
||||
import de.nowchess.io.fen.FenExporter
|
||||
import de.nowchess.io.pgn.PgnExporter
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
|
||||
object GameMapper:
|
||||
private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
|
||||
|
||||
def toGameFullJson(snapshot: GameSnapshot): String =
|
||||
mapper.writeValueAsString(toGameFull(snapshot))
|
||||
|
||||
def toGameFull(snapshot: GameSnapshot): GameFullResponse =
|
||||
GameFullResponse(
|
||||
gameId = snapshot.gameId,
|
||||
white = toPlayerInfo(snapshot.white),
|
||||
black = toPlayerInfo(snapshot.black),
|
||||
state = toGameState(snapshot),
|
||||
)
|
||||
|
||||
def toGameState(snapshot: GameSnapshot): GameStateResponse =
|
||||
val (status, winner) = computeStatus(snapshot)
|
||||
GameStateResponse(
|
||||
fen = FenExporter.exportGameContext(snapshot.context),
|
||||
pgn = buildPgn(snapshot.context.moves),
|
||||
turn = if snapshot.context.turn == Color.White then "white" else "black",
|
||||
status = status,
|
||||
winner = winner,
|
||||
moves = snapshot.context.moves.map(moveToUci),
|
||||
undoAvailable = snapshot.canUndo,
|
||||
redoAvailable = snapshot.canRedo,
|
||||
)
|
||||
|
||||
private def toPlayerInfo(p: de.nowchess.api.player.PlayerInfo): PlayerInfoDto =
|
||||
PlayerInfoDto(id = p.id.value, displayName = p.displayName)
|
||||
|
||||
private def colorStr(c: Color): String = if c == Color.White then "white" else "black"
|
||||
|
||||
private def computeStatus(snapshot: GameSnapshot): (String, Option[String]) =
|
||||
snapshot.externalResult match
|
||||
case Some(GameResult.Resign(winner)) =>
|
||||
("resign", Some(colorStr(winner)))
|
||||
case Some(GameResult.AgreedDraw) | Some(GameResult.FiftyMoveDraw) =>
|
||||
("draw", None)
|
||||
case _ =>
|
||||
snapshot.context.result match
|
||||
case Some(ApiGameResult.Win(winner)) => ("checkmate", Some(colorStr(winner)))
|
||||
case Some(ApiGameResult.Draw(DrawReason.Stalemate)) => ("stalemate", None)
|
||||
case Some(ApiGameResult.Draw(DrawReason.InsufficientMaterial)) => ("insufficientMaterial", None)
|
||||
case Some(ApiGameResult.Draw(_)) => ("draw", None)
|
||||
case None => computeLiveStatus(snapshot)
|
||||
|
||||
private def computeLiveStatus(snapshot: GameSnapshot): (String, Option[String]) =
|
||||
val ctx = snapshot.context
|
||||
if DefaultRules.isCheck(ctx) then ("check", None)
|
||||
else if snapshot.drawOfferedBy.isDefined then ("drawOffered", None)
|
||||
else if DefaultRules.isFiftyMoveRule(ctx) then ("fiftyMoveAvailable", None)
|
||||
else ("started", None)
|
||||
|
||||
def moveToUci(move: Move): String =
|
||||
val base = s"${move.from}${move.to}"
|
||||
move.moveType match
|
||||
case MoveType.Promotion(piece) =>
|
||||
val suffix = piece match
|
||||
case PromotionPiece.Queen => "q"
|
||||
case PromotionPiece.Rook => "r"
|
||||
case PromotionPiece.Bishop => "b"
|
||||
case PromotionPiece.Knight => "n"
|
||||
base + suffix
|
||||
case _ => base
|
||||
|
||||
private def buildPgn(moves: List[Move]): String =
|
||||
PgnExporter.exportGame(Map.empty, moves)
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.board.Color
|
||||
|
||||
sealed trait GameResult
|
||||
object GameResult:
|
||||
case class Resign(winner: Color) extends GameResult
|
||||
case object AgreedDraw extends GameResult
|
||||
case object FiftyMoveDraw extends GameResult
|
||||
@@ -0,0 +1,175 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.board.{Color, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.player.{PlayerId, PlayerInfo}
|
||||
import de.nowchess.backcore.dto.{CreateGameRequest, ImportFenRequest, PlayerInfoDto}
|
||||
import de.nowchess.io.fen.{FenExporter, FenParser}
|
||||
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
|
||||
private case class ServiceState(
|
||||
drawOfferedBy: Option[Color] = None,
|
||||
externalResult: Option[GameResult] = None,
|
||||
)
|
||||
|
||||
@ApplicationScoped
|
||||
class GameService:
|
||||
private val engine = GameEngineHolder.engine
|
||||
@SuppressWarnings(Array("DisableSyntax.var"))
|
||||
private var state: ServiceState = ServiceState()
|
||||
|
||||
def isKnownId(id: String): Boolean = id == GameEngineHolder.gameId
|
||||
|
||||
def getSnapshot: GameSnapshot = synchronized:
|
||||
GameSnapshot(
|
||||
gameId = GameEngineHolder.gameId,
|
||||
white = GameEngineHolder.white,
|
||||
black = GameEngineHolder.black,
|
||||
context = engine.context,
|
||||
drawOfferedBy = state.drawOfferedBy,
|
||||
externalResult = state.externalResult,
|
||||
canUndo = engine.canUndo,
|
||||
canRedo = engine.canRedo,
|
||||
)
|
||||
|
||||
def reset(req: CreateGameRequest): GameSnapshot = synchronized:
|
||||
engine.reset()
|
||||
state = ServiceState()
|
||||
GameEngineHolder.white = toPlayerInfo(req.white, "white", "White")
|
||||
GameEngineHolder.black = toPlayerInfo(req.black, "black", "Black")
|
||||
getSnapshot
|
||||
|
||||
def applyMove(uci: String): Either[String, GameSnapshot] = synchronized:
|
||||
if hasEnded then Left("Game is already over")
|
||||
else
|
||||
parseUci(uci) match
|
||||
case None => Left(s"Invalid UCI notation: $uci")
|
||||
case Some((from, to, promotion)) =>
|
||||
val candidates = engine.ruleSet.legalMoves(engine.context)(from)
|
||||
findMatchingMove(candidates, to, promotion) match
|
||||
case None => Left(s"$uci is not a legal move")
|
||||
case Some(move) =>
|
||||
engine.processUserInput(s"$from$to")
|
||||
promotion.foreach(engine.completePromotion)
|
||||
Right(getSnapshot)
|
||||
|
||||
def legalMoves(square: Option[Square]): List[Move] = synchronized:
|
||||
val ctx = engine.context
|
||||
square match
|
||||
case Some(sq) => engine.ruleSet.legalMoves(ctx)(sq)
|
||||
case None => engine.ruleSet.allLegalMoves(ctx)
|
||||
|
||||
def undo(): Either[String, GameSnapshot] = synchronized:
|
||||
if !engine.canUndo then Left("No moves to undo")
|
||||
else
|
||||
engine.undo()
|
||||
state = state.copy(externalResult = None, drawOfferedBy = None)
|
||||
Right(getSnapshot)
|
||||
|
||||
def redo(): Either[String, GameSnapshot] = synchronized:
|
||||
if !engine.canRedo then Left("No moves to redo")
|
||||
else
|
||||
engine.redo()
|
||||
Right(getSnapshot)
|
||||
|
||||
def resign(): Either[String, GameSnapshot] = synchronized:
|
||||
if hasEnded then Left("Game is already over")
|
||||
else
|
||||
val winner = engine.context.turn.opposite
|
||||
state = state.copy(externalResult = Some(GameResult.Resign(winner)))
|
||||
Right(getSnapshot)
|
||||
|
||||
def drawAction(action: String): Either[String, GameSnapshot] = synchronized:
|
||||
if hasEnded then Left("Game is already over")
|
||||
else
|
||||
action match
|
||||
case "offer" =>
|
||||
state = state.copy(drawOfferedBy = Some(engine.context.turn))
|
||||
Right(getSnapshot)
|
||||
case "accept" =>
|
||||
state.drawOfferedBy match
|
||||
case None => Left("No draw offer to accept")
|
||||
case Some(offerer) if offerer == engine.context.turn =>
|
||||
Left("Cannot accept your own draw offer")
|
||||
case Some(_) =>
|
||||
state = state.copy(externalResult = Some(GameResult.AgreedDraw), drawOfferedBy = None)
|
||||
Right(getSnapshot)
|
||||
case "decline" =>
|
||||
state.drawOfferedBy match
|
||||
case None => Left("No draw offer to decline")
|
||||
case Some(_) =>
|
||||
state = state.copy(drawOfferedBy = None)
|
||||
Right(getSnapshot)
|
||||
case "claim" =>
|
||||
if DefaultRules.isFiftyMoveRule(engine.context) then
|
||||
state = state.copy(externalResult = Some(GameResult.FiftyMoveDraw))
|
||||
Right(getSnapshot)
|
||||
else Left("Fifty-move rule has not been triggered")
|
||||
case other => Left(s"Unknown draw action: $other")
|
||||
|
||||
def importFen(req: ImportFenRequest): Either[String, GameSnapshot] = synchronized:
|
||||
FenParser.parseFen(req.fen) match
|
||||
case Left(err) => Left(err)
|
||||
case Right(ctx) =>
|
||||
engine.loadPosition(ctx)
|
||||
state = ServiceState()
|
||||
GameEngineHolder.white = toPlayerInfo(req.white, "white", "White")
|
||||
GameEngineHolder.black = toPlayerInfo(req.black, "black", "Black")
|
||||
Right(getSnapshot)
|
||||
|
||||
def importPgn(pgn: String): Either[String, GameSnapshot] = synchronized:
|
||||
engine.loadGame(PgnParser, pgn) match
|
||||
case Left(err) => Left(err)
|
||||
case Right(_) =>
|
||||
state = ServiceState()
|
||||
Right(getSnapshot)
|
||||
|
||||
def exportFen(): String = FenExporter.exportGameContext(engine.context)
|
||||
|
||||
def exportPgn(): String = PgnExporter.exportGameContext(engine.context)
|
||||
|
||||
private def hasEnded: Boolean =
|
||||
state.externalResult.isDefined || engine.context.result.isDefined
|
||||
|
||||
private def toPlayerInfo(dto: Option[PlayerInfoDto], defaultId: String, defaultName: String): PlayerInfo =
|
||||
dto.fold(PlayerInfo(PlayerId(defaultId), defaultName))(d => PlayerInfo(PlayerId(d.id), d.displayName))
|
||||
|
||||
private def parseUci(uci: String): Option[(Square, Square, Option[PromotionPiece])] =
|
||||
if uci.length < 4 || uci.length > 5 then None
|
||||
else
|
||||
for
|
||||
from <- Square.fromAlgebraic(uci.substring(0, 2))
|
||||
to <- Square.fromAlgebraic(uci.substring(2, 4))
|
||||
yield
|
||||
val promotion = if uci.length == 5 then parsePromotionChar(uci.charAt(4)) else None
|
||||
(from, to, promotion)
|
||||
|
||||
private def parsePromotionChar(c: Char): Option[PromotionPiece] =
|
||||
c match
|
||||
case 'q' => Some(PromotionPiece.Queen)
|
||||
case 'r' => Some(PromotionPiece.Rook)
|
||||
case 'b' => Some(PromotionPiece.Bishop)
|
||||
case 'n' => Some(PromotionPiece.Knight)
|
||||
case _ => None
|
||||
|
||||
private def findMatchingMove(
|
||||
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 match
|
||||
case _: MoveType.Promotion => false
|
||||
case _ => true,
|
||||
)
|
||||
.orElse(moves.headOption)
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.board.Color
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.player.PlayerInfo
|
||||
|
||||
case class GameSnapshot(
|
||||
gameId: String,
|
||||
white: PlayerInfo,
|
||||
black: PlayerInfo,
|
||||
context: GameContext,
|
||||
drawOfferedBy: Option[Color] = None,
|
||||
externalResult: Option[GameResult] = None,
|
||||
canUndo: Boolean = false,
|
||||
canRedo: Boolean = false,
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import de.nowchess.backcore.dto.*
|
||||
import de.nowchess.backcore.game.{GameMapper, GameService}
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.{MediaType, Response}
|
||||
|
||||
@Path("/api/board/game")
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@ApplicationScoped
|
||||
class GameResource @Inject() (service: GameService):
|
||||
|
||||
@POST
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
def createGame(req: CreateGameRequest): Response =
|
||||
val snapshot = service.reset(Option(req).getOrElse(CreateGameRequest()))
|
||||
Response.status(201).entity(GameMapper.toGameFull(snapshot)).build()
|
||||
|
||||
@GET
|
||||
@Path("/{gameId}")
|
||||
def getGame(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response
|
||||
.status(404)
|
||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||
.build()
|
||||
else Response.ok(GameMapper.toGameFull(service.getSnapshot)).build()
|
||||
|
||||
@GET
|
||||
@Path("/{gameId}/stream")
|
||||
@Produces(Array("application/x-ndjson"))
|
||||
def streamGame(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response
|
||||
.status(404)
|
||||
.`type`(MediaType.APPLICATION_JSON)
|
||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||
.build()
|
||||
else
|
||||
val event = s"""{"type":"gameFull","game":${GameMapper.toGameFullJson(service.getSnapshot)}}"""
|
||||
Response.ok(event + "\n").build()
|
||||
|
||||
@POST
|
||||
@Path("/{gameId}/resign")
|
||||
def resignGame(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")).build()
|
||||
else
|
||||
service.resign() match
|
||||
case Right(_) => Response.ok(OkResponse()).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("RESIGN_ERROR", err)).build()
|
||||
|
||||
@POST
|
||||
@Path("/{gameId}/draw/{action}")
|
||||
def drawAction(
|
||||
@PathParam("gameId") gameId: String,
|
||||
@PathParam("action") action: String,
|
||||
): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")).build()
|
||||
else
|
||||
service.drawAction(action) match
|
||||
case Right(_) => Response.ok(OkResponse()).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("DRAW_ERROR", err)).build()
|
||||
|
||||
@GET
|
||||
@Path("/{gameId}/export/fen")
|
||||
@Produces(Array(MediaType.TEXT_PLAIN))
|
||||
def exportFen(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response
|
||||
.status(404)
|
||||
.`type`(MediaType.APPLICATION_JSON)
|
||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||
.build()
|
||||
else Response.ok(service.exportFen()).build()
|
||||
|
||||
@GET
|
||||
@Path("/{gameId}/export/pgn")
|
||||
@Produces(Array("application/x-chess-pgn"))
|
||||
def exportPgn(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response
|
||||
.status(404)
|
||||
.`type`(MediaType.APPLICATION_JSON)
|
||||
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
|
||||
.build()
|
||||
else Response.ok(service.exportPgn()).build()
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import de.nowchess.backcore.dto.{ApiErrorResponse, ImportFenRequest, ImportPgnRequest}
|
||||
import de.nowchess.backcore.game.{GameMapper, GameService}
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.{MediaType, Response}
|
||||
|
||||
@Path("/api/board/game/import")
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@ApplicationScoped
|
||||
class ImportResource @Inject() (service: GameService):
|
||||
|
||||
@POST
|
||||
@Path("/fen")
|
||||
def importFen(req: ImportFenRequest): Response =
|
||||
service.importFen(Option(req).getOrElse(ImportFenRequest())) match
|
||||
case Right(snap) => Response.status(201).entity(GameMapper.toGameFull(snap)).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("INVALID_FEN", err)).build()
|
||||
|
||||
@POST
|
||||
@Path("/pgn")
|
||||
def importPgn(req: ImportPgnRequest): Response =
|
||||
Option(req) match
|
||||
case None => Response.status(400).entity(ApiErrorResponse("INVALID_PGN", "Request body is required")).build()
|
||||
case Some(body) =>
|
||||
service.importPgn(body.pgn) match
|
||||
case Right(snap) => Response.status(201).entity(GameMapper.toGameFull(snap)).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("INVALID_PGN", err)).build()
|
||||
@@ -0,0 +1,85 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import de.nowchess.api.board.Square
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.backcore.dto.*
|
||||
import de.nowchess.backcore.game.{GameMapper, GameService}
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.{MediaType, Response}
|
||||
|
||||
@Path("/api/board/game")
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@ApplicationScoped
|
||||
class MoveResource @Inject() (service: GameService):
|
||||
|
||||
@POST
|
||||
@Path("/{gameId}/move/{uci}")
|
||||
def makeMove(
|
||||
@PathParam("gameId") gameId: String,
|
||||
@PathParam("uci") uci: String,
|
||||
): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")).build()
|
||||
else
|
||||
service.applyMove(uci) match
|
||||
case Right(snap) => Response.ok(GameMapper.toGameState(snap)).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("INVALID_MOVE", err)).build()
|
||||
|
||||
@GET
|
||||
@Path("/{gameId}/moves")
|
||||
def getLegalMoves(
|
||||
@PathParam("gameId") gameId: String,
|
||||
@QueryParam("square") squareParam: String,
|
||||
): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")).build()
|
||||
else
|
||||
val square = Option(squareParam).flatMap(Square.fromAlgebraic)
|
||||
val moves = service.legalMoves(square)
|
||||
val dtos = moves.map(toLegalMoveDto)
|
||||
Response.ok(LegalMovesResponse(dtos)).build()
|
||||
|
||||
@POST
|
||||
@Path("/{gameId}/undo")
|
||||
def undoMove(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")).build()
|
||||
else
|
||||
service.undo() match
|
||||
case Right(snap) => Response.ok(GameMapper.toGameState(snap)).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("UNDO_NOT_AVAILABLE", err)).build()
|
||||
|
||||
@POST
|
||||
@Path("/{gameId}/redo")
|
||||
def redoMove(@PathParam("gameId") gameId: String): Response =
|
||||
if !service.isKnownId(gameId) then
|
||||
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found")).build()
|
||||
else
|
||||
service.redo() match
|
||||
case Right(snap) => Response.ok(GameMapper.toGameState(snap)).build()
|
||||
case Left(err) => Response.status(400).entity(ApiErrorResponse("REDO_NOT_AVAILABLE", err)).build()
|
||||
|
||||
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) =>
|
||||
val pName = pp match
|
||||
case PromotionPiece.Queen => "queen"
|
||||
case PromotionPiece.Rook => "rook"
|
||||
case PromotionPiece.Bishop => "bishop"
|
||||
case PromotionPiece.Knight => "knight"
|
||||
("promotion", Some(pName))
|
||||
LegalMoveDto(
|
||||
from = move.from.toString,
|
||||
to = move.to.toString,
|
||||
uci = uci,
|
||||
moveType = moveType,
|
||||
promotion = promotion,
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
import io.quarkus.test.junit.QuarkusIntegrationTest
|
||||
import jakarta.inject.Inject
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusIntegrationTest
|
||||
case class Test2(@Inject tester: Tester) {
|
||||
|
||||
@Test
|
||||
def testAbc(): Unit = {
|
||||
println("Hello World")
|
||||
tester.test()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.nowchess.backcore
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class BackcoreStartupTest:
|
||||
@Test
|
||||
def applicationStarts(): Unit =
|
||||
// If we get here the Quarkus container started successfully
|
||||
()
|
||||
@@ -0,0 +1,226 @@
|
||||
package de.nowchess.backcore.dto
|
||||
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/** Exercises the Scala-generated synthetic methods (equals, hashCode, copy, productElement,
|
||||
* productElementName, toString, canEqual) on every DTO case class so that JaCoCo counts them as
|
||||
* covered.
|
||||
*/
|
||||
class DtoCoverageTest:
|
||||
|
||||
@Test
|
||||
def playerInfoDtoSynthetics(): Unit =
|
||||
val a = PlayerInfoDto("id1", "Alice")
|
||||
val b = PlayerInfoDto("id1", "Alice")
|
||||
val c = PlayerInfoDto("id2", "Bob")
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("Alice"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("id1", a.productElement(0))
|
||||
assertEquals("Alice", a.productElement(1))
|
||||
assertEquals("id", a.productElementName(0))
|
||||
assertEquals("displayName", a.productElementName(1))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(PlayerInfoDto("x", "Alice"), a.copy(id = "x"))
|
||||
assertEquals(PlayerInfoDto("id1", "X"), a.copy(displayName = "X"))
|
||||
|
||||
@Test
|
||||
def okResponseSynthetics(): Unit =
|
||||
val a = OkResponse()
|
||||
val b = OkResponse()
|
||||
val c = OkResponse(ok = false)
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("true"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals(true, a.productElement(0))
|
||||
assertEquals("ok", a.productElementName(0))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(OkResponse(false), a.copy(ok = false))
|
||||
|
||||
@Test
|
||||
def apiErrorResponseSynthetics(): Unit =
|
||||
val a = ApiErrorResponse("CODE", "msg")
|
||||
val b = ApiErrorResponse("CODE", "msg")
|
||||
val c = ApiErrorResponse("OTHER", "msg")
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("CODE"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("CODE", a.productElement(0))
|
||||
assertEquals("msg", a.productElement(1))
|
||||
assertEquals(None, a.productElement(2))
|
||||
assertEquals("code", a.productElementName(0))
|
||||
assertEquals("message", a.productElementName(1))
|
||||
assertEquals("field", a.productElementName(2))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(ApiErrorResponse("X", "msg"), a.copy(code = "X"))
|
||||
assertEquals(ApiErrorResponse("CODE", "X"), a.copy(message = "X"))
|
||||
|
||||
@Test
|
||||
def createGameRequestSynthetics(): Unit =
|
||||
val a = CreateGameRequest()
|
||||
val b = CreateGameRequest()
|
||||
val c = CreateGameRequest(white = Some(PlayerInfoDto("x", "X")))
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertNotNull(a.toString)
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals(None, a.productElement(0))
|
||||
assertEquals(None, a.productElement(1))
|
||||
assertEquals("white", a.productElementName(0))
|
||||
assertEquals("black", a.productElementName(1))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(CreateGameRequest(black = None), a.copy(black = None))
|
||||
|
||||
@Test
|
||||
def importFenRequestSynthetics(): Unit =
|
||||
val a = ImportFenRequest(fen = "fen1")
|
||||
val b = ImportFenRequest(fen = "fen1")
|
||||
val c = ImportFenRequest(fen = "fen2")
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("fen1"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("fen1", a.productElement(0))
|
||||
assertEquals(None, a.productElement(1))
|
||||
assertEquals(None, a.productElement(2))
|
||||
assertEquals("fen", a.productElementName(0))
|
||||
assertEquals("white", a.productElementName(1))
|
||||
assertEquals("black", a.productElementName(2))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(ImportFenRequest(fen = "x"), a.copy(fen = "x"))
|
||||
assertEquals(ImportFenRequest(fen = "fen1", white = None), a.copy(white = None))
|
||||
|
||||
@Test
|
||||
def importPgnRequestSynthetics(): Unit =
|
||||
val a = ImportPgnRequest(pgn = "1. e4 *")
|
||||
val b = ImportPgnRequest(pgn = "1. e4 *")
|
||||
val c = ImportPgnRequest(pgn = "other")
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("e4"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("1. e4 *", a.productElement(0))
|
||||
assertEquals("pgn", a.productElementName(0))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(ImportPgnRequest(pgn = "x"), a.copy(pgn = "x"))
|
||||
|
||||
@Test
|
||||
def legalMoveDtoSynthetics(): Unit =
|
||||
val a = LegalMoveDto("e2", "e4", "e2e4", "normal")
|
||||
val b = LegalMoveDto("e2", "e4", "e2e4", "normal")
|
||||
val c = LegalMoveDto("d2", "d4", "d2d4", "normal")
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("e2"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("e2", a.productElement(0))
|
||||
assertEquals("e4", a.productElement(1))
|
||||
assertEquals("e2e4", a.productElement(2))
|
||||
assertEquals("normal", a.productElement(3))
|
||||
assertEquals(None, a.productElement(4))
|
||||
assertEquals("from", a.productElementName(0))
|
||||
assertEquals("to", a.productElementName(1))
|
||||
assertEquals("uci", a.productElementName(2))
|
||||
assertEquals("moveType", a.productElementName(3))
|
||||
assertEquals("promotion", a.productElementName(4))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(LegalMoveDto("x", "e4", "xe4", "normal"), a.copy(from = "x", uci = "xe4"))
|
||||
assertEquals(LegalMoveDto("e2", "x", "e2x", "normal"), a.copy(to = "x", uci = "e2x"))
|
||||
|
||||
@Test
|
||||
def legalMovesResponseSynthetics(): Unit =
|
||||
val a = LegalMovesResponse(List.empty)
|
||||
val b = LegalMovesResponse(List.empty)
|
||||
val c = LegalMovesResponse(List(LegalMoveDto("a1", "a2", "a1a2", "normal")))
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertNotNull(a.toString)
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals(List.empty, a.productElement(0))
|
||||
assertEquals("moves", a.productElementName(0))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(LegalMovesResponse(List.empty), a.copy(moves = List.empty))
|
||||
|
||||
@Test
|
||||
def gameStateResponseSynthetics(): Unit =
|
||||
val a = GameStateResponse("fen", "pgn", "white", "started", None, List.empty, false, false)
|
||||
val b = GameStateResponse("fen", "pgn", "white", "started", None, List.empty, false, false)
|
||||
val c = GameStateResponse("fen2", "pgn", "white", "started", None, List.empty, false, false)
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("fen"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("fen", a.productElement(0))
|
||||
assertEquals("pgn", a.productElement(1))
|
||||
assertEquals("white", a.productElement(2))
|
||||
assertEquals("started", a.productElement(3))
|
||||
assertEquals(None, a.productElement(4))
|
||||
assertEquals(List.empty, a.productElement(5))
|
||||
assertEquals(false, a.productElement(6))
|
||||
assertEquals(false, a.productElement(7))
|
||||
assertEquals("fen", a.productElementName(0))
|
||||
assertEquals("pgn", a.productElementName(1))
|
||||
assertEquals("turn", a.productElementName(2))
|
||||
assertEquals("status", a.productElementName(3))
|
||||
assertEquals("winner", a.productElementName(4))
|
||||
assertEquals("moves", a.productElementName(5))
|
||||
assertEquals("undoAvailable", a.productElementName(6))
|
||||
assertEquals("redoAvailable", a.productElementName(7))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(GameStateResponse("x", "pgn", "white", "started", None, List.empty, false, false), a.copy(fen = "x"))
|
||||
|
||||
@Test
|
||||
def gameFullResponseSynthetics(): Unit =
|
||||
val p = PlayerInfoDto("id", "Name")
|
||||
val state = GameStateResponse("fen", "pgn", "white", "started", None, List.empty, false, false)
|
||||
val a = GameFullResponse("gid", p, p, state)
|
||||
val b = GameFullResponse("gid", p, p, state)
|
||||
val c = GameFullResponse("other", p, p, state)
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("gid"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("gid", a.productElement(0))
|
||||
assertEquals(p, a.productElement(1))
|
||||
assertEquals(p, a.productElement(2))
|
||||
assertEquals(state, a.productElement(3))
|
||||
assertEquals("gameId", a.productElementName(0))
|
||||
assertEquals("white", a.productElementName(1))
|
||||
assertEquals("black", a.productElementName(2))
|
||||
assertEquals("state", a.productElementName(3))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(GameFullResponse("x", p, p, state), a.copy(gameId = "x"))
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.board.Color
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.player.{PlayerId, PlayerInfo}
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import java.io.{ByteArrayOutputStream, ObjectOutputStream}
|
||||
|
||||
/** Exercises Scala-generated synthetic methods on domain case classes and serializes singleton
|
||||
* objects (Scala objects implement Serializable; writeReplace is called on serialization).
|
||||
*/
|
||||
class GameDomainCoverageTest:
|
||||
|
||||
private val white = PlayerInfo(PlayerId("white"), "White")
|
||||
private val black = PlayerInfo(PlayerId("black"), "Black")
|
||||
|
||||
private def freshSnap(canUndo: Boolean = false, canRedo: Boolean = false): GameSnapshot =
|
||||
GameSnapshot(
|
||||
gameId = "g1",
|
||||
white = white,
|
||||
black = black,
|
||||
context = GameContext.initial,
|
||||
canUndo = canUndo,
|
||||
canRedo = canRedo,
|
||||
)
|
||||
|
||||
@Test
|
||||
def gameResultResignSynthetics(): Unit =
|
||||
val a = GameResult.Resign(Color.White)
|
||||
val b = GameResult.Resign(Color.White)
|
||||
val c = GameResult.Resign(Color.Black)
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.toString.contains("White"))
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals(Color.White, a.productElement(0))
|
||||
assertEquals("winner", a.productElementName(0))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(GameResult.Resign(Color.Black), a.copy(winner = Color.Black))
|
||||
|
||||
@Test
|
||||
def gameSnapshotSynthetics(): Unit =
|
||||
val a = freshSnap()
|
||||
val b = freshSnap()
|
||||
val c = freshSnap(canUndo = true)
|
||||
assertEquals(a, b)
|
||||
assertNotEquals(a, c)
|
||||
assertFalse(a.equals(null))
|
||||
assertFalse(a.equals("other"))
|
||||
assertEquals(a.hashCode, b.hashCode)
|
||||
assertTrue(a.canEqual(b))
|
||||
assertEquals("g1", a.productElement(0))
|
||||
assertEquals(white, a.productElement(1))
|
||||
assertEquals(black, a.productElement(2))
|
||||
assertEquals(GameContext.initial, a.productElement(3))
|
||||
assertEquals(None, a.productElement(4))
|
||||
assertEquals(None, a.productElement(5))
|
||||
assertEquals(false, a.productElement(6))
|
||||
assertEquals(false, a.productElement(7))
|
||||
assertEquals("gameId", a.productElementName(0))
|
||||
assertEquals("white", a.productElementName(1))
|
||||
assertEquals("black", a.productElementName(2))
|
||||
assertEquals("context", a.productElementName(3))
|
||||
assertEquals("drawOfferedBy", a.productElementName(4))
|
||||
assertEquals("externalResult", a.productElementName(5))
|
||||
assertEquals("canUndo", a.productElementName(6))
|
||||
assertEquals("canRedo", a.productElementName(7))
|
||||
assertEquals(a, a.copy())
|
||||
assertEquals(freshSnap(canUndo = true), a.copy(canUndo = true))
|
||||
assertEquals(freshSnap(canRedo = true), a.copy(canRedo = true))
|
||||
|
||||
@Test
|
||||
def gameMapperSingletonIsSerializable(): Unit =
|
||||
val bos = new ByteArrayOutputStream()
|
||||
val oos = new ObjectOutputStream(bos)
|
||||
oos.writeObject(GameMapper)
|
||||
oos.close()
|
||||
assertTrue(bos.size() > 0)
|
||||
|
||||
@Test
|
||||
def gameEngineHolderSingletonIsSerializable(): Unit =
|
||||
val bos = new ByteArrayOutputStream()
|
||||
val oos = new ObjectOutputStream(bos)
|
||||
oos.writeObject(GameEngineHolder)
|
||||
oos.close()
|
||||
assertTrue(bos.size() > 0)
|
||||
@@ -0,0 +1,190 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.board.{Color, File, Rank, Square}
|
||||
import de.nowchess.api.game.{DrawReason, GameContext, GameResult as ApiGameResult}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.player.{PlayerId, PlayerInfo}
|
||||
import de.nowchess.io.fen.FenParser
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class GameMapperTest:
|
||||
|
||||
private val white = PlayerInfo(PlayerId("white"), "White")
|
||||
private val black = PlayerInfo(PlayerId("black"), "Black")
|
||||
|
||||
private def snap(
|
||||
ctx: GameContext = GameContext.initial,
|
||||
externalResult: Option[GameResult] = None,
|
||||
drawOfferedBy: Option[Color] = None,
|
||||
canUndo: Boolean = false,
|
||||
canRedo: Boolean = false,
|
||||
): GameSnapshot =
|
||||
GameSnapshot(
|
||||
gameId = "testId1",
|
||||
white = white,
|
||||
black = black,
|
||||
context = ctx,
|
||||
drawOfferedBy = drawOfferedBy,
|
||||
externalResult = externalResult,
|
||||
canUndo = canUndo,
|
||||
canRedo = canRedo,
|
||||
)
|
||||
|
||||
@Test
|
||||
def resignWhiteReturnsResignWithWhiteWinner(): Unit =
|
||||
val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.Resign(Color.White))))
|
||||
assertEquals("resign", state.status)
|
||||
assertEquals(Some("white"), state.winner)
|
||||
|
||||
@Test
|
||||
def resignBlackReturnsResignWithBlackWinner(): Unit =
|
||||
val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.Resign(Color.Black))))
|
||||
assertEquals("resign", state.status)
|
||||
assertEquals(Some("black"), state.winner)
|
||||
|
||||
@Test
|
||||
def agreedDrawReturnsDrawNoWinner(): Unit =
|
||||
val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.AgreedDraw)))
|
||||
assertEquals("draw", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def fiftyMoveDrawReturnsDrawNoWinner(): Unit =
|
||||
val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.FiftyMoveDraw)))
|
||||
assertEquals("draw", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def contextWinWhiteReturnsCheckmate(): Unit =
|
||||
val ctx = GameContext.initial.withResult(Some(ApiGameResult.Win(Color.White)))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("checkmate", state.status)
|
||||
assertEquals(Some("white"), state.winner)
|
||||
|
||||
@Test
|
||||
def contextWinBlackReturnsCheckmate(): Unit =
|
||||
val ctx = GameContext.initial.withResult(Some(ApiGameResult.Win(Color.Black)))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("checkmate", state.status)
|
||||
assertEquals(Some("black"), state.winner)
|
||||
|
||||
@Test
|
||||
def contextDrawStalemateReturnsStalemate(): Unit =
|
||||
val ctx = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.Stalemate)))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("stalemate", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def contextDrawInsufficientMaterialReturnsInsufficientMaterial(): Unit =
|
||||
val ctx = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.InsufficientMaterial)))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("insufficientMaterial", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def contextDrawFiftyMoveRuleReturnsDraw(): Unit =
|
||||
val ctx = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.FiftyMoveRule)))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("draw", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def contextDrawAgreementReturnsDraw(): Unit =
|
||||
val ctx = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.Agreement)))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("draw", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def liveCheckPositionReturnsCheckStatus(): Unit =
|
||||
// White king on a1, black rook on h1 — white is in check
|
||||
val ctx = FenParser.parseFen("k7/8/8/8/8/8/8/K6r w - - 0 1")
|
||||
.getOrElse(fail("Invalid FEN"))
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("check", state.status)
|
||||
|
||||
@Test
|
||||
def liveDrawOfferedBySetReturnsDrawOffered(): Unit =
|
||||
val state = GameMapper.toGameState(snap(drawOfferedBy = Some(Color.White)))
|
||||
assertEquals("drawOffered", state.status)
|
||||
|
||||
@Test
|
||||
def liveFiftyMoveClockGe100ReturnsFiftyMoveAvailable(): Unit =
|
||||
val ctx = GameContext.initial.withHalfMoveClock(100)
|
||||
val state = GameMapper.toGameState(snap(ctx = ctx))
|
||||
assertEquals("fiftyMoveAvailable", state.status)
|
||||
|
||||
@Test
|
||||
def liveNormalPositionReturnsStarted(): Unit =
|
||||
val state = GameMapper.toGameState(snap())
|
||||
assertEquals("started", state.status)
|
||||
assertEquals(None, state.winner)
|
||||
|
||||
@Test
|
||||
def canUndoReflectedInToGameState(): Unit =
|
||||
val state = GameMapper.toGameState(snap(canUndo = true, canRedo = false))
|
||||
assertTrue(state.undoAvailable)
|
||||
assertFalse(state.redoAvailable)
|
||||
|
||||
@Test
|
||||
def canRedoReflectedInToGameState(): Unit =
|
||||
val state = GameMapper.toGameState(snap(canUndo = false, canRedo = true))
|
||||
assertFalse(state.undoAvailable)
|
||||
assertTrue(state.redoAvailable)
|
||||
|
||||
@Test
|
||||
def moveToUciNormalQuiet(): Unit =
|
||||
val sq = Square(File.E, Rank.R2)
|
||||
val sq2 = Square(File.E, Rank.R4)
|
||||
val move = Move(sq, sq2, MoveType.Normal(false))
|
||||
assertEquals("e2e4", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciNormalCapture(): Unit =
|
||||
val move = Move(Square(File.D, Rank.R4), Square(File.E, Rank.R5), MoveType.Normal(true))
|
||||
assertEquals("d4e5", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciCastleKingside(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
|
||||
assertEquals("e1g1", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciCastleQueenside(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside)
|
||||
assertEquals("e1c1", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciEnPassant(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant)
|
||||
assertEquals("e5d6", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciPromotionQueen(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Queen))
|
||||
assertEquals("e7e8q", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciPromotionRook(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Rook))
|
||||
assertEquals("e7e8r", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciPromotionBishop(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Bishop))
|
||||
assertEquals("e7e8b", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def moveToUciPromotionKnight(): Unit =
|
||||
val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Knight))
|
||||
assertEquals("e7e8n", GameMapper.moveToUci(move))
|
||||
|
||||
@Test
|
||||
def toGameFullPopulatesAllFields(): Unit =
|
||||
val full = GameMapper.toGameFull(snap())
|
||||
assertEquals("testId1", full.gameId)
|
||||
assertEquals("white", full.white.id)
|
||||
assertEquals("black", full.black.id)
|
||||
assertNotNull(full.state)
|
||||
@@ -0,0 +1,313 @@
|
||||
package de.nowchess.backcore.game
|
||||
|
||||
import de.nowchess.api.board.{Color, File, Rank, Square}
|
||||
import de.nowchess.backcore.dto.{CreateGameRequest, ImportFenRequest, PlayerInfoDto}
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.{BeforeEach, Test}
|
||||
|
||||
class GameServiceTest:
|
||||
|
||||
private val promotionFen = "8/4P3/8/8/8/8/8/4K2k w - - 0 1"
|
||||
|
||||
private def freshService(): GameService =
|
||||
val svc = new GameService()
|
||||
svc.reset(CreateGameRequest())
|
||||
svc
|
||||
|
||||
@Test
|
||||
def resetClearsStateAndReturnsSnapshot(): Unit =
|
||||
val svc = freshService()
|
||||
svc.resign()
|
||||
val snap = svc.reset(CreateGameRequest())
|
||||
assertEquals(GameEngineHolder.gameId, snap.gameId)
|
||||
assertEquals(None, snap.externalResult)
|
||||
assertEquals(None, snap.drawOfferedBy)
|
||||
|
||||
@Test
|
||||
def resetWithPlayersUpdatesHolderInfo(): Unit =
|
||||
val svc = freshService()
|
||||
val req = CreateGameRequest(
|
||||
white = Some(PlayerInfoDto("p1", "Alice")),
|
||||
black = Some(PlayerInfoDto("p2", "Bob")),
|
||||
)
|
||||
val snap = svc.reset(req)
|
||||
assertEquals("Alice", snap.white.displayName)
|
||||
assertEquals("Bob", snap.black.displayName)
|
||||
|
||||
@Test
|
||||
def applyMoveInvalidUciReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.applyMove("zzzz")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def applyMoveIllegalMoveReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.applyMove("e2e5")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def applyMoveValidMoveReturnsRightWithUpdatedContext(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.applyMove("e2e4")
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertEquals(Color.Black, snap.context.turn)
|
||||
|
||||
@Test
|
||||
def applyMoveWhenGameOverReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
svc.resign()
|
||||
val result = svc.applyMove("e2e4")
|
||||
assertTrue(result.isLeft)
|
||||
assertTrue(result.left.getOrElse("").contains("Game is already over"))
|
||||
|
||||
@Test
|
||||
def legalMovesNoneReturnsNonEmptyListAtStart(): Unit =
|
||||
val svc = freshService()
|
||||
val moves = svc.legalMoves(None)
|
||||
assertFalse(moves.isEmpty)
|
||||
|
||||
@Test
|
||||
def legalMovesForE2ReturnsE2e3AndE2e4(): Unit =
|
||||
val svc = freshService()
|
||||
val e2 = Square(File.E, Rank.R2)
|
||||
val moves = svc.legalMoves(Some(e2))
|
||||
val ucis = moves.map(m => s"${m.from}${m.to}")
|
||||
assertTrue(ucis.contains("e2e3"), s"Expected e2e3 in $ucis")
|
||||
assertTrue(ucis.contains("e2e4"), s"Expected e2e4 in $ucis")
|
||||
|
||||
@Test
|
||||
def undoWithNoHistoryReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.undo()
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def undoAfterMoveReverts(): Unit =
|
||||
val svc = freshService()
|
||||
svc.applyMove("e2e4")
|
||||
val result = svc.undo()
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertEquals(Color.White, snap.context.turn)
|
||||
|
||||
@Test
|
||||
def redoWithNoRedoStackReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.redo()
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def redoAfterUndoReturnsRight(): Unit =
|
||||
val svc = freshService()
|
||||
svc.applyMove("e2e4")
|
||||
svc.undo()
|
||||
val result = svc.redo()
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertEquals(Color.Black, snap.context.turn)
|
||||
|
||||
@Test
|
||||
def resignNormalReturnsRight(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.resign()
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertTrue(snap.externalResult.isDefined)
|
||||
snap.externalResult match
|
||||
case Some(GameResult.Resign(_)) => ()
|
||||
case other => fail(s"Expected Resign but got $other")
|
||||
|
||||
@Test
|
||||
def resignWhenAlreadyResignedReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
svc.resign()
|
||||
val result = svc.resign()
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def drawActionOfferSetsDrawOfferedBy(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.drawAction("offer")
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertTrue(snap.drawOfferedBy.isDefined)
|
||||
|
||||
@Test
|
||||
def drawActionAcceptWithNoOfferReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.drawAction("accept")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def drawActionAcceptOwnOfferReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
svc.drawAction("offer")
|
||||
val result = svc.drawAction("accept")
|
||||
assertTrue(result.isLeft)
|
||||
assertTrue(result.left.getOrElse("").contains("Cannot accept your own draw offer"))
|
||||
|
||||
@Test
|
||||
def drawActionAcceptOpponentAcceptsReturnsAgreedDraw(): Unit =
|
||||
val svc = freshService()
|
||||
svc.drawAction("offer")
|
||||
svc.applyMove("e2e4")
|
||||
val result = svc.drawAction("accept")
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertEquals(Some(GameResult.AgreedDraw), snap.externalResult)
|
||||
|
||||
@Test
|
||||
def drawActionDeclineWithNoOfferReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.drawAction("decline")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def drawActionDeclineAfterOfferClearsOffer(): Unit =
|
||||
val svc = freshService()
|
||||
svc.drawAction("offer")
|
||||
val result = svc.drawAction("decline")
|
||||
assertTrue(result.isRight)
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertEquals(None, snap.drawOfferedBy)
|
||||
|
||||
@Test
|
||||
def drawActionClaimWhenFiftyMoveNotTriggeredReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.drawAction("claim")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def drawActionUnknownReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.drawAction("unknown")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def importFenValidReturnsRight(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.importFen(ImportFenRequest(fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"))
|
||||
assertTrue(result.isRight)
|
||||
|
||||
@Test
|
||||
def importFenInvalidReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.importFen(ImportFenRequest(fen = "not-a-fen"))
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def importPgnValidReturnsRight(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.importPgn("1. e4 e5 2. Nf3 Nc6 *")
|
||||
assertTrue(result.isRight)
|
||||
|
||||
@Test
|
||||
def importPgnInvalidReturnsLeft(): Unit =
|
||||
val svc = freshService()
|
||||
val result = svc.importPgn("1. z9 *")
|
||||
assertTrue(result.isLeft)
|
||||
|
||||
@Test
|
||||
def exportFenReturnsNonEmptyString(): Unit =
|
||||
val svc = freshService()
|
||||
val fen = svc.exportFen()
|
||||
assertFalse(fen.isEmpty)
|
||||
|
||||
@Test
|
||||
def exportPgnReturnsString(): Unit =
|
||||
val svc = freshService()
|
||||
val pgn = svc.exportPgn()
|
||||
assertNotNull(pgn)
|
||||
|
||||
@Test
|
||||
def isKnownIdTrueForHolderGameId(): Unit =
|
||||
val svc = freshService()
|
||||
assertTrue(svc.isKnownId(GameEngineHolder.gameId))
|
||||
|
||||
@Test
|
||||
def isKnownIdFalseForOtherId(): Unit =
|
||||
val svc = freshService()
|
||||
assertFalse(svc.isKnownId("XXXXXXXX"))
|
||||
|
||||
@Test
|
||||
def applyMovePromotionQueenProducesQueen(): Unit =
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = promotionFen))
|
||||
val result = svc.applyMove("e7e8q")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
val e8 = Square(File.E, Rank.R8)
|
||||
snap.context.board.pieceAt(e8) match
|
||||
case Some(piece) =>
|
||||
assertEquals(de.nowchess.api.board.PieceType.Queen, piece.pieceType)
|
||||
case None => fail("Expected queen on e8")
|
||||
|
||||
@Test
|
||||
def applyMovePromotionRookProducesRook(): Unit =
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = promotionFen))
|
||||
val result = svc.applyMove("e7e8r")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
val e8 = Square(File.E, Rank.R8)
|
||||
snap.context.board.pieceAt(e8) match
|
||||
case Some(piece) => assertEquals(de.nowchess.api.board.PieceType.Rook, piece.pieceType)
|
||||
case None => fail("Expected rook on e8")
|
||||
|
||||
@Test
|
||||
def applyMovePromotionBishopProducesBishop(): Unit =
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = promotionFen))
|
||||
val result = svc.applyMove("e7e8b")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
val e8 = Square(File.E, Rank.R8)
|
||||
snap.context.board.pieceAt(e8) match
|
||||
case Some(piece) => assertEquals(de.nowchess.api.board.PieceType.Bishop, piece.pieceType)
|
||||
case None => fail("Expected bishop on e8")
|
||||
|
||||
@Test
|
||||
def applyMovePromotionKnightProducesKnight(): Unit =
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = promotionFen))
|
||||
val result = svc.applyMove("e7e8n")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
val e8 = Square(File.E, Rank.R8)
|
||||
snap.context.board.pieceAt(e8) match
|
||||
case Some(piece) => assertEquals(de.nowchess.api.board.PieceType.Knight, piece.pieceType)
|
||||
case None => fail("Expected knight on e8")
|
||||
|
||||
@Test
|
||||
def applyMoveWithoutPromotionCharFallsBackToFirstPromotion(): Unit =
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = promotionFen))
|
||||
val result = svc.applyMove("e7e8")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
|
||||
@Test
|
||||
def applyMoveWithInvalidPromotionCharFallsBackToFirstPromotion(): Unit =
|
||||
// 'x' → parsePromotionChar wildcard branch → None → orElse(headOption)
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = promotionFen))
|
||||
val result = svc.applyMove("e7e8x")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
|
||||
@Test
|
||||
def drawActionClaimWhenFiftyMoveTriggeredReturnsRight(): Unit =
|
||||
val svc = freshService()
|
||||
svc.importFen(ImportFenRequest(fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 100 51"))
|
||||
val result = svc.drawAction("claim")
|
||||
assertTrue(result.isRight, s"Expected Right but got $result")
|
||||
val snap = result.getOrElse(fail("Expected Right"))
|
||||
assertEquals(Some(GameResult.FiftyMoveDraw), snap.externalResult)
|
||||
|
||||
@Test
|
||||
def engineSetterPropagatesNewEngine(): Unit =
|
||||
val original = GameEngineHolder.engine
|
||||
val fresh = new de.nowchess.chess.engine.GameEngine()
|
||||
GameEngineHolder.engine = fresh
|
||||
assertEquals(fresh, GameEngineHolder.engine)
|
||||
GameEngineHolder.engine = original
|
||||
@@ -0,0 +1,112 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import org.hamcrest.Matchers.{equalTo, matchesPattern, notNullValue}
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class GameResourceTest:
|
||||
|
||||
@Test
|
||||
def createGameReturns201WithGameId(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.body("gameId", matchesPattern("[A-Za-z0-9]{8}"))
|
||||
.body("state.fen", notNullValue())
|
||||
.body("state.turn", equalTo("white"))
|
||||
.body("state.status", equalTo("started"))
|
||||
|
||||
@Test
|
||||
def createGameWithPlayersReturns201(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"white":{"id":"p1","displayName":"Alice"},"black":{"id":"p2","displayName":"Bob"}}""")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.body("white.id", equalTo("p1"))
|
||||
.body("black.displayName", equalTo("Bob"))
|
||||
|
||||
@Test
|
||||
def getGameReturns200ForExistingGame(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("gameId", equalTo(gameId))
|
||||
|
||||
@Test
|
||||
def getGameReturns404ForUnknownId(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get("/api/board/game/XXXXXXXX")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
@Test
|
||||
def createGameWithNullBodyReturns201(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("null")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
|
||||
@Test
|
||||
def exportPgnOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get("/api/board/game/XXXXXXXX/export/pgn")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
@Test
|
||||
def resignWhenAlreadyResignedReturns400(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/resign")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/resign")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
@@ -0,0 +1,199 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import org.hamcrest.Matchers.{containsString, equalTo, matchesPattern, notNullValue}
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class ImportExportTest:
|
||||
|
||||
private val startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||
|
||||
// ─── Import FEN ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def importFenReturns201WithCorrectPosition(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body(s"""{"fen":"$startFen"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.body("gameId", matchesPattern("[A-Za-z0-9]{8}"))
|
||||
.body("state.fen", equalTo(startFen))
|
||||
.body("state.turn", equalTo("white"))
|
||||
|
||||
@Test
|
||||
def importFenWithCustomPositionWorks(): Unit =
|
||||
val fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body(s"""{"fen":"$fen"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.body("state.fen", equalTo(fen))
|
||||
.body("state.turn", equalTo("black"))
|
||||
|
||||
@Test
|
||||
def importFenWithInvalidFenReturns400(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"fen":"not-a-fen"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
@Test
|
||||
def importFenWithNullBodyReturns400(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("null")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
// ─── Import PGN ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def importPgnReturns201(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"pgn":"1. e4 e5 2. Nf3 Nc6 *"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/pgn")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.body("gameId", matchesPattern("[A-Za-z0-9]{8}"))
|
||||
.body("state.turn", equalTo("white"))
|
||||
|
||||
@Test
|
||||
def importPgnWithInvalidPgnReturns400(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"pgn":"1. z9 *"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/pgn")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
@Test
|
||||
def importPgnWithNullBodyReturns400(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("null")
|
||||
.when()
|
||||
.post("/api/board/game/import/pgn")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
// ─── Export FEN ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def exportFenReturnsStartingFen(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/export/fen")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body(equalTo(startFen))
|
||||
|
||||
@Test
|
||||
def exportFenOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get("/api/board/game/XXXXXXXX/export/fen")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
// ─── Export PGN ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def exportPgnReturnsText(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/export/pgn")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body(containsString("e4"))
|
||||
|
||||
// ─── Stream ────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def streamReturnsNdjsonSnapshot(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
val body = RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/stream")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.contentType("application/x-ndjson")
|
||||
.extract()
|
||||
.body()
|
||||
.asString()
|
||||
|
||||
assert(body.trim.startsWith("""{"type":"gameFull""""), s"Expected gameFull event, got: $body")
|
||||
|
||||
@Test
|
||||
def streamOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get("/api/board/game/XXXXXXXX/stream")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
@@ -0,0 +1,164 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import org.hamcrest.Matchers.{containsString, empty, equalTo, hasItem, hasItems, not, notNullValue}
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class MoveResourceTest:
|
||||
|
||||
private def createGame(): String =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
@Test
|
||||
def makeMoveReturns200WithUpdatedFen(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("fen", containsString("4P3")) // e4 pawn present in FEN
|
||||
.body("turn", equalTo("black"))
|
||||
.body("moves", hasItem("e2e4"))
|
||||
|
||||
@Test
|
||||
def makeMoveOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post("/api/board/game/XXXXXXXX/move/e2e4")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
@Test
|
||||
def illegalMoveReturns400(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.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`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/moves")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves", not(empty()))
|
||||
|
||||
@Test
|
||||
def getLegalMovesFilteredBySquareReturnsCorrectMoves(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/moves?square=e2")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.uci", hasItems("e2e3", "e2e4"))
|
||||
|
||||
@Test
|
||||
def getLegalMovesOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get("/api/board/game/XXXXXXXX/moves")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
@Test
|
||||
def getLegalMovesIncludesCaptureType(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"fen":"rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/moves?square=e4")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.moveType", hasItem("capture"))
|
||||
|
||||
@Test
|
||||
def getLegalMovesIncludesCastlingTypes(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"fen":"4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/moves?square=e1")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.moveType", hasItems("castleKingside", "castleQueenside"))
|
||||
|
||||
@Test
|
||||
def getLegalMovesIncludesEnPassantType(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"fen":"rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/moves?square=e5")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.moveType", hasItem("enPassant"))
|
||||
|
||||
@Test
|
||||
def getLegalMovesIncludesAllPromotionTypes(): Unit =
|
||||
val gameId = RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("""{"fen":"8/4P3/8/8/8/8/8/4K2k w - - 0 1"}""")
|
||||
.when()
|
||||
.post("/api/board/game/import/fen")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId/moves?square=e7")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.promotion", hasItems("rook", "bishop", "knight"))
|
||||
@@ -0,0 +1,168 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import org.hamcrest.Matchers.{equalTo, notNullValue}
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class ResignDrawTest:
|
||||
|
||||
private def createGame(): String =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
// ─── Resign ────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def resignReturns200(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/resign")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("ok", equalTo(true))
|
||||
|
||||
@Test
|
||||
def afterResignGameShowsResignStatusAndWinner(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/resign")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("state.status", equalTo("resign"))
|
||||
.body("state.winner", notNullValue())
|
||||
|
||||
@Test
|
||||
def resignOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post("/api/board/game/XXXXXXXX/resign")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
// ─── Draw ──────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def offerDrawSetsDrawOfferedStatus(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/draw/offer")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("ok", equalTo(true))
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("state.status", equalTo("drawOffered"))
|
||||
|
||||
@Test
|
||||
def acceptDrawAfterOfferSetsDrawStatus(): Unit =
|
||||
val gameId = createGame()
|
||||
// White offers
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/draw/offer")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
// Black moves so it's black's turn... actually the API doesn't enforce turn-based draw accept.
|
||||
// White offered, so black (opponent) accepts — but since there's no auth, we just call 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.
|
||||
// We need to make a move first to switch turns.
|
||||
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`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/draw/accept")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("ok", equalTo(true))
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("state.status", equalTo("draw"))
|
||||
|
||||
@Test
|
||||
def declineDrawClearsOffer(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/draw/offer")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/draw/decline")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("ok", equalTo(true))
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.get(s"/api/board/game/$gameId")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("state.status", equalTo("started"))
|
||||
|
||||
@Test
|
||||
def acceptWithoutOfferReturns400(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/draw/accept")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
@Test
|
||||
def drawOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post("/api/board/game/XXXXXXXX/draw/offer")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
@@ -0,0 +1,106 @@
|
||||
package de.nowchess.backcore.resource
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import org.hamcrest.Matchers.{containsString, equalTo}
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class UndoRedoTest:
|
||||
|
||||
private val initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||
|
||||
private def createGame(): String =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.contentType("application/json")
|
||||
.body("{}")
|
||||
.when()
|
||||
.post("/api/board/game")
|
||||
.`then`()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.path[String]("gameId")
|
||||
|
||||
@Test
|
||||
def undoAfterMoveRestoresOriginalPosition(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/undo")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("fen", equalTo(initialFen))
|
||||
.body("undoAvailable", equalTo(false))
|
||||
|
||||
@Test
|
||||
def redoAfterUndoRestoresMovedPosition(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/move/e2e4")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/undo")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/redo")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("fen", containsString("4P3"))
|
||||
.body("turn", equalTo("black"))
|
||||
|
||||
@Test
|
||||
def undoWithNoHistoryReturns400(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/undo")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
@Test
|
||||
def redoWithNoRedoStackReturns400(): Unit =
|
||||
val gameId = createGame()
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post(s"/api/board/game/$gameId/redo")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
@Test
|
||||
def undoMoveOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post("/api/board/game/XXXXXXXX/undo")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
|
||||
@Test
|
||||
def redoMoveOnUnknownGameReturns404(): Unit =
|
||||
RestAssured
|
||||
.`given`()
|
||||
.when()
|
||||
.post("/api/board/game/XXXXXXXX/redo")
|
||||
.`then`()
|
||||
.statusCode(404)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
-2
@@ -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"
|
||||
|
||||
+3
-1
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -90,6 +90,14 @@ dependencies {
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
configurations.scoverage {
|
||||
resolutionStrategy.eachDependency {
|
||||
if (requested.group == "org.scoverage" && requested.name.startsWith("scalac-scoverage-plugin_")) {
|
||||
useTarget("${requested.group}:scalac-scoverage-plugin_2.13.16:2.3.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
includeEngines("scalatest")
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
package de.nowchess.ui
|
||||
|
||||
import de.nowchess.chess.engine.GameEngine
|
||||
import de.nowchess.ui.terminal.TerminalUI
|
||||
import de.nowchess.ui.gui.ChessGUILauncher
|
||||
import de.nowchess.ui.terminal.TerminalUI
|
||||
|
||||
/** Application entry point - starts both GUI and Terminal UI for the chess game. Both views subscribe to the same
|
||||
* GameEngine via Observer pattern.
|
||||
*/
|
||||
object Main:
|
||||
def main(args: Array[String]): Unit =
|
||||
// Create the core game engine (single source of truth)
|
||||
val engine = new GameEngine()
|
||||
|
||||
// Launch ScalaFX GUI in separate thread
|
||||
ChessGUILauncher.launch(engine)
|
||||
|
||||
// Create and start the terminal UI (blocks on main thread)
|
||||
val tui = new TerminalUI(engine)
|
||||
tui.start()
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+16
-1
@@ -1,8 +1,23 @@
|
||||
rootProject.name = "NowChessSystems"
|
||||
|
||||
pluginManagement {
|
||||
val quarkusPluginVersion: String by settings
|
||||
val quarkusPluginId: String by settings
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
mavenLocal()
|
||||
}
|
||||
plugins {
|
||||
id(quarkusPluginId) version quarkusPluginVersion
|
||||
}
|
||||
}
|
||||
|
||||
include(
|
||||
"modules:core",
|
||||
"modules:api",
|
||||
"modules:io",
|
||||
"modules:rule",
|
||||
"modules:ui",
|
||||
)
|
||||
"modules:backcore"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user