Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a176f9e7ca | |||
| b52623b8f6 | |||
| 5d3d3b4be3 | |||
| 3febed3cca | |||
| b706c65a0a | |||
| 1401297e7f | |||
| 5ad5efb41e | |||
| f215ec681a | |||
| 0091d50467 | |||
| 2e4c7549b5 | |||
| dceab0875e |
@@ -1,87 +0,0 @@
|
||||
name: Build & Push Native Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-actor:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
allowed: ${{ steps.check.outputs.allowed }}
|
||||
steps:
|
||||
- id: check
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.actor }}" == "TeamCity" ]]; then
|
||||
echo "allowed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "allowed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
build-and-push:
|
||||
needs: check-actor
|
||||
if: needs.check-actor.outputs.allowed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
module:
|
||||
- core
|
||||
- io
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up GraalVM
|
||||
uses: graalvm/setup-graalvm@v1
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'graalvm-community'
|
||||
native-image-job-reports: 'true'
|
||||
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: gradle-${{ runner.os }}-
|
||||
|
||||
- name: Build native binary
|
||||
run: ./gradlew :modules:${{ matrix.module }}:build -Dquarkus.native.enabled=true --no-daemon
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/now-chess/now-chess-systems/${{ matrix.module }}
|
||||
tags: |
|
||||
type=sha,prefix=,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: modules/${{ matrix.module }}/src/main/docker/Dockerfile.native
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ matrix.module }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.module }}
|
||||
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.bot.main,NowChessSystems.modules.bot.scoverage,NowChessSystems.modules.bot.test,NowChessSystems.modules.core.integrationTest,NowChessSystems.modules.core.main,NowChessSystems.modules.core.native-test,NowChessSystems.modules.core.quarkus-generated-sources,NowChessSystems.modules.core.quarkus-test-generated-sources,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.integrationTest,NowChessSystems.modules.io.main,NowChessSystems.modules.io.native-test,NowChessSystems.modules.io.quarkus-generated-sources,NowChessSystems.modules.io.quarkus-test-generated-sources,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.bot.main,NowChessSystems.modules.bot.scoverage,NowChessSystems.modules.bot.test,NowChessSystems.modules.core.integrationTest,NowChessSystems.modules.core.main,NowChessSystems.modules.core.native-test,NowChessSystems.modules.core.quarkus-generated-sources,NowChessSystems.modules.core.quarkus-test-generated-sources,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>
|
||||
@@ -9,7 +9,6 @@ Scala 3.5.1 · Gradle 9
|
||||
./compile # Compile all modules — always run
|
||||
./test # Run all tests
|
||||
./coverage # Check coverage
|
||||
./lint # Run linters
|
||||
```
|
||||
Use consistently.
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ Scala 3.5.1 · Gradle 9
|
||||
./compile # Compile all modules — always run
|
||||
./test # Run all tests
|
||||
./coverage # Check coverage
|
||||
./lint # Run linters
|
||||
```
|
||||
Try to stick to these commands for consistency.
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
vars {
|
||||
baseUrl: http://localhost:8080
|
||||
ioBaseUrl: http://localhost:8081
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
meta {
|
||||
name: Export FEN
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
http {
|
||||
method: POST
|
||||
url: {{ioBaseUrl}}/io/export/fen
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"board": {
|
||||
"a1": {"color": "White", "pieceType": "Rook"},
|
||||
"b1": {"color": "White", "pieceType": "Knight"},
|
||||
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||
"d1": {"color": "White", "pieceType": "Queen"},
|
||||
"e1": {"color": "White", "pieceType": "King"},
|
||||
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||
"g1": {"color": "White", "pieceType": "Knight"},
|
||||
"h1": {"color": "White", "pieceType": "Rook"},
|
||||
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||
"e8": {"color": "Black", "pieceType": "King"},
|
||||
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||
},
|
||||
"turn": "White",
|
||||
"castlingRights": {
|
||||
"whiteKingSide": true,
|
||||
"whiteQueenSide": true,
|
||||
"blackKingSide": true,
|
||||
"blackQueenSide": true
|
||||
},
|
||||
"enPassantSquare": null,
|
||||
"halfMoveClock": 0,
|
||||
"moves": [],
|
||||
"result": null,
|
||||
"initialBoard": {
|
||||
"a1": {"color": "White", "pieceType": "Rook"},
|
||||
"b1": {"color": "White", "pieceType": "Knight"},
|
||||
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||
"d1": {"color": "White", "pieceType": "Queen"},
|
||||
"e1": {"color": "White", "pieceType": "King"},
|
||||
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||
"g1": {"color": "White", "pieceType": "Knight"},
|
||||
"h1": {"color": "White", "pieceType": "Rook"},
|
||||
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||
"e8": {"color": "Black", "pieceType": "King"},
|
||||
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
meta {
|
||||
name: Export PGN
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
http {
|
||||
method: POST
|
||||
url: {{ioBaseUrl}}/io/export/pgn
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"board": {
|
||||
"a1": {"color": "White", "pieceType": "Rook"},
|
||||
"b1": {"color": "White", "pieceType": "Knight"},
|
||||
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||
"d1": {"color": "White", "pieceType": "Queen"},
|
||||
"e1": {"color": "White", "pieceType": "King"},
|
||||
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||
"g1": {"color": "White", "pieceType": "Knight"},
|
||||
"h1": {"color": "White", "pieceType": "Rook"},
|
||||
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||
"e8": {"color": "Black", "pieceType": "King"},
|
||||
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||
},
|
||||
"turn": "White",
|
||||
"castlingRights": {
|
||||
"whiteKingSide": true,
|
||||
"whiteQueenSide": true,
|
||||
"blackKingSide": true,
|
||||
"blackQueenSide": true
|
||||
},
|
||||
"enPassantSquare": null,
|
||||
"halfMoveClock": 0,
|
||||
"moves": [],
|
||||
"result": null,
|
||||
"initialBoard": {
|
||||
"a1": {"color": "White", "pieceType": "Rook"},
|
||||
"b1": {"color": "White", "pieceType": "Knight"},
|
||||
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||
"d1": {"color": "White", "pieceType": "Queen"},
|
||||
"e1": {"color": "White", "pieceType": "King"},
|
||||
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||
"g1": {"color": "White", "pieceType": "Knight"},
|
||||
"h1": {"color": "White", "pieceType": "Rook"},
|
||||
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||
"e8": {"color": "Black", "pieceType": "King"},
|
||||
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
meta {
|
||||
name: export
|
||||
seq: 2
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
meta {
|
||||
name: Import FEN
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
http {
|
||||
method: POST
|
||||
url: {{ioBaseUrl}}/io/import/fen
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
meta {
|
||||
name: Import PGN
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
http {
|
||||
method: POST
|
||||
url: {{ioBaseUrl}}/io/import/pgn
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"pgn": "1. e4 e5 2. Nf3 Nc6 *"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
meta {
|
||||
name: import
|
||||
seq: 1
|
||||
}
|
||||
+2
-6
@@ -36,11 +36,7 @@ val coverageExclusions = listOf(
|
||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala",
|
||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.scala",
|
||||
// GameResource — REST integration layer with @Inject var fields; mocking dependencies for unit tests is infeasible with Quarkus DI; integration tests would require @QuarkusTest which Scoverage doesn't instrument
|
||||
"**/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala",
|
||||
// IoResource — same rationale as GameResource; @QuarkusTest not instrumented by Scoverage
|
||||
"**/io/src/main/scala/de/nowchess/io/service/resource/IoResource.scala",
|
||||
// JacksonConfig — Quarkus lifecycle hook, no testable logic beyond ObjectMapper registration
|
||||
"**/io/src/main/scala/de/nowchess/io/service/config/JacksonConfig.scala",
|
||||
"**/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala"
|
||||
)
|
||||
|
||||
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
|
||||
@@ -76,7 +72,7 @@ sonar {
|
||||
val versions = mapOf(
|
||||
"QUARKUS_SCALA3" to "1.0.0",
|
||||
"SCALA3" to "3.5.1",
|
||||
"SCALA_LIBRARY" to "2.13.16",
|
||||
"SCALA_LIBRARY" to "2.13.18",
|
||||
"SCALATEST" to "3.2.19",
|
||||
"SCALATEST_JUNIT" to "0.1.11",
|
||||
"SCOVERAGE" to "2.1.1",
|
||||
|
||||
@@ -60,37 +60,3 @@
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
## (2026-04-22)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([fb5c61d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fb5c61de63292e5d70c06304cba2193686aa1607))
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package de.nowchess.api.dto
|
||||
|
||||
case class ImportFenRequest(fen: String)
|
||||
@@ -1,3 +0,0 @@
|
||||
package de.nowchess.api.dto
|
||||
|
||||
case class ImportPgnRequest(pgn: String)
|
||||
@@ -1,3 +1,3 @@
|
||||
MAJOR=0
|
||||
MINOR=11
|
||||
MINOR=8
|
||||
PATCH=0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": 1,
|
||||
"created": "2026-04-13T19:58:38.629943",
|
||||
"total_positions": 3022562,
|
||||
"stockfish_depth": 12,
|
||||
"sources": [
|
||||
{
|
||||
"type": "legacy_import",
|
||||
"path": "data/training_data.jsonl",
|
||||
"count": 2009355,
|
||||
"note": "Migrated from data/training_data.jsonl"
|
||||
},
|
||||
{
|
||||
"type": "test_extend",
|
||||
"count": 4,
|
||||
"actual_count": 3
|
||||
},
|
||||
{
|
||||
"type": "test_new_positions",
|
||||
"count": 3,
|
||||
"actual_count": 3
|
||||
},
|
||||
{
|
||||
"type": "test_mixed",
|
||||
"count": 5,
|
||||
"actual_count": 0
|
||||
},
|
||||
{
|
||||
"type": "test_all_dups",
|
||||
"count": 2,
|
||||
"actual_count": 0
|
||||
},
|
||||
{
|
||||
"type": "guaranteed_unique",
|
||||
"count": 10,
|
||||
"actual_count": 8
|
||||
},
|
||||
{
|
||||
"type": "merged_sources",
|
||||
"count": 600000,
|
||||
"sources": [
|
||||
{
|
||||
"type": "tactical",
|
||||
"count": 600000,
|
||||
"max_puzzles": 600000
|
||||
}
|
||||
],
|
||||
"actual_count": 599993
|
||||
},
|
||||
{
|
||||
"type": "merged_sources",
|
||||
"count": 500000,
|
||||
"sources": [
|
||||
{
|
||||
"type": "lichess",
|
||||
"count": 500000,
|
||||
"params": {
|
||||
"min_depth": 20,
|
||||
"max_positions": 500000
|
||||
}
|
||||
}
|
||||
],
|
||||
"actual_count": 500000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -341,93 +341,3 @@
|
||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
|
||||
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
|
||||
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
|
||||
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
|
||||
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([0091d50](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0091d50467e9f955f23570128b96c977c01bc51b))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
|
||||
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
|
||||
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
|
||||
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
|
||||
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
|
||||
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
|
||||
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([0091d50](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0091d50467e9f955f23570128b96c977c01bc51b))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([9b51852](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/9b5185298e9e721e6103ea8372ca29073913775c))
|
||||
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
|
||||
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
|
||||
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||
## (2026-04-22)
|
||||
|
||||
### Features
|
||||
|
||||
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
|
||||
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
|
||||
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
|
||||
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
|
||||
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
|
||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([0091d50](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0091d50467e9f955f23570128b96c977c01bc51b))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([9b51852](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/9b5185298e9e721e6103ea8372ca29073913775c))
|
||||
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
|
||||
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
|
||||
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([fb5c61d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fb5c61de63292e5d70c06304cba2193686aa1607))
|
||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||
|
||||
@@ -48,6 +48,7 @@ dependencies {
|
||||
}
|
||||
|
||||
implementation(project(":modules:api"))
|
||||
implementation(project(":modules:io"))
|
||||
implementation(project(":modules:rule"))
|
||||
implementation(project(":modules:bot"))
|
||||
|
||||
@@ -67,15 +68,11 @@ dependencies {
|
||||
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||
|
||||
|
||||
testImplementation(project(":modules:io"))
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
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-junit5-mockito")
|
||||
testImplementation("io.rest-assured:rest-assured")
|
||||
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
@@ -118,4 +115,3 @@ tasks.reportScoverage {
|
||||
tasks.jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew :modules:core:build -Dquarkus.native.enabled=true
|
||||
# ./gradlew build -Dquarkus.native.enabled=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
@@ -13,7 +13,7 @@
|
||||
#
|
||||
# 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.
|
||||
# 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
|
||||
@@ -21,7 +21,7 @@ WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root --chmod=0755 modules/core/build/*-runner /work/application
|
||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
quarkus:
|
||||
http:
|
||||
port: 8080
|
||||
application:
|
||||
name: nowchess-core
|
||||
rest-client:
|
||||
io-service:
|
||||
url: http://localhost:8081
|
||||
greeting:
|
||||
message: "hello"
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
greeting:
|
||||
message: "hello"
|
||||
@@ -0,0 +1,6 @@
|
||||
-- This file allow to write SQL commands that will be emitted in test and dev.
|
||||
-- The commands are commented as their support depends of the database
|
||||
-- insert into myentity (id, field) values(1, 'field-1');
|
||||
-- insert into myentity (id, field) values(2, 'field-2');
|
||||
-- insert into myentity (id, field) values(3, 'field-3');
|
||||
-- alter sequence myentity_seq restart with 4;
|
||||
@@ -1,35 +0,0 @@
|
||||
package de.nowchess.chess.client
|
||||
|
||||
import de.nowchess.api.dto.{ImportFenRequest, ImportPgnRequest}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.MediaType
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
|
||||
|
||||
@Path("/io")
|
||||
@RegisterRestClient(configKey = "io-service")
|
||||
trait IoServiceClient:
|
||||
|
||||
@POST
|
||||
@Path("/import/fen")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def importFen(body: ImportFenRequest): GameContext
|
||||
|
||||
@POST
|
||||
@Path("/import/pgn")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def importPgn(body: ImportPgnRequest): GameContext
|
||||
|
||||
@POST
|
||||
@Path("/export/fen")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.TEXT_PLAIN))
|
||||
def exportFen(ctx: GameContext): String
|
||||
|
||||
@POST
|
||||
@Path("/export/pgn")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array("application/x-chess-pgn"))
|
||||
def exportPgn(ctx: GameContext): String
|
||||
@@ -1,23 +1,11 @@
|
||||
package de.nowchess.chess.config
|
||||
|
||||
import com.fasterxml.jackson.core.Version
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.Square
|
||||
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||
import jakarta.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class JacksonConfig extends ObjectMapperCustomizer:
|
||||
def customize(mapper: ObjectMapper): Unit =
|
||||
mapper.registerModule(new DefaultScalaModule() {
|
||||
override def version(): Version =
|
||||
// scalafix:off DisableSyntax.null
|
||||
new Version(2, 21, 1, null, "com.fasterxml.jackson.module", "jackson-module-scala")
|
||||
// scalafix:on DisableSyntax.null
|
||||
})
|
||||
val squareModule = new SimpleModule()
|
||||
squareModule.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
squareModule.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mapper.registerModule(squareModule)
|
||||
mapper.registerModule(DefaultScalaModule)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package de.nowchess.chess.config
|
||||
|
||||
import de.nowchess.api.board.{CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.dto.*
|
||||
import de.nowchess.api.game.{DrawReason, GameContext, GameResult}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
|
||||
@RegisterForReflection(
|
||||
@@ -21,19 +18,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
classOf[LegalMovesResponseDto],
|
||||
classOf[OkResponseDto],
|
||||
classOf[PlayerInfoDto],
|
||||
classOf[GameContext],
|
||||
classOf[Color],
|
||||
classOf[Piece],
|
||||
classOf[PieceType],
|
||||
classOf[CastlingRights],
|
||||
classOf[Square],
|
||||
classOf[File],
|
||||
classOf[Rank],
|
||||
classOf[Move],
|
||||
classOf[MoveType],
|
||||
classOf[PromotionPiece],
|
||||
classOf[GameResult],
|
||||
classOf[DrawReason],
|
||||
),
|
||||
)
|
||||
class NativeReflectionConfig
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package de.nowchess.chess.config
|
||||
|
||||
import com.fasterxml.jackson.databind.{DeserializationContext, KeyDeserializer}
|
||||
import de.nowchess.api.board.Square
|
||||
|
||||
class SquareKeyDeserializer extends KeyDeserializer:
|
||||
override def deserializeKey(key: String, ctx: DeserializationContext): AnyRef =
|
||||
Square.fromAlgebraic(key).orNull
|
||||
@@ -1,9 +0,0 @@
|
||||
package de.nowchess.chess.config
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider}
|
||||
import de.nowchess.api.board.Square
|
||||
|
||||
class SquareKeySerializer extends JsonSerializer[Square]:
|
||||
override def serialize(value: Square, gen: JsonGenerator, provider: SerializerProvider): Unit =
|
||||
gen.writeFieldName(value.toString)
|
||||
@@ -7,7 +7,7 @@ import de.nowchess.api.player.{PlayerId, PlayerInfo}
|
||||
import de.nowchess.chess.controller.Parser
|
||||
import de.nowchess.chess.observer.*
|
||||
import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult}
|
||||
import de.nowchess.api.io.{GameContextExport, GameContextImport}
|
||||
import de.nowchess.io.{GameContextExport, GameContextImport}
|
||||
import de.nowchess.rules.RuleSet
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
|
||||
|
||||
@@ -6,18 +6,18 @@ import de.nowchess.api.dto.*
|
||||
import de.nowchess.api.game.{DrawReason, GameContext, GameResult}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.player.{PlayerId, PlayerInfo}
|
||||
import de.nowchess.chess.client.IoServiceClient
|
||||
import de.nowchess.chess.controller.Parser
|
||||
import de.nowchess.chess.engine.GameEngine
|
||||
import de.nowchess.chess.exception.{BadRequestException, GameNotFoundException}
|
||||
import de.nowchess.chess.observer.*
|
||||
import de.nowchess.chess.registry.{GameEntry, GameRegistry}
|
||||
import de.nowchess.io.fen.{FenExporter, FenParser}
|
||||
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||
import io.smallrye.mutiny.Multi
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.{MediaType, Response}
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import scala.compiletime.uninitialized
|
||||
@@ -32,10 +32,6 @@ class GameResource:
|
||||
|
||||
@Inject
|
||||
var objectMapper: ObjectMapper = uninitialized
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
var ioClient: IoServiceClient = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
private val DefaultWhite = PlayerInfo(PlayerId("p1"), "Player 1")
|
||||
@@ -86,8 +82,16 @@ class GameResource:
|
||||
private def toGameStateDto(entry: GameEntry): GameStateDto =
|
||||
val ctx = entry.engine.context
|
||||
GameStateDto(
|
||||
fen = ioClient.exportFen(ctx),
|
||||
pgn = ioClient.exportPgn(ctx),
|
||||
fen = FenExporter.exportGameContext(ctx),
|
||||
pgn = PgnExporter.exportGame(
|
||||
Map(
|
||||
"Event" -> "NowChess game",
|
||||
"White" -> entry.white.displayName,
|
||||
"Black" -> entry.black.displayName,
|
||||
"Result" -> "*",
|
||||
),
|
||||
ctx.moves,
|
||||
),
|
||||
turn = ctx.turn.label.toLowerCase,
|
||||
status = statusOf(entry),
|
||||
winner = ctx.result.collect { case GameResult.Win(c) => c.label.toLowerCase },
|
||||
@@ -259,7 +263,9 @@ class GameResource:
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def importFen(body: ImportFenRequestDto): Response =
|
||||
val ctx = ioClient.importFen(ImportFenRequest(body.fen))
|
||||
val ctx = FenParser.parseFen(body.fen) match
|
||||
case Left(err) => throw BadRequestException("INVALID_FEN", err, Some("fen"))
|
||||
case Right(ctx) => ctx
|
||||
val white = playerInfoFrom(body.white, DefaultWhite)
|
||||
val black = playerInfoFrom(body.black, DefaultBlack)
|
||||
val entry = newEntry(ctx, white, black)
|
||||
@@ -271,8 +277,11 @@ class GameResource:
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def importPgn(body: ImportPgnRequestDto): Response =
|
||||
val ctx = ioClient.importPgn(ImportPgnRequest(body.pgn))
|
||||
val entry = newEntry(ctx, DefaultWhite, DefaultBlack)
|
||||
val engine = GameEngine()
|
||||
engine.loadGame(PgnParser, body.pgn) match
|
||||
case Left(err) => throw BadRequestException("INVALID_PGN", err, Some("pgn"))
|
||||
case Right(_) => ()
|
||||
val entry = GameEntry(registry.generateId(), engine, DefaultWhite, DefaultBlack)
|
||||
registry.store(entry)
|
||||
created(toGameFullDto(entry))
|
||||
|
||||
@@ -281,12 +290,21 @@ class GameResource:
|
||||
@Produces(Array(MediaType.TEXT_PLAIN))
|
||||
def exportFen(@PathParam("gameId") gameId: String): Response =
|
||||
val entry = registry.get(gameId).getOrElse(throw GameNotFoundException(gameId))
|
||||
ok(ioClient.exportFen(entry.engine.context))
|
||||
ok(FenExporter.exportGameContext(entry.engine.context))
|
||||
|
||||
@GET
|
||||
@Path("/{gameId}/export/pgn")
|
||||
@Produces(Array("application/x-chess-pgn"))
|
||||
def exportPgn(@PathParam("gameId") gameId: String): Response =
|
||||
val entry = registry.get(gameId).getOrElse(throw GameNotFoundException(gameId))
|
||||
ok(ioClient.exportPgn(entry.engine.context))
|
||||
val pgn = PgnExporter.exportGame(
|
||||
Map(
|
||||
"Event" -> "NowChess game",
|
||||
"White" -> entry.white.displayName,
|
||||
"Black" -> entry.black.displayName,
|
||||
"Result" -> "*",
|
||||
),
|
||||
entry.engine.context.moves,
|
||||
)
|
||||
ok(pgn)
|
||||
// scalafix:on DisableSyntax.throw
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import de.nowchess.api.board.{Board, Color, File, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.chess.observer.{GameEvent, InvalidMoveEvent, InvalidMoveReason, MoveRedoneEvent, Observer}
|
||||
import de.nowchess.api.io.GameContextImport
|
||||
import de.nowchess.io.GameContextImport
|
||||
import de.nowchess.rules.RuleSet
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package de.nowchess.chess.engine
|
||||
|
||||
import de.nowchess.chess.observer.{GameEvent, Observer}
|
||||
import scala.collection.mutable
|
||||
import de.nowchess.api.board.{Board, Color}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.chess.observer.{GameEvent, Observer, PgnLoadedEvent}
|
||||
import de.nowchess.io.pgn.PgnParser
|
||||
import de.nowchess.io.fen.FenParser
|
||||
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||
import de.nowchess.io.pgn.PgnExporter
|
||||
import de.nowchess.io.fen.FenExporter
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
class GameEngineLoadGameTest extends AnyFunSuite with Matchers:
|
||||
|
||||
test("loadGame with PgnParser: loads valid PGN and enables undo/redo"):
|
||||
|
||||
+1
-22
@@ -1,19 +1,11 @@
|
||||
package de.nowchess.chess.resource
|
||||
|
||||
import de.nowchess.api.dto.*
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.chess.client.IoServiceClient
|
||||
import de.nowchess.chess.exception.BadRequestException
|
||||
import de.nowchess.io.fen.FenExporter
|
||||
import de.nowchess.io.pgn.PgnParser
|
||||
import io.quarkus.test.InjectMock
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import jakarta.inject.Inject
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||
import org.junit.jupiter.api.{BeforeEach, DisplayName, Test}
|
||||
import org.junit.jupiter.api.{DisplayName, Test}
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.when
|
||||
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
@@ -25,19 +17,6 @@ class GameResourceIntegrationTest:
|
||||
@Inject
|
||||
var resource: GameResource = uninitialized
|
||||
|
||||
@InjectMock
|
||||
@RestClient
|
||||
var ioClient: IoServiceClient = uninitialized
|
||||
|
||||
@BeforeEach
|
||||
def setupMocks(): Unit =
|
||||
when(ioClient.importFen(any())).thenReturn(GameContext.initial)
|
||||
when(ioClient.importPgn(any())).thenReturn(
|
||||
PgnParser.importGameContext("1. e4 c5").toOption.get,
|
||||
)
|
||||
when(ioClient.exportFen(any())).thenReturn(FenExporter.exportGameContext(GameContext.initial))
|
||||
when(ioClient.exportPgn(any())).thenReturn("1. e4 c5")
|
||||
|
||||
@Test
|
||||
@DisplayName("createGame returns 201")
|
||||
def testCreateGame(): Unit =
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
MAJOR=0
|
||||
MINOR=19
|
||||
MINOR=16
|
||||
PATCH=0
|
||||
|
||||
@@ -76,42 +76,3 @@
|
||||
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
||||
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-29 JSON - Cherry Picked ([#28](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/28)) ([dbcafd2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dbcafd286993e0604a6fa286c5543581a149439e))
|
||||
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
||||
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-29 JSON - Cherry Picked ([#28](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/28)) ([dbcafd2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dbcafd286993e0604a6fa286c5543581a149439e))
|
||||
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
||||
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([9b51852](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/9b5185298e9e721e6103ea8372ca29073913775c))
|
||||
## (2026-04-22)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-29 JSON - Cherry Picked ([#28](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/28)) ([dbcafd2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dbcafd286993e0604a6fa286c5543581a149439e))
|
||||
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
||||
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([9b51852](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/9b5185298e9e721e6103ea8372ca29073913775c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([fb5c61d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fb5c61de63292e5d70c06304cba2193686aa1607))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
id("scala")
|
||||
id("org.scoverage") version "8.1"
|
||||
id("io.quarkus")
|
||||
}
|
||||
|
||||
group = "de.nowchess"
|
||||
@@ -29,10 +28,6 @@ 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 {
|
||||
|
||||
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||
@@ -58,51 +53,19 @@ dependencies {
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${versions["JACKSON"]!!}")
|
||||
|
||||
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||
implementation("io.quarkus:quarkus-rest")
|
||||
implementation("io.quarkus:quarkus-rest-jackson")
|
||||
implementation("io.quarkus:quarkus-arc")
|
||||
implementation("io.quarkus:quarkus-config-yaml")
|
||||
implementation("io.quarkus:quarkus-smallrye-health")
|
||||
implementation("io.quarkus:quarkus-smallrye-openapi")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
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.rest-assured:rest-assured")
|
||||
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
}
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
includeEngines("scalatest")
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
events("skipped", "failed")
|
||||
}
|
||||
}
|
||||
finalizedBy(tasks.reportScoverage)
|
||||
@@ -110,6 +73,3 @@ tasks.test {
|
||||
tasks.reportScoverage {
|
||||
dependsOn(tasks.test)
|
||||
}
|
||||
tasks.jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
quarkus:
|
||||
http:
|
||||
port: 8081
|
||||
application:
|
||||
name: nowchess-io
|
||||
smallrye-openapi:
|
||||
info-title: NowChess IO Service
|
||||
info-version: 1.0.0
|
||||
info-description: Chess notation import and export — FEN and PGN
|
||||
path: /openapi
|
||||
swagger-ui:
|
||||
always-include: true
|
||||
path: /swagger-ui
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package de.nowchess.api.io
|
||||
package de.nowchess.io
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
|
||||
trait GameContextExport:
|
||||
|
||||
def exportGameContext(context: GameContext): String
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package de.nowchess.api.io
|
||||
package de.nowchess.io
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
|
||||
trait GameContextImport:
|
||||
|
||||
def importGameContext(input: String): Either[String, GameContext]
|
||||
@@ -1,8 +1,6 @@
|
||||
package de.nowchess.io
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.{GameContextExport, GameContextImport}
|
||||
|
||||
import java.nio.file.{Files, Path}
|
||||
import java.nio.charset.StandardCharsets
|
||||
import scala.util.Try
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.nowchess.io.fen
|
||||
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextExport
|
||||
import de.nowchess.io.GameContextExport
|
||||
|
||||
object FenExporter extends GameContextExport:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.nowchess.io.fen
|
||||
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextImport
|
||||
import de.nowchess.io.GameContextImport
|
||||
|
||||
object FenParser extends GameContextImport:
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@ package de.nowchess.io.fen
|
||||
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.game.GameContext
|
||||
|
||||
import de.nowchess.io.GameContextImport
|
||||
import scala.util.parsing.combinator.RegexParsers
|
||||
import FenParserSupport.*
|
||||
import de.nowchess.api.io.GameContextImport
|
||||
|
||||
object FenParserCombinators extends RegexParsers with GameContextImport:
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import fastparse.*
|
||||
import fastparse.NoWhitespace.*
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.io.GameContextImport
|
||||
import FenParserSupport.*
|
||||
import de.nowchess.api.io.GameContextImport
|
||||
|
||||
object FenParserFastParse extends GameContextImport:
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@ import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextExport
|
||||
import de.nowchess.io.GameContextExport
|
||||
import de.nowchess.io.pgn.PgnExporter
|
||||
|
||||
import java.time.{LocalDate, ZoneId, ZonedDateTime}
|
||||
|
||||
/** Exports a GameContext to a comprehensive JSON format using Jackson.
|
||||
|
||||
@@ -5,8 +5,7 @@ import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextImport
|
||||
|
||||
import de.nowchess.io.GameContextImport
|
||||
import scala.util.Try
|
||||
|
||||
/** Imports a GameContext from JSON format using Jackson.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.databind.{DeserializationContext, KeyDeserializer}
|
||||
import de.nowchess.api.board.Square
|
||||
|
||||
class SquareKeyDeserializer extends KeyDeserializer:
|
||||
override def deserializeKey(key: String, ctx: DeserializationContext): AnyRef =
|
||||
Square.fromAlgebraic(key).orNull
|
||||
@@ -1,9 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider}
|
||||
import de.nowchess.api.board.Square
|
||||
|
||||
class SquareKeySerializer extends JsonSerializer[Square]:
|
||||
override def serialize(value: Square, gen: JsonGenerator, provider: SerializerProvider): Unit =
|
||||
gen.writeFieldName(value.toString)
|
||||
@@ -3,7 +3,7 @@ package de.nowchess.io.pgn
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextExport
|
||||
import de.nowchess.io.GameContextExport
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
|
||||
object PgnExporter extends GameContextExport:
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.nowchess.io.pgn
|
||||
import de.nowchess.api.board.*
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextImport
|
||||
import de.nowchess.io.GameContextImport
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
|
||||
/** A parsed PGN game containing headers and the resolved move list. */
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package de.nowchess.io.service.config
|
||||
|
||||
import com.fasterxml.jackson.core.Version
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.Square
|
||||
import de.nowchess.io.json.{SquareKeyDeserializer, SquareKeySerializer}
|
||||
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||
import jakarta.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class JacksonConfig extends ObjectMapperCustomizer:
|
||||
def customize(mapper: ObjectMapper): Unit =
|
||||
mapper.registerModule(new DefaultScalaModule() {
|
||||
override def version(): Version =
|
||||
// scalafix:off DisableSyntax.null
|
||||
new Version(2, 21, 1, null, "com.fasterxml.jackson.module", "jackson-module-scala")
|
||||
// scalafix:on DisableSyntax.null
|
||||
})
|
||||
val squareModule = new SimpleModule()
|
||||
squareModule.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
squareModule.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mapper.registerModule(squareModule)
|
||||
@@ -1,29 +0,0 @@
|
||||
package de.nowchess.io.service.config
|
||||
|
||||
import de.nowchess.api.board.{CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.{DrawReason, GameContext, GameResult}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.io.service.dto.{ImportFenRequest, ImportPgnRequest, IoErrorDto}
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
|
||||
@RegisterForReflection(
|
||||
targets = Array(
|
||||
classOf[ImportFenRequest],
|
||||
classOf[ImportPgnRequest],
|
||||
classOf[IoErrorDto],
|
||||
classOf[GameContext],
|
||||
classOf[GameResult],
|
||||
classOf[DrawReason],
|
||||
classOf[Color],
|
||||
classOf[Piece],
|
||||
classOf[PieceType],
|
||||
classOf[CastlingRights],
|
||||
classOf[Square],
|
||||
classOf[File],
|
||||
classOf[Rank],
|
||||
classOf[Move],
|
||||
classOf[MoveType],
|
||||
classOf[PromotionPiece],
|
||||
),
|
||||
)
|
||||
class NativeReflectionConfig
|
||||
@@ -1,3 +0,0 @@
|
||||
package de.nowchess.io.service.dto
|
||||
|
||||
case class ImportFenRequest(fen: String)
|
||||
@@ -1,3 +0,0 @@
|
||||
package de.nowchess.io.service.dto
|
||||
|
||||
case class ImportPgnRequest(pgn: String)
|
||||
@@ -1,3 +0,0 @@
|
||||
package de.nowchess.io.service.dto
|
||||
|
||||
case class IoErrorDto(code: String, message: String)
|
||||
@@ -1,77 +0,0 @@
|
||||
package de.nowchess.io.service.resource
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.io.fen.{FenExporter, FenParser}
|
||||
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||
import de.nowchess.io.service.dto.{ImportFenRequest, ImportPgnRequest, IoErrorDto}
|
||||
import io.smallrye.mutiny.Uni
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.{MediaType, Response}
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation
|
||||
import org.eclipse.microprofile.openapi.annotations.media.{Content, Schema}
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.{APIResponse, APIResponses}
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag
|
||||
|
||||
@Path("/io")
|
||||
@ApplicationScoped
|
||||
@Tag(name = "IO", description = "Chess notation import and export")
|
||||
class IoResource:
|
||||
|
||||
@POST
|
||||
@Path("/import/fen")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@Operation(summary = "Import FEN", description = "Parse a FEN string into a GameContext")
|
||||
@APIResponses(
|
||||
Array(
|
||||
new APIResponse(responseCode = "200", description = "Parsed GameContext"),
|
||||
new APIResponse(responseCode = "400", description = "Invalid FEN"),
|
||||
),
|
||||
)
|
||||
def importFen(body: ImportFenRequest): Uni[Response] =
|
||||
Uni.createFrom().item {
|
||||
FenParser.parseFen(body.fen) match
|
||||
case Left(err) =>
|
||||
Response.status(400).entity(IoErrorDto("INVALID_FEN", err)).build()
|
||||
case Right(ctx) =>
|
||||
Response.ok(ctx).build()
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/import/pgn")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@Operation(summary = "Import PGN", description = "Parse a PGN string into a GameContext")
|
||||
@APIResponses(
|
||||
Array(
|
||||
new APIResponse(responseCode = "200", description = "Parsed GameContext"),
|
||||
new APIResponse(responseCode = "400", description = "Invalid PGN"),
|
||||
),
|
||||
)
|
||||
def importPgn(body: ImportPgnRequest): Uni[Response] =
|
||||
Uni.createFrom().item {
|
||||
PgnParser.importGameContext(body.pgn) match
|
||||
case Left(err) =>
|
||||
Response.status(400).entity(IoErrorDto("INVALID_PGN", err)).build()
|
||||
case Right(ctx) =>
|
||||
Response.ok(ctx).build()
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/export/fen")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.TEXT_PLAIN))
|
||||
@Operation(summary = "Export FEN", description = "Serialize a GameContext to FEN notation")
|
||||
@APIResponse(responseCode = "200", description = "FEN string")
|
||||
def exportFen(ctx: GameContext): Uni[Response] =
|
||||
Uni.createFrom().item(Response.ok(FenExporter.exportGameContext(ctx)).build())
|
||||
|
||||
@POST
|
||||
@Path("/export/pgn")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array("application/x-chess-pgn"))
|
||||
@Operation(summary = "Export PGN", description = "Serialize a GameContext to PGN notation")
|
||||
@APIResponse(responseCode = "200", description = "PGN text")
|
||||
def exportPgn(ctx: GameContext): Uni[Response] =
|
||||
Uni.createFrom().item(Response.ok(PgnExporter.exportGameContext(ctx)).build())
|
||||
@@ -1,14 +1,13 @@
|
||||
package de.nowchess.io
|
||||
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.io.GameContextExport
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import de.nowchess.api.move.Move
|
||||
import de.nowchess.io.json.{JsonExporter, JsonParser}
|
||||
import java.nio.file.{Files, Paths}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
import java.nio.file.{Files, Paths}
|
||||
import scala.util.Using
|
||||
|
||||
class GameFileServiceSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
|
||||
// Edge cases with defaults
|
||||
test("parse invalid turn color returns error") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "Invalid", "board": []},
|
||||
"moves": []
|
||||
@@ -141,7 +141,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse invalid piece type filters it out") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
@@ -158,7 +158,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse invalid color in board filters piece") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
@@ -175,7 +175,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse with missing turn uses default") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"board": []},
|
||||
"moves": []
|
||||
@@ -187,7 +187,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse with missing board uses empty") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White"},
|
||||
"moves": []
|
||||
@@ -199,7 +199,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse with missing moves uses empty list") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []}
|
||||
}"""
|
||||
@@ -210,7 +210,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse invalid square in board filters it") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
@@ -227,7 +227,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse all valid piece types") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
@@ -255,7 +255,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse with all castling rights false") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
@@ -278,7 +278,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
|
||||
// Move type parsing tests
|
||||
test("parse all move type variations") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {"event": "Game", "result": "*"},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [
|
||||
@@ -303,7 +303,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse invalid move type defaults to None") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {"event": "Game"},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "e2", "to": "e4", "type": {"type": "unknown"}}]
|
||||
@@ -313,7 +313,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse promotion with invalid piece uses default") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "invalid"}}]
|
||||
@@ -323,7 +323,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse move with invalid from/to skips it") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "e2", "to": "invalid", "type": {"type": "normal"}}]
|
||||
@@ -335,7 +335,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse normal move with isCapture true") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "e4", "to": "d5", "type": {"type": "normal", "isCapture": true}}]
|
||||
@@ -348,7 +348,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse board with invalid pieces filters them") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
@@ -366,7 +366,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
}
|
||||
|
||||
test("parse with empty board") {
|
||||
val json = """{
|
||||
val json = """{
|
||||
"metadata": {"event": "Game", "players": {"white": "A", "black": "B"}, "date": "2026-04-06", "result": "*"},
|
||||
"gameState": {
|
||||
"board": [],
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.core.`type`.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class SquareKeyDeserializerTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def mapper: ObjectMapper =
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
m.registerModule(DefaultScalaModule)
|
||||
m.registerModule(mod)
|
||||
m
|
||||
|
||||
private def readMap(json: String): Map[Square, Int] =
|
||||
mapper.readValue(json, new TypeReference[Map[Square, Int]] {})
|
||||
|
||||
test("deserializes valid algebraic key") {
|
||||
val result = readMap("""{"e4":1}""")
|
||||
result(Square(File.E, Rank.R4)) shouldBe 1
|
||||
}
|
||||
|
||||
test("deserializes a1 corner") {
|
||||
val result = readMap("""{"a1":1}""")
|
||||
result(Square(File.A, Rank.R1)) shouldBe 1
|
||||
}
|
||||
|
||||
test("deserializes h8 corner") {
|
||||
val result = readMap("""{"h8":1}""")
|
||||
result(Square(File.H, Rank.R8)) shouldBe 1
|
||||
}
|
||||
|
||||
test("deserializes multiple squares") {
|
||||
val result = readMap("""{"a1":1,"h8":2,"e4":3}""")
|
||||
result(Square(File.A, Rank.R1)) shouldBe 1
|
||||
result(Square(File.H, Rank.R8)) shouldBe 2
|
||||
result(Square(File.E, Rank.R4)) shouldBe 3
|
||||
}
|
||||
|
||||
// scalafix:off DisableSyntax.null
|
||||
test("deserializeKey returns null for invalid square") {
|
||||
new SquareKeyDeserializer().deserializeKey("invalid", null) shouldBe null
|
||||
}
|
||||
|
||||
test("deserializeKey returns null for wrong-length key") {
|
||||
new SquareKeyDeserializer().deserializeKey("e44", null) shouldBe null
|
||||
}
|
||||
|
||||
test("deserializeKey returns null for bad file") {
|
||||
new SquareKeyDeserializer().deserializeKey("z4", null) shouldBe null
|
||||
}
|
||||
|
||||
test("deserializeKey returns null for bad rank") {
|
||||
new SquareKeyDeserializer().deserializeKey("e9", null) shouldBe null
|
||||
}
|
||||
// scalafix:on DisableSyntax.null
|
||||
@@ -1,50 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.core.`type`.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class SquareKeySerializerTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def mapper: ObjectMapper =
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
m.registerModule(DefaultScalaModule)
|
||||
m.registerModule(mod)
|
||||
m
|
||||
|
||||
test("serializes square as algebraic notation") {
|
||||
val json = mapper.writeValueAsString(Map(Square(File.E, Rank.R4) -> 1))
|
||||
json should include("\"e4\"")
|
||||
}
|
||||
|
||||
test("serializes a1 corner") {
|
||||
val json = mapper.writeValueAsString(Map(Square(File.A, Rank.R1) -> 1))
|
||||
json should include("\"a1\"")
|
||||
}
|
||||
|
||||
test("serializes h8 corner") {
|
||||
val json = mapper.writeValueAsString(Map(Square(File.H, Rank.R8) -> 1))
|
||||
json should include("\"h8\"")
|
||||
}
|
||||
|
||||
test("round-trips with SquareKeyDeserializer") {
|
||||
val rt = {
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
m.registerModule(DefaultScalaModule)
|
||||
m.registerModule(mod)
|
||||
m
|
||||
}
|
||||
val original = Map(Square(File.D, Rank.R5) -> 99)
|
||||
val json = rt.writeValueAsString(original)
|
||||
val result = rt.readValue(json, new TypeReference[Map[Square, Int]] {})
|
||||
result shouldBe original
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package de.nowchess.io.service.resource
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.Square
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.io.json.{SquareKeyDeserializer, SquareKeySerializer}
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import io.restassured.http.ContentType
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class IoResourceTest:
|
||||
private lazy val testMapper: ObjectMapper =
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
m.registerModule(new DefaultScalaModule())
|
||||
m.registerModule(mod)
|
||||
m
|
||||
|
||||
private def contextJson(ctx: GameContext): String = testMapper.writeValueAsString(ctx)
|
||||
|
||||
@Test
|
||||
def importFenReturns200(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"fen":"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"}""")
|
||||
.post("/io/import/fen")
|
||||
assertEquals(200, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def importFenInvalidReturns400(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"fen":"not-a-fen"}""")
|
||||
.post("/io/import/fen")
|
||||
assertEquals(400, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def importPgnReturns200(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"pgn":"1. e4 e5"}""")
|
||||
.post("/io/import/pgn")
|
||||
assertEquals(200, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def importPgnInvalidReturns400(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"pgn":"not valid pgn !!!###"}""")
|
||||
.post("/io/import/pgn")
|
||||
assertEquals(400, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def exportFenReturns200WithFen(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextJson(GameContext.initial))
|
||||
.post("/io/export/fen")
|
||||
assertEquals(200, resp.statusCode())
|
||||
assertTrue(resp.getBody.asString().contains("rnbqkbnr"))
|
||||
|
||||
@Test
|
||||
def exportPgnReturns200(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextJson(GameContext.initial))
|
||||
.post("/io/export/pgn")
|
||||
assertEquals(200, resp.statusCode())
|
||||
@@ -1,3 +1,3 @@
|
||||
MAJOR=0
|
||||
MINOR=13
|
||||
MINOR=10
|
||||
PATCH=0
|
||||
|
||||
@@ -79,42 +79,3 @@
|
||||
### Bug Fixes
|
||||
|
||||
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
||||
## (2026-04-21)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
||||
## (2026-04-22)
|
||||
|
||||
### Features
|
||||
|
||||
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id("scala")
|
||||
id("org.scoverage") version "8.1"
|
||||
id("io.quarkus")
|
||||
}
|
||||
|
||||
group = "de.nowchess"
|
||||
@@ -25,6 +26,10 @@ 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 {
|
||||
|
||||
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||
@@ -40,20 +45,56 @@ dependencies {
|
||||
|
||||
implementation(project(":modules:api"))
|
||||
|
||||
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||
implementation("io.quarkus:quarkus-rest")
|
||||
implementation("io.quarkus:quarkus-hibernate-orm")
|
||||
implementation("io.quarkus:quarkus-rest-client-jackson")
|
||||
implementation("io.quarkus:quarkus-rest-client")
|
||||
implementation("io.quarkus:quarkus-rest-jackson")
|
||||
implementation("io.quarkus:quarkus-config-yaml")
|
||||
implementation("io.quarkus:quarkus-smallrye-fault-tolerance")
|
||||
implementation("io.quarkus:quarkus-smallrye-jwt")
|
||||
implementation("io.quarkus:quarkus-smallrye-health")
|
||||
implementation("io.quarkus:quarkus-micrometer")
|
||||
implementation("io.quarkus:quarkus-arc")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||
|
||||
testImplementation(project(":modules:io"))
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
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.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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<Jar>().configureEach {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
includeEngines("scalatest")
|
||||
includeEngines("scalatest", "junit-jupiter")
|
||||
testLogging {
|
||||
events("skipped", "failed")
|
||||
events("passed", "skipped", "failed")
|
||||
}
|
||||
}
|
||||
finalizedBy(tasks.reportScoverage)
|
||||
@@ -61,3 +102,7 @@ tasks.test {
|
||||
tasks.reportScoverage {
|
||||
dependsOn(tasks.test)
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
+5
-5
@@ -3,17 +3,17 @@
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew :modules:io:build -Dquarkus.native.enabled=true
|
||||
# ./gradlew build -Dquarkus.native.enabled=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.native -t quarkus/backio .
|
||||
# docker build -f src/main/docker/Dockerfile.native -t quarkus/backcore .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/backio
|
||||
# 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.
|
||||
# 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
|
||||
@@ -21,7 +21,7 @@ WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root --chmod=0755 modules/io/build/*-runner /work/application
|
||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
@@ -0,0 +1,5 @@
|
||||
quarkus:
|
||||
http:
|
||||
port: 8081
|
||||
application:
|
||||
name: rule-service
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.nowchess.rules.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,128 @@
|
||||
package de.nowchess.rules.dto
|
||||
|
||||
import de.nowchess.api.board.{Board, CastlingRights, Color, Piece, PieceType, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
|
||||
object DtoMapper:
|
||||
|
||||
def toColor(s: String): Either[String, Color] = s match
|
||||
case "White" => Right(Color.White)
|
||||
case "Black" => Right(Color.Black)
|
||||
case other => Left(s"Unknown color: $other")
|
||||
|
||||
def toPieceType(s: String): Either[String, PieceType] = s match
|
||||
case "Pawn" => Right(PieceType.Pawn)
|
||||
case "Knight" => Right(PieceType.Knight)
|
||||
case "Bishop" => Right(PieceType.Bishop)
|
||||
case "Rook" => Right(PieceType.Rook)
|
||||
case "Queen" => Right(PieceType.Queen)
|
||||
case "King" => Right(PieceType.King)
|
||||
case other => Left(s"Unknown piece type: $other")
|
||||
|
||||
def toSquare(s: String): Either[String, Square] =
|
||||
Square.fromAlgebraic(s).toRight(s"Invalid square: $s")
|
||||
|
||||
def toMoveType(dto: MoveDto): Either[String, MoveType] = dto.moveType match
|
||||
case "normal" => Right(MoveType.Normal(isCapture = false))
|
||||
case "capture" => Right(MoveType.Normal(isCapture = true))
|
||||
case "castleKingside" => Right(MoveType.CastleKingside)
|
||||
case "castleQueenside" => Right(MoveType.CastleQueenside)
|
||||
case "enPassant" => Right(MoveType.EnPassant)
|
||||
case "promotion" =>
|
||||
dto.promotionPiece.toRight("Missing promotion piece").flatMap(toPromotionPiece).map(MoveType.Promotion(_))
|
||||
case other => Left(s"Unknown move type: $other")
|
||||
|
||||
def toMove(dto: MoveDto): Either[String, Move] =
|
||||
for
|
||||
from <- toSquare(dto.from)
|
||||
to <- toSquare(dto.to)
|
||||
moveType <- toMoveType(dto)
|
||||
yield Move(from, to, moveType)
|
||||
|
||||
def toBoard(pieces: List[PieceOnSquareDto]): Either[String, Board] =
|
||||
sequenceList(pieces.map(toPieceOnSquare)).map(entries => Board(entries.toMap))
|
||||
|
||||
def toGameContext(dto: GameContextDto): Either[String, GameContext] =
|
||||
for
|
||||
board <- toBoard(dto.board)
|
||||
turn <- toColor(dto.turn)
|
||||
epSquare <- sequenceOpt(dto.enPassantSquare.map(toSquare))
|
||||
moves <- sequenceList(dto.moves.map(toMove))
|
||||
initialBoard <- toBoard(dto.initialBoard)
|
||||
yield GameContext(
|
||||
board = board,
|
||||
turn = turn,
|
||||
castlingRights = toCastlingRights(dto.castlingRights),
|
||||
enPassantSquare = epSquare,
|
||||
halfMoveClock = dto.halfMoveClock,
|
||||
moves = moves,
|
||||
initialBoard = initialBoard,
|
||||
)
|
||||
|
||||
def fromMove(move: Move): MoveDto =
|
||||
val (moveType, promotionPiece) = fromMoveType(move.moveType)
|
||||
MoveDto(move.from.toString, move.to.toString, moveType, promotionPiece)
|
||||
|
||||
def fromBoard(board: Board): List[PieceOnSquareDto] =
|
||||
board.pieces.toList.map { case (sq, p) =>
|
||||
PieceOnSquareDto(sq.toString, p.color.label, p.pieceType.label)
|
||||
}
|
||||
|
||||
def fromGameContext(ctx: GameContext): GameContextDto =
|
||||
GameContextDto(
|
||||
board = fromBoard(ctx.board),
|
||||
turn = ctx.turn.label,
|
||||
castlingRights = fromCastlingRights(ctx.castlingRights),
|
||||
enPassantSquare = ctx.enPassantSquare.map(_.toString),
|
||||
halfMoveClock = ctx.halfMoveClock,
|
||||
moves = ctx.moves.map(fromMove),
|
||||
initialBoard = fromBoard(ctx.initialBoard),
|
||||
)
|
||||
|
||||
private def toPromotionPiece(s: String): Either[String, PromotionPiece] = s match
|
||||
case "Queen" => Right(PromotionPiece.Queen)
|
||||
case "Rook" => Right(PromotionPiece.Rook)
|
||||
case "Bishop" => Right(PromotionPiece.Bishop)
|
||||
case "Knight" => Right(PromotionPiece.Knight)
|
||||
case other => Left(s"Unknown promotion piece: $other")
|
||||
|
||||
private def fromMoveType(mt: MoveType): (String, Option[String]) = mt match
|
||||
case MoveType.Normal(false) => ("normal", None)
|
||||
case MoveType.Normal(true) => ("capture", None)
|
||||
case MoveType.CastleKingside => ("castleKingside", None)
|
||||
case MoveType.CastleQueenside => ("castleQueenside", None)
|
||||
case MoveType.EnPassant => ("enPassant", None)
|
||||
case MoveType.Promotion(pp) => ("promotion", Some(fromPromotionPiece(pp)))
|
||||
|
||||
private def fromPromotionPiece(pp: PromotionPiece): String = pp match
|
||||
case PromotionPiece.Queen => "Queen"
|
||||
case PromotionPiece.Rook => "Rook"
|
||||
case PromotionPiece.Bishop => "Bishop"
|
||||
case PromotionPiece.Knight => "Knight"
|
||||
|
||||
private def toCastlingRights(dto: CastlingRightsDto): CastlingRights =
|
||||
CastlingRights(dto.whiteKingSide, dto.whiteQueenSide, dto.blackKingSide, dto.blackQueenSide)
|
||||
|
||||
private def fromCastlingRights(cr: CastlingRights): CastlingRightsDto =
|
||||
CastlingRightsDto(cr.whiteKingSide, cr.whiteQueenSide, cr.blackKingSide, cr.blackQueenSide)
|
||||
|
||||
private def toPieceOnSquare(dto: PieceOnSquareDto): Either[String, (Square, Piece)] =
|
||||
for
|
||||
sq <- toSquare(dto.square)
|
||||
color <- toColor(dto.color)
|
||||
pieceType <- toPieceType(dto.pieceType)
|
||||
yield sq -> Piece(color, pieceType)
|
||||
|
||||
private def sequenceList[R](list: List[Either[String, R]]): Either[String, List[R]] =
|
||||
list.foldLeft[Either[String, List[R]]](Right(List.empty)) {
|
||||
case (Right(acc), Right(v)) => Right(acc :+ v)
|
||||
case (Left(e), _) => Left(e)
|
||||
case (_, Left(e)) => Left(e)
|
||||
}
|
||||
|
||||
private def sequenceOpt[R](opt: Option[Either[String, R]]): Either[String, Option[R]] =
|
||||
opt match
|
||||
case None => Right(None)
|
||||
case Some(Right(v)) => Right(Some(v))
|
||||
case Some(Left(e)) => Left(e)
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.nowchess.rules.dto
|
||||
|
||||
case class PieceOnSquareDto(square: String, color: String, pieceType: String)
|
||||
|
||||
case class CastlingRightsDto(
|
||||
whiteKingSide: Boolean,
|
||||
whiteQueenSide: Boolean,
|
||||
blackKingSide: Boolean,
|
||||
blackQueenSide: Boolean,
|
||||
)
|
||||
|
||||
case class MoveDto(
|
||||
from: String,
|
||||
to: String,
|
||||
moveType: String,
|
||||
promotionPiece: Option[String],
|
||||
)
|
||||
|
||||
case class GameContextDto(
|
||||
board: List[PieceOnSquareDto],
|
||||
turn: String,
|
||||
castlingRights: CastlingRightsDto,
|
||||
enPassantSquare: Option[String],
|
||||
halfMoveClock: Int,
|
||||
moves: List[MoveDto],
|
||||
initialBoard: List[PieceOnSquareDto],
|
||||
)
|
||||
|
||||
case class ContextRequest(context: GameContextDto)
|
||||
|
||||
case class ContextSquareRequest(context: GameContextDto, square: String)
|
||||
|
||||
case class ContextMoveRequest(context: GameContextDto, move: MoveDto)
|
||||
|
||||
case class MovesResponse(moves: List[MoveDto])
|
||||
|
||||
case class BooleanResponse(result: Boolean)
|
||||
@@ -0,0 +1,94 @@
|
||||
package de.nowchess.rules.resource
|
||||
|
||||
import de.nowchess.rules.dto.*
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.MediaType
|
||||
|
||||
@Path("/api/rules")
|
||||
@ApplicationScoped
|
||||
class RuleSetResource:
|
||||
private val rules = DefaultRules
|
||||
|
||||
// scalafix:off DisableSyntax.throw
|
||||
private def parse[T](e: Either[String, T]): T = e match
|
||||
case Right(v) => v
|
||||
case Left(msg) => throw BadRequestException(msg)
|
||||
// scalafix:on DisableSyntax.throw
|
||||
|
||||
@POST
|
||||
@Path("/candidate-moves")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def candidateMoves(req: ContextSquareRequest): MovesResponse =
|
||||
val ctx = parse(DtoMapper.toGameContext(req.context))
|
||||
val sq = parse(DtoMapper.toSquare(req.square))
|
||||
MovesResponse(rules.candidateMoves(ctx)(sq).map(DtoMapper.fromMove))
|
||||
|
||||
@POST
|
||||
@Path("/legal-moves")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def legalMoves(req: ContextSquareRequest): MovesResponse =
|
||||
val ctx = parse(DtoMapper.toGameContext(req.context))
|
||||
val sq = parse(DtoMapper.toSquare(req.square))
|
||||
MovesResponse(rules.legalMoves(ctx)(sq).map(DtoMapper.fromMove))
|
||||
|
||||
@POST
|
||||
@Path("/all-legal-moves")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def allLegalMoves(req: ContextRequest): MovesResponse =
|
||||
MovesResponse(rules.allLegalMoves(parse(DtoMapper.toGameContext(req.context))).map(DtoMapper.fromMove))
|
||||
|
||||
@POST
|
||||
@Path("/is-check")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def isCheck(req: ContextRequest): BooleanResponse =
|
||||
BooleanResponse(rules.isCheck(parse(DtoMapper.toGameContext(req.context))))
|
||||
|
||||
@POST
|
||||
@Path("/is-checkmate")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def isCheckmate(req: ContextRequest): BooleanResponse =
|
||||
BooleanResponse(rules.isCheckmate(parse(DtoMapper.toGameContext(req.context))))
|
||||
|
||||
@POST
|
||||
@Path("/is-stalemate")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def isStalemate(req: ContextRequest): BooleanResponse =
|
||||
BooleanResponse(rules.isStalemate(parse(DtoMapper.toGameContext(req.context))))
|
||||
|
||||
@POST
|
||||
@Path("/is-insufficient-material")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def isInsufficientMaterial(req: ContextRequest): BooleanResponse =
|
||||
BooleanResponse(rules.isInsufficientMaterial(parse(DtoMapper.toGameContext(req.context))))
|
||||
|
||||
@POST
|
||||
@Path("/is-fifty-move-rule")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def isFiftyMoveRule(req: ContextRequest): BooleanResponse =
|
||||
BooleanResponse(rules.isFiftyMoveRule(parse(DtoMapper.toGameContext(req.context))))
|
||||
|
||||
@POST
|
||||
@Path("/is-threefold-repetition")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def isThreefoldRepetition(req: ContextRequest): BooleanResponse =
|
||||
BooleanResponse(rules.isThreefoldRepetition(parse(DtoMapper.toGameContext(req.context))))
|
||||
|
||||
@POST
|
||||
@Path("/apply-move")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
def applyMove(req: ContextMoveRequest): GameContextDto =
|
||||
val ctx = parse(DtoMapper.toGameContext(req.context))
|
||||
val move = parse(DtoMapper.toMove(req.move))
|
||||
DtoMapper.fromGameContext(rules.applyMove(ctx)(move))
|
||||
@@ -0,0 +1,168 @@
|
||||
package de.nowchess.rules.dto
|
||||
|
||||
import de.nowchess.api.board.{Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class DtoMapperTest extends AnyFunSuite with Matchers:
|
||||
|
||||
// ── toColor ────────────────────────────────────────────────────────
|
||||
|
||||
test("toColor converts White"):
|
||||
DtoMapper.toColor("White") shouldBe Right(Color.White)
|
||||
|
||||
test("toColor converts Black"):
|
||||
DtoMapper.toColor("Black") shouldBe Right(Color.Black)
|
||||
|
||||
test("toColor rejects unknown"):
|
||||
DtoMapper.toColor("Red") shouldBe a[Left[?, ?]]
|
||||
|
||||
// ── toPieceType ────────────────────────────────────────────────────
|
||||
|
||||
test("toPieceType converts all piece types"):
|
||||
DtoMapper.toPieceType("Pawn") shouldBe Right(PieceType.Pawn)
|
||||
DtoMapper.toPieceType("Knight") shouldBe Right(PieceType.Knight)
|
||||
DtoMapper.toPieceType("Bishop") shouldBe Right(PieceType.Bishop)
|
||||
DtoMapper.toPieceType("Rook") shouldBe Right(PieceType.Rook)
|
||||
DtoMapper.toPieceType("Queen") shouldBe Right(PieceType.Queen)
|
||||
DtoMapper.toPieceType("King") shouldBe Right(PieceType.King)
|
||||
|
||||
test("toPieceType rejects unknown"):
|
||||
DtoMapper.toPieceType("Dragon") shouldBe a[Left[?, ?]]
|
||||
|
||||
// ── toSquare ───────────────────────────────────────────────────────
|
||||
|
||||
test("toSquare converts valid algebraic"):
|
||||
DtoMapper.toSquare("e4") shouldBe Right(Square(File.E, Rank.R4))
|
||||
|
||||
test("toSquare rejects invalid"):
|
||||
DtoMapper.toSquare("z9") shouldBe a[Left[?, ?]]
|
||||
|
||||
// ── toMoveType ────────────────────────────────────────────────────
|
||||
|
||||
test("toMoveType converts normal non-capture"):
|
||||
DtoMapper.toMoveType(MoveDto("e2", "e4", "normal", None)) shouldBe Right(MoveType.Normal(false))
|
||||
|
||||
test("toMoveType converts capture"):
|
||||
DtoMapper.toMoveType(MoveDto("e2", "d3", "capture", None)) shouldBe Right(MoveType.Normal(true))
|
||||
|
||||
test("toMoveType converts castleKingside"):
|
||||
DtoMapper.toMoveType(MoveDto("e1", "g1", "castleKingside", None)) shouldBe Right(MoveType.CastleKingside)
|
||||
|
||||
test("toMoveType converts castleQueenside"):
|
||||
DtoMapper.toMoveType(MoveDto("e1", "c1", "castleQueenside", None)) shouldBe Right(MoveType.CastleQueenside)
|
||||
|
||||
test("toMoveType converts enPassant"):
|
||||
DtoMapper.toMoveType(MoveDto("e5", "d6", "enPassant", None)) shouldBe Right(MoveType.EnPassant)
|
||||
|
||||
test("toMoveType converts all promotion pieces"):
|
||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Queen"))) shouldBe Right(
|
||||
MoveType.Promotion(PromotionPiece.Queen),
|
||||
)
|
||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Rook"))) shouldBe Right(
|
||||
MoveType.Promotion(PromotionPiece.Rook),
|
||||
)
|
||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Bishop"))) shouldBe Right(
|
||||
MoveType.Promotion(PromotionPiece.Bishop),
|
||||
)
|
||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Knight"))) shouldBe Right(
|
||||
MoveType.Promotion(PromotionPiece.Knight),
|
||||
)
|
||||
|
||||
test("toMoveType rejects promotion without piece"):
|
||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", None)) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toMoveType rejects promotion with unknown piece"):
|
||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Pawn"))) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toMoveType rejects unknown type"):
|
||||
DtoMapper.toMoveType(MoveDto("e2", "e4", "unknown", None)) shouldBe a[Left[?, ?]]
|
||||
|
||||
// ── toBoard ───────────────────────────────────────────────────────
|
||||
|
||||
test("toBoard builds valid board"):
|
||||
val pieces = List(
|
||||
PieceOnSquareDto("e1", "White", "King"),
|
||||
PieceOnSquareDto("e8", "Black", "King"),
|
||||
)
|
||||
val result = DtoMapper.toBoard(pieces)
|
||||
result.isRight shouldBe true
|
||||
result.map(_.pieceAt(Square(File.E, Rank.R1))) shouldBe Right(Some(Piece(Color.White, PieceType.King)))
|
||||
|
||||
test("toBoard rejects invalid square"):
|
||||
DtoMapper.toBoard(List(PieceOnSquareDto("z9", "White", "King"))) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toBoard rejects invalid color"):
|
||||
DtoMapper.toBoard(List(PieceOnSquareDto("e1", "Red", "King"))) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toBoard rejects invalid piece type"):
|
||||
DtoMapper.toBoard(List(PieceOnSquareDto("e1", "White", "Dragon"))) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toBoard with multiple invalid pieces covers all sequenceList branches"):
|
||||
val pieces = List(
|
||||
PieceOnSquareDto("z9", "White", "King"),
|
||||
PieceOnSquareDto("z8", "White", "Queen"),
|
||||
)
|
||||
DtoMapper.toBoard(pieces) shouldBe a[Left[?, ?]]
|
||||
|
||||
// ── toGameContext ─────────────────────────────────────────────────
|
||||
|
||||
test("toGameContext round-trips initial position"):
|
||||
val ctx = GameContext.initial
|
||||
val dto = DtoMapper.fromGameContext(ctx)
|
||||
DtoMapper.toGameContext(dto) shouldBe Right(ctx)
|
||||
|
||||
test("toGameContext rejects invalid turn"):
|
||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(turn = "Red")
|
||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toGameContext rejects invalid en passant square"):
|
||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(enPassantSquare = Some("z9"))
|
||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toGameContext rejects invalid initial board"):
|
||||
val badBoard = List(PieceOnSquareDto("z9", "White", "King"))
|
||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(initialBoard = badBoard)
|
||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
||||
|
||||
test("toGameContext rejects invalid move"):
|
||||
val badMove = MoveDto("z9", "e4", "normal", None)
|
||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(moves = List(badMove))
|
||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
||||
|
||||
// ── fromGameContext ───────────────────────────────────────────────
|
||||
|
||||
test("fromGameContext includes en passant square when present"):
|
||||
val ctx = GameContext.initial.copy(enPassantSquare = Some(Square(File.E, Rank.R3)))
|
||||
DtoMapper.fromGameContext(ctx).enPassantSquare shouldBe Some("e3")
|
||||
|
||||
// ── fromMove ──────────────────────────────────────────────────────
|
||||
|
||||
test("fromMove converts all move types"):
|
||||
DtoMapper.fromMove(Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))).moveType shouldBe "normal"
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R2), Square(File.D, Rank.R3), MoveType.Normal(true)))
|
||||
.moveType shouldBe "capture"
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside))
|
||||
.moveType shouldBe "castleKingside"
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside))
|
||||
.moveType shouldBe "castleQueenside"
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant))
|
||||
.moveType shouldBe "enPassant"
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Queen)))
|
||||
.promotionPiece shouldBe Some("Queen")
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Rook)))
|
||||
.promotionPiece shouldBe Some("Rook")
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Bishop)))
|
||||
.promotionPiece shouldBe Some("Bishop")
|
||||
DtoMapper
|
||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Knight)))
|
||||
.promotionPiece shouldBe Some("Knight")
|
||||
@@ -0,0 +1,294 @@
|
||||
package de.nowchess.rules.resource
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.Move
|
||||
import de.nowchess.rules.dto.{ContextMoveRequest, ContextRequest, ContextSquareRequest, DtoMapper}
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import io.restassured.http.ContentType
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class RuleSetResourceTest:
|
||||
|
||||
private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
|
||||
private val rules = DefaultRules
|
||||
|
||||
private def request() = RestAssured.`given`()
|
||||
|
||||
private def toJson(value: AnyRef): String = mapper.writeValueAsString(value)
|
||||
|
||||
private def contextBody(ctx: GameContext): String =
|
||||
toJson(ContextRequest(DtoMapper.fromGameContext(ctx)))
|
||||
|
||||
private def contextSquareBody(ctx: GameContext, square: String): String =
|
||||
toJson(ContextSquareRequest(DtoMapper.fromGameContext(ctx), square))
|
||||
|
||||
private def contextMoveBody(ctx: GameContext, move: Move): String =
|
||||
toJson(ContextMoveRequest(DtoMapper.fromGameContext(ctx), DtoMapper.fromMove(move)))
|
||||
|
||||
// ── all-legal-moves ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def allLegalMoves_initialPositionHas20Moves(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/all-legal-moves")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.size()", is(20))
|
||||
|
||||
// ── legal-moves ───────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def legalMoves_e2PawnHas2Moves(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextSquareBody(GameContext.initial, "e2"))
|
||||
.when()
|
||||
.post("/api/rules/legal-moves")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.size()", is(2))
|
||||
|
||||
// ── candidate-moves ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def candidateMoves_e2PawnHas2Candidates(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextSquareBody(GameContext.initial, "e2"))
|
||||
.when()
|
||||
.post("/api/rules/candidate-moves")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("moves.size()", is(2))
|
||||
|
||||
// ── is-check ──────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def isCheck_falseForInitialPosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/is-check")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(false))
|
||||
|
||||
@Test
|
||||
def isCheck_trueWhenKingAttacked(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(buildCheckContext()))
|
||||
.when()
|
||||
.post("/api/rules/is-check")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(true))
|
||||
|
||||
// ── is-checkmate ──────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def isCheckmate_falseForInitialPosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/is-checkmate")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(false))
|
||||
|
||||
@Test
|
||||
def isCheckmate_trueForFoolsMate(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(buildFoolsMate()))
|
||||
.when()
|
||||
.post("/api/rules/is-checkmate")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(true))
|
||||
|
||||
// ── is-stalemate ──────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def isStalemate_falseForInitialPosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/is-stalemate")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(false))
|
||||
|
||||
@Test
|
||||
def isStalemate_trueForStalematePosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(buildStalemateContext()))
|
||||
.when()
|
||||
.post("/api/rules/is-stalemate")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(true))
|
||||
|
||||
// ── is-insufficient-material ──────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def isInsufficientMaterial_falseForInitialPosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/is-insufficient-material")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(false))
|
||||
|
||||
@Test
|
||||
def isInsufficientMaterial_trueForKingsOnly(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(buildKingsOnlyContext()))
|
||||
.when()
|
||||
.post("/api/rules/is-insufficient-material")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(true))
|
||||
|
||||
// ── is-fifty-move-rule ────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def isFiftyMoveRule_falseForInitialPosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/is-fifty-move-rule")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(false))
|
||||
|
||||
@Test
|
||||
def isFiftyMoveRule_trueWhenClockAt100(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial.copy(halfMoveClock = 100)))
|
||||
.when()
|
||||
.post("/api/rules/is-fifty-move-rule")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(true))
|
||||
|
||||
// ── is-threefold-repetition ───────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def isThreefoldRepetition_falseForInitialPosition(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(GameContext.initial))
|
||||
.when()
|
||||
.post("/api/rules/is-threefold-repetition")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(false))
|
||||
|
||||
@Test
|
||||
def isThreefoldRepetition_trueAfterRepeatedMoves(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextBody(buildThreefoldContext()))
|
||||
.when()
|
||||
.post("/api/rules/is-threefold-repetition")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("result", is(true))
|
||||
|
||||
// ── apply-move ────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def applyMove_updatesContext(): Unit =
|
||||
val move = rules
|
||||
.legalMoves(GameContext.initial)(Square(File.E, Rank.R2))
|
||||
.find(_.to == Square(File.E, Rank.R4))
|
||||
.get
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextMoveBody(GameContext.initial, move))
|
||||
.when()
|
||||
.post("/api/rules/apply-move")
|
||||
.`then`()
|
||||
.statusCode(200)
|
||||
.body("turn", is("Black"))
|
||||
|
||||
// ── error handling ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
def invalidSquare_returns400(): Unit =
|
||||
request()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(toJson(ContextSquareRequest(DtoMapper.fromGameContext(GameContext.initial), "z9")))
|
||||
.when()
|
||||
.post("/api/rules/legal-moves")
|
||||
.`then`()
|
||||
.statusCode(400)
|
||||
|
||||
// ── position builders ─────────────────────────────────────────────
|
||||
|
||||
private def buildCheckContext(): GameContext =
|
||||
val board = Board(Map(
|
||||
Square(File.E, Rank.R1) -> Piece(Color.White, PieceType.King),
|
||||
Square(File.E, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
Square(File.E, Rank.R3) -> Piece(Color.Black, PieceType.Rook),
|
||||
))
|
||||
GameContext(board, Color.White, CastlingRights.None, None, 0, List.empty, initialBoard = board)
|
||||
|
||||
private def buildFoolsMate(): GameContext =
|
||||
val moves = List(("f2", "f3"), ("e7", "e5"), ("g2", "g4"), ("d8", "h4"))
|
||||
moves.foldLeft(GameContext.initial) { (ctx, fromTo) =>
|
||||
val from = Square.fromAlgebraic(fromTo._1).get
|
||||
val to = Square.fromAlgebraic(fromTo._2).get
|
||||
rules.legalMoves(ctx)(from).find(_.to == to).fold(ctx)(rules.applyMove(ctx))
|
||||
}
|
||||
|
||||
private def buildStalemateContext(): GameContext =
|
||||
val board = Board(Map(
|
||||
Square(File.H, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
Square(File.F, Rank.R7) -> Piece(Color.White, PieceType.Queen),
|
||||
Square(File.G, Rank.R6) -> Piece(Color.White, PieceType.King),
|
||||
))
|
||||
GameContext(board, Color.Black, CastlingRights.None, None, 0, List.empty, initialBoard = board)
|
||||
|
||||
private def buildKingsOnlyContext(): GameContext =
|
||||
val board = Board(Map(
|
||||
Square(File.E, Rank.R1) -> Piece(Color.White, PieceType.King),
|
||||
Square(File.E, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
))
|
||||
GameContext(board, Color.White, CastlingRights.None, None, 0, List.empty, initialBoard = board)
|
||||
|
||||
private def buildThreefoldContext(): GameContext =
|
||||
val g1 = Square(File.G, Rank.R1)
|
||||
val f3 = Square(File.F, Rank.R3)
|
||||
val g8 = Square(File.G, Rank.R8)
|
||||
val f6 = Square(File.F, Rank.R6)
|
||||
def mv(ctx: GameContext, from: Square, to: Square): GameContext =
|
||||
rules.legalMoves(ctx)(from).find(_.to == to).fold(ctx)(rules.applyMove(ctx))
|
||||
val ctx1 = mv(GameContext.initial, g1, f3)
|
||||
val ctx2 = mv(ctx1, g8, f6)
|
||||
val ctx3 = mv(ctx2, f3, g1)
|
||||
val ctx4 = mv(ctx3, f6, g8)
|
||||
val ctx5 = mv(ctx4, g1, f3)
|
||||
val ctx6 = mv(ctx5, g8, f6)
|
||||
val ctx7 = mv(ctx6, f3, g1)
|
||||
mv(ctx7, f6, g8)
|
||||
@@ -1,3 +1,3 @@
|
||||
MAJOR=0
|
||||
MINOR=8
|
||||
MINOR=5
|
||||
PATCH=0
|
||||
|
||||
Reference in New Issue
Block a user