feat: NCS-52 Rules as a microservice (#36)
Build & Test (NowChessSystems) TeamCity build finished

Implemented module rules as a microservice.
## Summary

   - Adds Quarkus to the `rule` module, making it independently deployable as a standalone microservice on port 8081
   - Exposes all `RuleSet` methods via a REST API at `/api/rules` (candidate moves, legal moves, check/checkmate/stalemate/draw detection, apply
   move)
   - Introduces DTOs and a `DtoMapper` for serializing/deserializing chess types (board, moves, game context) as flat JSON strings
   - Adds `JacksonConfig` for Scala module registration and an `application.yml` for the rule service
   - Includes Dockerfiles for JVM, legacy-jar, native, and native-micro targets
   - Full test coverage: 17 `@QuarkusTest` HTTP-level tests + 29 `DtoMapper` unit tests (100% condition coverage)

   ## Test plan

   - [ ] `./compile` — all modules build successfully
   - [ ] `./test` — all tests pass (rule module: 107 tests total)
   - [ ] `./coverage` — 100% condition coverage in `rule` module
   - [ ] Rule service runs standalone: `./gradlew :modules:rule:quarkusDev` starts on port 8081
   - [ ] `GET /api/rules/candidate-moves` returns valid moves for initial position
   - [ ] `GET /api/rules/is-check` returns `false` for initial position

Co-authored-by: LQ63 <lkhermann@web.de>
Co-authored-by: Janis <janis-e@gmx.de>
Reviewed-on: #36
Co-authored-by: Leon Hermann <lq@blackhole.local>
Co-committed-by: Leon Hermann <lq@blackhole.local>
This commit was merged in pull request #36.
This commit is contained in:
2026-04-22 09:29:15 +02:00
committed by Janis
parent 3b1e4451d6
commit e73b6a7514
53 changed files with 1654 additions and 75 deletions
@@ -0,0 +1,41 @@
package de.nowchess.api.rules
import de.nowchess.api.board.Square
import de.nowchess.api.game.GameContext
import de.nowchess.api.move.Move
/** Extension point for chess rule variants (standard, Chess960, etc.). All rule queries are stateless: given a
* GameContext, return the answer.
*/
trait RuleSet:
/** All pseudo-legal moves for the piece on `square` (ignores check). */
def candidateMoves(context: GameContext)(square: Square): List[Move]
/** Legal moves for `square`: candidates that don't leave own king in check. */
def legalMoves(context: GameContext)(square: Square): List[Move]
/** All legal moves for the side to move. */
def allLegalMoves(context: GameContext): List[Move]
/** True if the side to move's king is in check. */
def isCheck(context: GameContext): Boolean
/** True if the side to move is in check and has no legal moves. */
def isCheckmate(context: GameContext): Boolean
/** True if the side to move is not in check and has no legal moves. */
def isStalemate(context: GameContext): Boolean
/** True if neither side has enough material to checkmate. */
def isInsufficientMaterial(context: GameContext): Boolean
/** True if halfMoveClock >= 100 (50-move rule). */
def isFiftyMoveRule(context: GameContext): Boolean
/** True if the same position has occurred 3 times (including current position). */
def isThreefoldRepetition(context: GameContext): Boolean
/** Apply a legal move to produce the next game context. Handles all special move types: castling, en passant,
* promotion. Updates castling rights, en passant square, half-move clock, turn, and move history.
*/
def applyMove(context: GameContext)(move: Move): GameContext