Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a386f57c21 | |||
| 3ca2afbb4b | |||
| b5a2966ada | |||
| 74a4fce0ca | |||
| 8fc97bde02 | |||
| 2d75b2e80e | |||
| f088c4e9ff | |||
| 8a1cf909d4 | |||
| 33e785d22a | |||
| d16cec176b | |||
| 8744bee2dd |
@@ -0,0 +1,87 @@
|
|||||||
|
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 }}
|
||||||
@@ -9,6 +9,7 @@ Scala 3.5.1 · Gradle 9
|
|||||||
./compile # Compile all modules — always run
|
./compile # Compile all modules — always run
|
||||||
./test # Run all tests
|
./test # Run all tests
|
||||||
./coverage # Check coverage
|
./coverage # Check coverage
|
||||||
|
./lint # Run linters
|
||||||
```
|
```
|
||||||
Use consistently.
|
Use consistently.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Scala 3.5.1 · Gradle 9
|
|||||||
./compile # Compile all modules — always run
|
./compile # Compile all modules — always run
|
||||||
./test # Run all tests
|
./test # Run all tests
|
||||||
./coverage # Check coverage
|
./coverage # Check coverage
|
||||||
|
./lint # Run linters
|
||||||
```
|
```
|
||||||
Try to stick to these commands for consistency.
|
Try to stick to these commands for consistency.
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -36,7 +36,11 @@ val coverageExclusions = listOf(
|
|||||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala",
|
"**/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala",
|
||||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.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
|
// 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"
|
"**/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",
|
||||||
)
|
)
|
||||||
|
|
||||||
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
|
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
|
||||||
@@ -72,7 +76,7 @@ sonar {
|
|||||||
val versions = mapOf(
|
val versions = mapOf(
|
||||||
"QUARKUS_SCALA3" to "1.0.0",
|
"QUARKUS_SCALA3" to "1.0.0",
|
||||||
"SCALA3" to "3.5.1",
|
"SCALA3" to "3.5.1",
|
||||||
"SCALA_LIBRARY" to "2.13.18",
|
"SCALA_LIBRARY" to "2.13.16",
|
||||||
"SCALATEST" to "3.2.19",
|
"SCALATEST" to "3.2.19",
|
||||||
"SCALATEST_JUNIT" to "0.1.11",
|
"SCALATEST_JUNIT" to "0.1.11",
|
||||||
"SCOVERAGE" to "2.1.1",
|
"SCOVERAGE" to "2.1.1",
|
||||||
|
|||||||
@@ -60,3 +60,23 @@
|
|||||||
* 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-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-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))
|
* 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))
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package de.nowchess.api.dto
|
||||||
|
|
||||||
|
case class ImportFenRequest(fen: String)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package de.nowchess.api.dto
|
||||||
|
|
||||||
|
case class ImportPgnRequest(pgn: String)
|
||||||
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
package de.nowchess.io
|
package de.nowchess.api.io
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
|
|
||||||
trait GameContextExport:
|
trait GameContextExport:
|
||||||
|
|
||||||
def exportGameContext(context: GameContext): String
|
def exportGameContext(context: GameContext): String
|
||||||
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
package de.nowchess.io
|
package de.nowchess.api.io
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
|
|
||||||
trait GameContextImport:
|
trait GameContextImport:
|
||||||
|
|
||||||
def importGameContext(input: String): Either[String, GameContext]
|
def importGameContext(input: String): Either[String, GameContext]
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=8
|
MINOR=10
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implementation(project(":modules:api"))
|
implementation(project(":modules:api"))
|
||||||
|
implementation(project(":modules:io"))
|
||||||
implementation(project(":modules:rule"))
|
implementation(project(":modules:rule"))
|
||||||
implementation("com.microsoft.onnxruntime:onnxruntime:${versions["ONNXRUNTIME"]!!}")
|
implementation("com.microsoft.onnxruntime:onnxruntime:${versions["ONNXRUNTIME"]!!}")
|
||||||
|
|
||||||
testImplementation(project(":modules:io"))
|
|
||||||
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
|
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
"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,3 +341,62 @@
|
|||||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
* 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 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))
|
* 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))
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implementation(project(":modules:api"))
|
implementation(project(":modules:api"))
|
||||||
implementation(project(":modules:io"))
|
|
||||||
implementation(project(":modules:rule"))
|
implementation(project(":modules:rule"))
|
||||||
implementation(project(":modules:bot"))
|
implementation(project(":modules:bot"))
|
||||||
|
|
||||||
@@ -69,11 +68,14 @@ dependencies {
|
|||||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
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(platform("org.junit:junit-bom:5.13.4"))
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||||
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
||||||
testImplementation("io.quarkus:quarkus-junit5")
|
testImplementation("io.quarkus:quarkus-junit5")
|
||||||
|
testImplementation("io.quarkus:quarkus-junit5-mockito")
|
||||||
testImplementation("io.rest-assured:rest-assured")
|
testImplementation("io.rest-assured:rest-assured")
|
||||||
|
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Before building the container image run:
|
# Before building the container image run:
|
||||||
#
|
#
|
||||||
# ./gradlew build -Dquarkus.native.enabled=true
|
# ./gradlew :modules:core:build -Dquarkus.native.enabled=true
|
||||||
#
|
#
|
||||||
# Then, build the image with:
|
# Then, build the image with:
|
||||||
#
|
#
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
#
|
#
|
||||||
# docker run -i --rm -p 8080:8080 quarkus/backcore
|
# 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`.
|
# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
|
||||||
###
|
###
|
||||||
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7
|
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7
|
||||||
@@ -21,7 +21,7 @@ WORKDIR /work/
|
|||||||
RUN chown 1001 /work \
|
RUN chown 1001 /work \
|
||||||
&& chmod "g+rwX" /work \
|
&& chmod "g+rwX" /work \
|
||||||
&& chown 1001:root /work
|
&& chown 1001:root /work
|
||||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
COPY --chown=1001:root --chmod=0755 modules/core/build/*-runner /work/application
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER 1001
|
USER 1001
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package de.nowchess.chess.client
|
package de.nowchess.chess.client
|
||||||
|
|
||||||
|
import de.nowchess.api.dto.{ImportFenRequest, ImportPgnRequest}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.service.dto.{ImportFenRequest, ImportPgnRequest}
|
|
||||||
import jakarta.ws.rs.*
|
import jakarta.ws.rs.*
|
||||||
import jakarta.ws.rs.core.MediaType
|
import jakarta.ws.rs.core.MediaType
|
||||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
|
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||||
import de.nowchess.api.board.Square
|
import de.nowchess.api.board.Square
|
||||||
import de.nowchess.io.json.{SquareKeyDeserializer, SquareKeySerializer}
|
|
||||||
import io.quarkus.jackson.ObjectMapperCustomizer
|
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||||
import jakarta.inject.Singleton
|
import jakarta.inject.Singleton
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection
|
|||||||
classOf[LegalMovesResponseDto],
|
classOf[LegalMovesResponseDto],
|
||||||
classOf[OkResponseDto],
|
classOf[OkResponseDto],
|
||||||
classOf[PlayerInfoDto],
|
classOf[PlayerInfoDto],
|
||||||
|
|
||||||
classOf[GameContext],
|
classOf[GameContext],
|
||||||
classOf[Color],
|
classOf[Color],
|
||||||
classOf[Piece],
|
classOf[Piece],
|
||||||
@@ -35,7 +34,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection
|
|||||||
classOf[PromotionPiece],
|
classOf[PromotionPiece],
|
||||||
classOf[GameResult],
|
classOf[GameResult],
|
||||||
classOf[DrawReason],
|
classOf[DrawReason],
|
||||||
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
class NativeReflectionConfig
|
class NativeReflectionConfig
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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.controller.Parser
|
||||||
import de.nowchess.chess.observer.*
|
import de.nowchess.chess.observer.*
|
||||||
import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult}
|
import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult}
|
||||||
import de.nowchess.io.{GameContextExport, GameContextImport}
|
import de.nowchess.api.io.{GameContextExport, GameContextImport}
|
||||||
import de.nowchess.rules.RuleSet
|
import de.nowchess.rules.RuleSet
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ import de.nowchess.chess.engine.GameEngine
|
|||||||
import de.nowchess.chess.exception.{BadRequestException, GameNotFoundException}
|
import de.nowchess.chess.exception.{BadRequestException, GameNotFoundException}
|
||||||
import de.nowchess.chess.observer.*
|
import de.nowchess.chess.observer.*
|
||||||
import de.nowchess.chess.registry.{GameEntry, GameRegistry}
|
import de.nowchess.chess.registry.{GameEntry, GameRegistry}
|
||||||
import de.nowchess.io.fen.FenExporter
|
|
||||||
import de.nowchess.io.pgn.PgnExporter
|
|
||||||
import de.nowchess.io.service.dto.{ImportFenRequest, ImportPgnRequest}
|
|
||||||
import io.smallrye.mutiny.Multi
|
import io.smallrye.mutiny.Multi
|
||||||
import jakarta.enterprise.context.ApplicationScoped
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
import jakarta.inject.Inject
|
import jakarta.inject.Inject
|
||||||
@@ -89,16 +86,8 @@ class GameResource:
|
|||||||
private def toGameStateDto(entry: GameEntry): GameStateDto =
|
private def toGameStateDto(entry: GameEntry): GameStateDto =
|
||||||
val ctx = entry.engine.context
|
val ctx = entry.engine.context
|
||||||
GameStateDto(
|
GameStateDto(
|
||||||
fen = FenExporter.exportGameContext(ctx),
|
fen = ioClient.exportFen(ctx),
|
||||||
pgn = PgnExporter.exportGame(
|
pgn = ioClient.exportPgn(ctx),
|
||||||
Map(
|
|
||||||
"Event" -> "NowChess game",
|
|
||||||
"White" -> entry.white.displayName,
|
|
||||||
"Black" -> entry.black.displayName,
|
|
||||||
"Result" -> "*",
|
|
||||||
),
|
|
||||||
ctx.moves,
|
|
||||||
),
|
|
||||||
turn = ctx.turn.label.toLowerCase,
|
turn = ctx.turn.label.toLowerCase,
|
||||||
status = statusOf(entry),
|
status = statusOf(entry),
|
||||||
winner = ctx.result.collect { case GameResult.Win(c) => c.label.toLowerCase },
|
winner = ctx.result.collect { case GameResult.Win(c) => c.label.toLowerCase },
|
||||||
|
|||||||
+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.game.GameContext
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
import de.nowchess.chess.observer.{GameEvent, InvalidMoveEvent, InvalidMoveReason, MoveRedoneEvent, Observer}
|
import de.nowchess.chess.observer.{GameEvent, InvalidMoveEvent, InvalidMoveReason, MoveRedoneEvent, Observer}
|
||||||
import de.nowchess.io.GameContextImport
|
import de.nowchess.api.io.GameContextImport
|
||||||
import de.nowchess.rules.RuleSet
|
import de.nowchess.rules.RuleSet
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package de.nowchess.chess.engine
|
package de.nowchess.chess.engine
|
||||||
|
|
||||||
import scala.collection.mutable
|
import de.nowchess.chess.observer.{GameEvent, Observer}
|
||||||
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.fen.FenParser
|
||||||
import de.nowchess.io.pgn.PgnExporter
|
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||||
import de.nowchess.io.fen.FenExporter
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
class GameEngineLoadGameTest extends AnyFunSuite with Matchers:
|
class GameEngineLoadGameTest extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
test("loadGame with PgnParser: loads valid PGN and enables undo/redo"):
|
test("loadGame with PgnParser: loads valid PGN and enables undo/redo"):
|
||||||
|
|||||||
+22
-1
@@ -1,11 +1,19 @@
|
|||||||
package de.nowchess.chess.resource
|
package de.nowchess.chess.resource
|
||||||
|
|
||||||
import de.nowchess.api.dto.*
|
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.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 io.quarkus.test.junit.QuarkusTest
|
||||||
import jakarta.inject.Inject
|
import jakarta.inject.Inject
|
||||||
import org.junit.jupiter.api.{DisplayName, Test}
|
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||||
|
import org.junit.jupiter.api.{BeforeEach, DisplayName, Test}
|
||||||
import org.junit.jupiter.api.Assertions.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.mockito.ArgumentMatchers.any
|
||||||
|
import org.mockito.Mockito.when
|
||||||
|
|
||||||
import scala.compiletime.uninitialized
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
@@ -17,6 +25,19 @@ class GameResourceIntegrationTest:
|
|||||||
@Inject
|
@Inject
|
||||||
var resource: GameResource = uninitialized
|
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
|
@Test
|
||||||
@DisplayName("createGame returns 201")
|
@DisplayName("createGame returns 201")
|
||||||
def testCreateGame(): Unit =
|
def testCreateGame(): Unit =
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=16
|
MINOR=18
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -76,3 +76,26 @@
|
|||||||
* 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-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-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))
|
* 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))
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ dependencies {
|
|||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
||||||
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
####
|
||||||
|
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||||
|
#
|
||||||
|
# Before building the container image run:
|
||||||
|
#
|
||||||
|
# ./gradlew build
|
||||||
|
#
|
||||||
|
# Then, build the image with:
|
||||||
|
#
|
||||||
|
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/backcore-jvm .
|
||||||
|
#
|
||||||
|
# Then run the container using:
|
||||||
|
#
|
||||||
|
# docker run -i --rm -p 8080:8080 quarkus/backcore-jvm
|
||||||
|
#
|
||||||
|
# If you want to include the debug port into your docker image
|
||||||
|
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
||||||
|
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
||||||
|
# when running the container
|
||||||
|
#
|
||||||
|
# Then run the container using :
|
||||||
|
#
|
||||||
|
# docker run -i --rm -p 8080:8080 quarkus/backcore-jvm
|
||||||
|
#
|
||||||
|
# This image uses the `run-java.sh` script to run the application.
|
||||||
|
# This scripts computes the command line to execute your Java application, and
|
||||||
|
# includes memory/GC tuning.
|
||||||
|
# You can configure the behavior using the following environment properties:
|
||||||
|
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
||||||
|
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
||||||
|
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||||
|
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||||
|
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||||
|
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||||
|
# If used in a container without any memory constraints for the container then this
|
||||||
|
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||||
|
# of the container available memory as set here. The default is `50` which means 50%
|
||||||
|
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||||
|
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||||
|
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||||
|
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||||
|
# If used in a container without any memory constraints for the container then this
|
||||||
|
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||||
|
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||||
|
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||||
|
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||||
|
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||||
|
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||||
|
# a container without any memory constraints for the container then this option has
|
||||||
|
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||||
|
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||||
|
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||||
|
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||||
|
# when things are happening. This option, if set to true, will set
|
||||||
|
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||||
|
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||||
|
# true").
|
||||||
|
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||||
|
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||||
|
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||||
|
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||||
|
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||||
|
# (example: "20")
|
||||||
|
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||||
|
# (example: "40")
|
||||||
|
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||||
|
# (example: "4")
|
||||||
|
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||||
|
# previous GC times. (example: "90")
|
||||||
|
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||||
|
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||||
|
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||||
|
# contain the necessary JRE command-line options to specify the required GC, which
|
||||||
|
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||||
|
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||||
|
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||||
|
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||||
|
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||||
|
#
|
||||||
|
# You can find more information about the UBI base runtime images and their configuration here:
|
||||||
|
# https://rh-openjdk.github.io/redhat-openjdk-containers/
|
||||||
|
###
|
||||||
|
FROM registry.access.redhat.com/ubi9/openjdk-21-runtime:1.24
|
||||||
|
|
||||||
|
ENV LANGUAGE='en_US:en'
|
||||||
|
|
||||||
|
|
||||||
|
# We make four distinct layers so if there are application changes the library layers can be re-used
|
||||||
|
COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
|
||||||
|
COPY --chown=185 build/quarkus-app/*.jar /deployments/
|
||||||
|
COPY --chown=185 build/quarkus-app/app/ /deployments/app/
|
||||||
|
COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
USER 185
|
||||||
|
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||||
|
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||||
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
####
|
||||||
|
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||||
|
#
|
||||||
|
# Before building the container image run:
|
||||||
|
#
|
||||||
|
# ./gradlew build -Dquarkus.package.jar.type=legacy-jar
|
||||||
|
#
|
||||||
|
# Then, build the image with:
|
||||||
|
#
|
||||||
|
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/backcore-legacy-jar .
|
||||||
|
#
|
||||||
|
# Then run the container using:
|
||||||
|
#
|
||||||
|
# docker run -i --rm -p 8080:8080 quarkus/backcore-legacy-jar
|
||||||
|
#
|
||||||
|
# If you want to include the debug port into your docker image
|
||||||
|
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
||||||
|
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
||||||
|
# when running the container
|
||||||
|
#
|
||||||
|
# Then run the container using :
|
||||||
|
#
|
||||||
|
# docker run -i --rm -p 8080:8080 quarkus/backcore-legacy-jar
|
||||||
|
#
|
||||||
|
# This image uses the `run-java.sh` script to run the application.
|
||||||
|
# This scripts computes the command line to execute your Java application, and
|
||||||
|
# includes memory/GC tuning.
|
||||||
|
# You can configure the behavior using the following environment properties:
|
||||||
|
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
||||||
|
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
||||||
|
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||||
|
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||||
|
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||||
|
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||||
|
# If used in a container without any memory constraints for the container then this
|
||||||
|
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||||
|
# of the container available memory as set here. The default is `50` which means 50%
|
||||||
|
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||||
|
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||||
|
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||||
|
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||||
|
# If used in a container without any memory constraints for the container then this
|
||||||
|
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||||
|
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||||
|
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||||
|
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||||
|
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||||
|
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||||
|
# a container without any memory constraints for the container then this option has
|
||||||
|
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||||
|
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||||
|
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||||
|
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||||
|
# when things are happening. This option, if set to true, will set
|
||||||
|
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||||
|
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||||
|
# true").
|
||||||
|
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||||
|
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||||
|
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||||
|
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||||
|
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||||
|
# (example: "20")
|
||||||
|
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||||
|
# (example: "40")
|
||||||
|
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||||
|
# (example: "4")
|
||||||
|
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||||
|
# previous GC times. (example: "90")
|
||||||
|
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||||
|
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||||
|
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||||
|
# contain the necessary JRE command-line options to specify the required GC, which
|
||||||
|
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||||
|
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||||
|
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||||
|
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||||
|
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||||
|
#
|
||||||
|
# You can find more information about the UBI base runtime images and their configuration here:
|
||||||
|
# https://rh-openjdk.github.io/redhat-openjdk-containers/
|
||||||
|
###
|
||||||
|
FROM registry.access.redhat.com/ubi9/openjdk-21-runtime:1.24
|
||||||
|
|
||||||
|
ENV LANGUAGE='en_US:en'
|
||||||
|
|
||||||
|
|
||||||
|
COPY build/lib/* /deployments/lib/
|
||||||
|
COPY build/*-runner.jar /deployments/quarkus-run.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
USER 185
|
||||||
|
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||||
|
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
####
|
||||||
|
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
|
||||||
|
#
|
||||||
|
# Before building the container image run:
|
||||||
|
#
|
||||||
|
# ./gradlew :modules:io:build -Dquarkus.native.enabled=true
|
||||||
|
#
|
||||||
|
# Then, build the image with:
|
||||||
|
#
|
||||||
|
# docker build -f src/main/docker/Dockerfile.native -t quarkus/backio .
|
||||||
|
#
|
||||||
|
# Then run the container using:
|
||||||
|
#
|
||||||
|
# docker run -i --rm -p 8080:8080 quarkus/backio
|
||||||
|
#
|
||||||
|
# The `registry.access.redhat.com/ubi9/ubi-minimal:9.7` base image is based on UBI 9.
|
||||||
|
# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
|
||||||
|
###
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7
|
||||||
|
WORKDIR /work/
|
||||||
|
RUN chown 1001 /work \
|
||||||
|
&& chmod "g+rwX" /work \
|
||||||
|
&& chown 1001:root /work
|
||||||
|
COPY --chown=1001:root --chmod=0755 modules/io/build/*-runner /work/application
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
USER 1001
|
||||||
|
|
||||||
|
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
####
|
||||||
|
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
|
||||||
|
# It uses a micro base image, tuned for Quarkus native executables.
|
||||||
|
# It reduces the size of the resulting container image.
|
||||||
|
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
|
||||||
|
#
|
||||||
|
# Before building the container image run:
|
||||||
|
#
|
||||||
|
# ./gradlew build -Dquarkus.native.enabled=true
|
||||||
|
#
|
||||||
|
# Then, build the image with:
|
||||||
|
#
|
||||||
|
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/backcore .
|
||||||
|
#
|
||||||
|
# Then run the container using:
|
||||||
|
#
|
||||||
|
# docker run -i --rm -p 8080:8080 quarkus/backcore
|
||||||
|
#
|
||||||
|
# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
|
||||||
|
# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
|
||||||
|
###
|
||||||
|
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
|
||||||
|
WORKDIR /work/
|
||||||
|
RUN chown 1001 /work \
|
||||||
|
&& chmod "g+rwX" /work \
|
||||||
|
&& chown 1001:root /work
|
||||||
|
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
USER 1001
|
||||||
|
|
||||||
|
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package de.nowchess.io
|
package de.nowchess.io
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
|
import de.nowchess.api.io.{GameContextExport, GameContextImport}
|
||||||
|
|
||||||
import java.nio.file.{Files, Path}
|
import java.nio.file.{Files, Path}
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package de.nowchess.io.fen
|
|||||||
|
|
||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextExport
|
import de.nowchess.api.io.GameContextExport
|
||||||
|
|
||||||
object FenExporter extends GameContextExport:
|
object FenExporter extends GameContextExport:
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package de.nowchess.io.fen
|
|||||||
|
|
||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextImport
|
import de.nowchess.api.io.GameContextImport
|
||||||
|
|
||||||
object FenParser extends GameContextImport:
|
object FenParser extends GameContextImport:
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package de.nowchess.io.fen
|
|||||||
|
|
||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextImport
|
|
||||||
import scala.util.parsing.combinator.RegexParsers
|
import scala.util.parsing.combinator.RegexParsers
|
||||||
import FenParserSupport.*
|
import FenParserSupport.*
|
||||||
|
import de.nowchess.api.io.GameContextImport
|
||||||
|
|
||||||
object FenParserCombinators extends RegexParsers with GameContextImport:
|
object FenParserCombinators extends RegexParsers with GameContextImport:
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import fastparse.*
|
|||||||
import fastparse.NoWhitespace.*
|
import fastparse.NoWhitespace.*
|
||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextImport
|
|
||||||
import FenParserSupport.*
|
import FenParserSupport.*
|
||||||
|
import de.nowchess.api.io.GameContextImport
|
||||||
|
|
||||||
object FenParserFastParse extends GameContextImport:
|
object FenParserFastParse extends GameContextImport:
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
|||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextExport
|
import de.nowchess.api.io.GameContextExport
|
||||||
import de.nowchess.io.pgn.PgnExporter
|
import de.nowchess.io.pgn.PgnExporter
|
||||||
|
|
||||||
import java.time.{LocalDate, ZoneId, ZonedDateTime}
|
import java.time.{LocalDate, ZoneId, ZonedDateTime}
|
||||||
|
|
||||||
/** Exports a GameContext to a comprehensive JSON format using Jackson.
|
/** Exports a GameContext to a comprehensive JSON format using Jackson.
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
|||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextImport
|
import de.nowchess.api.io.GameContextImport
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
/** Imports a GameContext from JSON format using Jackson.
|
/** Imports a GameContext from JSON format using Jackson.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package de.nowchess.io.pgn
|
|||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextExport
|
import de.nowchess.api.io.GameContextExport
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
|
|
||||||
object PgnExporter extends GameContextExport:
|
object PgnExporter extends GameContextExport:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package de.nowchess.io.pgn
|
|||||||
import de.nowchess.api.board.*
|
import de.nowchess.api.board.*
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.io.GameContextImport
|
import de.nowchess.api.io.GameContextImport
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
|
|
||||||
/** A parsed PGN game containing headers and the resolved move list. */
|
/** A parsed PGN game containing headers and the resolved move list. */
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class IoResource:
|
|||||||
def importFen(body: ImportFenRequest): Uni[Response] =
|
def importFen(body: ImportFenRequest): Uni[Response] =
|
||||||
Uni.createFrom().item {
|
Uni.createFrom().item {
|
||||||
FenParser.parseFen(body.fen) match
|
FenParser.parseFen(body.fen) match
|
||||||
case Left(err) =>
|
case Left(err) =>
|
||||||
Response.status(400).entity(IoErrorDto("INVALID_FEN", err)).build()
|
Response.status(400).entity(IoErrorDto("INVALID_FEN", err)).build()
|
||||||
case Right(ctx) =>
|
case Right(ctx) =>
|
||||||
Response.ok(ctx).build()
|
Response.ok(ctx).build()
|
||||||
@@ -52,7 +52,7 @@ class IoResource:
|
|||||||
def importPgn(body: ImportPgnRequest): Uni[Response] =
|
def importPgn(body: ImportPgnRequest): Uni[Response] =
|
||||||
Uni.createFrom().item {
|
Uni.createFrom().item {
|
||||||
PgnParser.importGameContext(body.pgn) match
|
PgnParser.importGameContext(body.pgn) match
|
||||||
case Left(err) =>
|
case Left(err) =>
|
||||||
Response.status(400).entity(IoErrorDto("INVALID_PGN", err)).build()
|
Response.status(400).entity(IoErrorDto("INVALID_PGN", err)).build()
|
||||||
case Right(ctx) =>
|
case Right(ctx) =>
|
||||||
Response.ok(ctx).build()
|
Response.ok(ctx).build()
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package de.nowchess.io
|
package de.nowchess.io
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.board.{File, Rank, Square}
|
import de.nowchess.api.board.{File, Rank, Square}
|
||||||
|
import de.nowchess.api.game.GameContext
|
||||||
|
import de.nowchess.api.io.GameContextExport
|
||||||
import de.nowchess.api.move.Move
|
import de.nowchess.api.move.Move
|
||||||
import de.nowchess.io.json.{JsonExporter, JsonParser}
|
import de.nowchess.io.json.{JsonExporter, JsonParser}
|
||||||
import java.nio.file.{Files, Paths}
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import scala.util.Using
|
|
||||||
|
import java.nio.file.{Files, Paths}
|
||||||
|
|
||||||
class GameFileServiceSuite extends AnyFunSuite with Matchers:
|
class GameFileServiceSuite extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package de.nowchess.io.json
|
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class JsonExporterBranchCoverageSuite extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
test("export all promotion pieces separately for full branch coverage") {
|
|
||||||
val promotions = List(
|
|
||||||
(PromotionPiece.Queen, "queen"),
|
|
||||||
(PromotionPiece.Rook, "rook"),
|
|
||||||
(PromotionPiece.Bishop, "bishop"),
|
|
||||||
(PromotionPiece.Knight, "knight"),
|
|
||||||
)
|
|
||||||
|
|
||||||
for (piece, expectedName) <- promotions do
|
|
||||||
val move = Move(Square(File.A, Rank.R7), Square(File.A, Rank.R8), MoveType.Promotion(piece))
|
|
||||||
// Empty boards can cause issues in PgnExporter, using initial
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(move))
|
|
||||||
// try-catch to ignore PgnExporter errors but cover convertMoveType
|
|
||||||
try {
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
json should include(s""""$expectedName"""")
|
|
||||||
} catch { case _: Exception => }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("export normal non-capture move") {
|
|
||||||
val quietMove = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4), MoveType.Normal(false))
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(quietMove))
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
json should include("\"normal\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
test("export normal capture move manually") {
|
|
||||||
val move = Move(Square(File.E, Rank.R4), Square(File.D, Rank.R5), MoveType.Normal(true))
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(move))
|
|
||||||
try {
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
json should include("\"normal\"")
|
|
||||||
json should include("\"isCapture\": true")
|
|
||||||
} catch { case _: Exception => }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("export all move type categories") {
|
|
||||||
val move = Move(Square(File.D, Rank.R2), Square(File.D, Rank.R4))
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(move))
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
|
|
||||||
json should include("\"moves\"")
|
|
||||||
json should include("\"from\"")
|
|
||||||
json should include("\"to\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
test("export castle queenside move") {
|
|
||||||
val move = Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside)
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(move))
|
|
||||||
try {
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
json should include("\"castleQueenside\"")
|
|
||||||
} catch { case _: Exception => }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("export castle kingside move") {
|
|
||||||
val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(move))
|
|
||||||
try {
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
json should include("\"castleKingside\"")
|
|
||||||
} catch { case _: Exception => }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("export en passant move manually") {
|
|
||||||
val move = Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant)
|
|
||||||
val ctx = GameContext.initial.copy(moves = List(move))
|
|
||||||
try {
|
|
||||||
val json = JsonExporter.exportGameContext(ctx)
|
|
||||||
json should include("\"enPassant\"")
|
|
||||||
json should include("\"isCapture\": true")
|
|
||||||
} catch { case _: Exception => }
|
|
||||||
}
|
|
||||||
+63
-9
@@ -6,7 +6,7 @@ import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
|||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
class JsonExporterSuite extends AnyFunSuite with Matchers:
|
class JsonExporterTest extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
test("exportGameContext: exports initial position") {
|
test("exportGameContext: exports initial position") {
|
||||||
val context = GameContext.initial
|
val context = GameContext.initial
|
||||||
@@ -87,14 +87,6 @@ class JsonExporterSuite extends AnyFunSuite with Matchers:
|
|||||||
json should include("\"enPassantSquare\": null")
|
json should include("\"enPassantSquare\": null")
|
||||||
}
|
}
|
||||||
|
|
||||||
test("exportGameContext: exports different move destinations") {
|
|
||||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
|
||||||
val context = GameContext.initial.withMove(move)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
|
|
||||||
json should include("\"moves\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
test("exportGameContext: exports empty board") {
|
test("exportGameContext: exports empty board") {
|
||||||
val emptyBoard = Board(Map.empty)
|
val emptyBoard = Board(Map.empty)
|
||||||
val context = GameContext.initial.copy(board = emptyBoard)
|
val context = GameContext.initial.copy(board = emptyBoard)
|
||||||
@@ -113,3 +105,65 @@ class JsonExporterSuite extends AnyFunSuite with Matchers:
|
|||||||
json should include("\"blackKingSide\": false")
|
json should include("\"blackKingSide\": false")
|
||||||
json should include("\"blackQueenSide\": false")
|
json should include("\"blackQueenSide\": false")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("export all promotion pieces for full branch coverage") {
|
||||||
|
val promotions = List(
|
||||||
|
(PromotionPiece.Queen, "queen"),
|
||||||
|
(PromotionPiece.Rook, "rook"),
|
||||||
|
(PromotionPiece.Bishop, "bishop"),
|
||||||
|
(PromotionPiece.Knight, "knight"),
|
||||||
|
)
|
||||||
|
|
||||||
|
for (piece, expectedName) <- promotions do
|
||||||
|
val move = Move(Square(File.A, Rank.R7), Square(File.A, Rank.R8), MoveType.Promotion(piece))
|
||||||
|
val ctx = GameContext.initial.copy(moves = List(move))
|
||||||
|
try {
|
||||||
|
val json = JsonExporter.exportGameContext(ctx)
|
||||||
|
json should include(s""""$expectedName"""")
|
||||||
|
} catch { case _: Exception => }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("export normal non-capture move") {
|
||||||
|
val quietMove = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4), MoveType.Normal(false))
|
||||||
|
val ctx = GameContext.initial.copy(moves = List(quietMove))
|
||||||
|
val json = JsonExporter.exportGameContext(ctx)
|
||||||
|
json should include("\"normal\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("export normal capture move") {
|
||||||
|
val move = Move(Square(File.E, Rank.R4), Square(File.D, Rank.R5), MoveType.Normal(true))
|
||||||
|
val ctx = GameContext.initial.copy(moves = List(move))
|
||||||
|
try {
|
||||||
|
val json = JsonExporter.exportGameContext(ctx)
|
||||||
|
json should include("\"normal\"")
|
||||||
|
json should include("\"isCapture\": true")
|
||||||
|
} catch { case _: Exception => }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("export castle queenside move") {
|
||||||
|
val move = Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside)
|
||||||
|
val ctx = GameContext.initial.copy(moves = List(move))
|
||||||
|
try {
|
||||||
|
val json = JsonExporter.exportGameContext(ctx)
|
||||||
|
json should include("\"castleQueenside\"")
|
||||||
|
} catch { case _: Exception => }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("export castle kingside move") {
|
||||||
|
val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
|
||||||
|
val ctx = GameContext.initial.copy(moves = List(move))
|
||||||
|
try {
|
||||||
|
val json = JsonExporter.exportGameContext(ctx)
|
||||||
|
json should include("\"castleKingside\"")
|
||||||
|
} catch { case _: Exception => }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("export en passant move") {
|
||||||
|
val move = Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant)
|
||||||
|
val ctx = GameContext.initial.copy(moves = List(move))
|
||||||
|
try {
|
||||||
|
val json = JsonExporter.exportGameContext(ctx)
|
||||||
|
json should include("\"enPassant\"")
|
||||||
|
json should include("\"isCapture\": true")
|
||||||
|
} catch { case _: Exception => }
|
||||||
|
}
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package de.nowchess.io.json
|
|
||||||
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class JsonModelExtraTestSuite extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
test("JsonMetadata with all fields") {
|
|
||||||
val meta = JsonMetadata(Some("Event"), Some(Map("a" -> "b")), Some("2026-04-08"), Some("1-0"))
|
|
||||||
assert(meta.event.contains("Event"))
|
|
||||||
assert(meta.players.exists(_.contains("a")))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMetadata with None fields") {
|
|
||||||
val meta = JsonMetadata()
|
|
||||||
assert(meta.event.isEmpty)
|
|
||||||
assert(meta.players.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonPiece with square and piece") {
|
|
||||||
val piece = JsonPiece(Some("e4"), Some("White"), Some("Pawn"))
|
|
||||||
assert(piece.square.contains("e4"))
|
|
||||||
assert(piece.color.contains("White"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonCastlingRights all true") {
|
|
||||||
val cr = JsonCastlingRights(Some(true), Some(true), Some(true), Some(true))
|
|
||||||
assert(cr.whiteKingSide.contains(true))
|
|
||||||
assert(cr.blackQueenSide.contains(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonCastlingRights all false") {
|
|
||||||
val cr = JsonCastlingRights(Some(false), Some(false), Some(false), Some(false))
|
|
||||||
assert(cr.whiteKingSide.contains(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonGameState with all fields") {
|
|
||||||
val gs = JsonGameState(
|
|
||||||
Some(Nil),
|
|
||||||
Some("White"),
|
|
||||||
Some(JsonCastlingRights()),
|
|
||||||
Some("e3"),
|
|
||||||
Some(5),
|
|
||||||
)
|
|
||||||
assert(gs.board.contains(Nil))
|
|
||||||
assert(gs.halfMoveClock.contains(5))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonGameState with None fields") {
|
|
||||||
val gs = JsonGameState()
|
|
||||||
assert(gs.board.isEmpty)
|
|
||||||
assert(gs.halfMoveClock.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonCapturedPieces with pieces") {
|
|
||||||
val cp = JsonCapturedPieces(Some(List("Pawn")), Some(List("Knight")))
|
|
||||||
assert(cp.byWhite.exists(_.contains("Pawn")))
|
|
||||||
assert(cp.byBlack.exists(_.contains("Knight")))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMoveType normal with capture") {
|
|
||||||
val mt = JsonMoveType(Some("normal"), Some(true), None)
|
|
||||||
assert(mt.`type`.contains("normal"))
|
|
||||||
assert(mt.isCapture.contains(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMoveType promotion") {
|
|
||||||
val mt = JsonMoveType(Some("promotion"), None, Some("queen"))
|
|
||||||
assert(mt.`type`.contains("promotion"))
|
|
||||||
assert(mt.promotionPiece.contains("queen"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMoveType castle kingside") {
|
|
||||||
val mt = JsonMoveType(Some("castleKingside"), None, None)
|
|
||||||
assert(mt.`type`.contains("castleKingside"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMove with coordinates") {
|
|
||||||
val move = JsonMove(Some("e2"), Some("e4"), Some(JsonMoveType(Some("normal"), Some(false), None)))
|
|
||||||
assert(move.from.contains("e2"))
|
|
||||||
assert(move.to.contains("e4"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonGameRecord full structure") {
|
|
||||||
val record = JsonGameRecord(
|
|
||||||
Some(JsonMetadata()),
|
|
||||||
Some(JsonGameState()),
|
|
||||||
Some(""),
|
|
||||||
Some(Nil),
|
|
||||||
Some(JsonCapturedPieces()),
|
|
||||||
Some("2026-04-08T00:00:00Z"),
|
|
||||||
)
|
|
||||||
assert(record.metadata.nonEmpty)
|
|
||||||
assert(record.timestamp.nonEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonGameRecord empty") {
|
|
||||||
val record = JsonGameRecord()
|
|
||||||
assert(record.metadata.isEmpty)
|
|
||||||
assert(record.moves.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonPiece with no fields") {
|
|
||||||
val piece = JsonPiece()
|
|
||||||
assert(piece.square.isEmpty)
|
|
||||||
assert(piece.color.isEmpty)
|
|
||||||
assert(piece.piece.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMoveType with no fields") {
|
|
||||||
val mt = JsonMoveType()
|
|
||||||
assert(mt.`type`.isEmpty)
|
|
||||||
assert(mt.isCapture.isEmpty)
|
|
||||||
assert(mt.promotionPiece.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("JsonMove with empty fields") {
|
|
||||||
val move = JsonMove()
|
|
||||||
assert(move.from.isEmpty)
|
|
||||||
assert(move.to.isEmpty)
|
|
||||||
assert(move.`type`.isEmpty)
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package de.nowchess.io.json
|
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.board.{Color, PieceType}
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class JsonParserEdgeCasesSuite extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
test("parse invalid turn color returns error") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "Invalid", "board": []},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isLeft)
|
|
||||||
assert(result.left.toOption.get.contains("Invalid turn color"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse invalid piece type filters it out") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {
|
|
||||||
"turn": "White",
|
|
||||||
"board": [
|
|
||||||
{"square": "a1", "color": "White", "piece": "InvalidPiece"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.board.pieces.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse invalid color in board filters piece") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {
|
|
||||||
"turn": "White",
|
|
||||||
"board": [
|
|
||||||
{"square": "a1", "color": "InvalidColor", "piece": "Pawn"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.board.pieces.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse with missing turn uses default") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"board": []},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.turn == Color.White)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse with missing board uses empty") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "White"},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.board.pieces.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse with missing moves uses empty list") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "White", "board": []}
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.moves.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse invalid square in board filters it") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {
|
|
||||||
"turn": "White",
|
|
||||||
"board": [
|
|
||||||
{"square": "invalid99", "color": "White", "piece": "Pawn"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.board.pieces.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse all valid piece types") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {
|
|
||||||
"turn": "White",
|
|
||||||
"board": [
|
|
||||||
{"square": "a1", "color": "White", "piece": "Pawn"},
|
|
||||||
{"square": "b1", "color": "White", "piece": "Knight"},
|
|
||||||
{"square": "c1", "color": "White", "piece": "Bishop"},
|
|
||||||
{"square": "d1", "color": "White", "piece": "Rook"},
|
|
||||||
{"square": "e1", "color": "White", "piece": "Queen"},
|
|
||||||
{"square": "f1", "color": "White", "piece": "King"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.board.pieces.size == 6)
|
|
||||||
assert(
|
|
||||||
ctx.board
|
|
||||||
.pieceAt(de.nowchess.api.board.Square(de.nowchess.api.board.File.A, de.nowchess.api.board.Rank.R1))
|
|
||||||
.get
|
|
||||||
.pieceType == PieceType.Pawn,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse with all castling rights false") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {
|
|
||||||
"turn": "White",
|
|
||||||
"board": [],
|
|
||||||
"castlingRights": {
|
|
||||||
"whiteKingSide": false,
|
|
||||||
"whiteQueenSide": false,
|
|
||||||
"blackKingSide": false,
|
|
||||||
"blackQueenSide": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.castlingRights.whiteKingSide == false)
|
|
||||||
assert(ctx.castlingRights.blackQueenSide == false)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package de.nowchess.io.json
|
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class JsonParserErrorHandlingSuite extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
test("parse completely invalid JSON returns error") {
|
|
||||||
val invalidJson = "{ this is not valid json at all }"
|
|
||||||
val result = JsonParser.importGameContext(invalidJson)
|
|
||||||
assert(result.isLeft)
|
|
||||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse empty string returns error") {
|
|
||||||
val result = JsonParser.importGameContext("")
|
|
||||||
assert(result.isLeft)
|
|
||||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse number value returns error") {
|
|
||||||
val result = JsonParser.importGameContext("123")
|
|
||||||
assert(result.isLeft)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse malformed JSON object returns error") {
|
|
||||||
val malformed = """{"metadata": {"unclosed": """
|
|
||||||
val result = JsonParser.importGameContext(malformed)
|
|
||||||
assert(result.isLeft)
|
|
||||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse invalid JSON array returns error") {
|
|
||||||
val invalidArray = "[1, 2, 3"
|
|
||||||
val result = JsonParser.importGameContext(invalidArray)
|
|
||||||
assert(result.isLeft)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse JSON with missing required fields") {
|
|
||||||
val json = """{"metadata": {}}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
// Should still succeed because all fields have defaults
|
|
||||||
assert(result.isRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse valid JSON with invalid turn falls back to default") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "White", "board": []},
|
|
||||||
"moves": []
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package de.nowchess.io.json
|
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.board.{Color, File, Piece, PieceType, Rank, Square}
|
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class JsonParserMoveTypeSuite extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
test("parse all move type variations") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {"event": "Game", "result": "*"},
|
|
||||||
"gameState": {"turn": "White", "board": []},
|
|
||||||
"moves": [
|
|
||||||
{"from": "e2", "to": "e4", "type": {"type": "normal", "isCapture": false}},
|
|
||||||
{"from": "e1", "to": "g1", "type": {"type": "castleKingside"}},
|
|
||||||
{"from": "e1", "to": "c1", "type": {"type": "castleQueenside"}},
|
|
||||||
{"from": "e5", "to": "d4", "type": {"type": "enPassant"}},
|
|
||||||
{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "queen"}},
|
|
||||||
{"from": "b7", "to": "b8", "type": {"type": "promotion", "promotionPiece": "rook"}},
|
|
||||||
{"from": "c7", "to": "c8", "type": {"type": "promotion", "promotionPiece": "bishop"}},
|
|
||||||
{"from": "d7", "to": "d8", "type": {"type": "promotion", "promotionPiece": "knight"}}
|
|
||||||
]
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
assert(ctx.moves.length == 8)
|
|
||||||
assert(ctx.moves(0).moveType == MoveType.Normal(false))
|
|
||||||
assert(ctx.moves(1).moveType == MoveType.CastleKingside)
|
|
||||||
assert(ctx.moves(2).moveType == MoveType.CastleQueenside)
|
|
||||||
assert(ctx.moves(3).moveType == MoveType.EnPassant)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse invalid move type defaults to None") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {"event": "Game"},
|
|
||||||
"gameState": {"turn": "White", "board": []},
|
|
||||||
"moves": [{"from": "e2", "to": "e4", "type": {"type": "unknown"}}]
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
// Invalid move type is skipped, so moves list should be empty
|
|
||||||
assert(result.isRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse promotion with default piece") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "White", "board": []},
|
|
||||||
"moves": [{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "invalid"}}]
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
// Invalid promotion piece should use default
|
|
||||||
assert(result.isRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse move with missing from/to skips it") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "White", "board": []},
|
|
||||||
"moves": [{"from": "e2", "to": "invalid", "type": {"type": "normal"}}]
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
// Invalid square should be filtered out
|
|
||||||
assert(ctx.moves.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse with invalid JSON returns error") {
|
|
||||||
val json = """{"invalid json"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isLeft)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse normal move with isCapture true") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {"turn": "White", "board": []},
|
|
||||||
"moves": [{"from": "e4", "to": "d5", "type": {"type": "normal", "isCapture": true}}]
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
val move = ctx.moves.head
|
|
||||||
assert(move.moveType == MoveType.Normal(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("parse board with invalid pieces filters them") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {},
|
|
||||||
"gameState": {
|
|
||||||
"turn": "White",
|
|
||||||
"board": [
|
|
||||||
{"square": "a1", "color": "White", "piece": "Rook"},
|
|
||||||
{"square": "invalid", "color": "White", "piece": "King"},
|
|
||||||
{"square": "a2", "color": "Invalid", "piece": "Pawn"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
assert(result.isRight)
|
|
||||||
val ctx = result.toOption.get
|
|
||||||
// Only valid piece should be in board
|
|
||||||
assert(ctx.board.pieces.size == 1)
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package de.nowchess.io.json
|
|
||||||
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.board.{CastlingRights, Color, File, Rank, Square}
|
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class JsonParserSuite extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
test("importGameContext: parses valid JSON") {
|
|
||||||
val json = JsonExporter.exportGameContext(GameContext.initial)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.isRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: restores board state") {
|
|
||||||
val context = GameContext.initial
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result == Right(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: restores turn") {
|
|
||||||
val context = GameContext.initial.withTurn(Color.Black)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.turn) == Right(Color.Black))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: restores moves") {
|
|
||||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
|
||||||
val context = GameContext.initial.withMove(move)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.moves.length) == Right(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles empty board") {
|
|
||||||
val json = """{
|
|
||||||
"metadata": {"event": "Game", "players": {"white": "A", "black": "B"}, "date": "2026-04-06", "result": "*"},
|
|
||||||
"gameState": {
|
|
||||||
"board": [],
|
|
||||||
"turn": "White",
|
|
||||||
"castlingRights": {"whiteKingSide": true, "whiteQueenSide": true, "blackKingSide": true, "blackQueenSide": true},
|
|
||||||
"enPassantSquare": null,
|
|
||||||
"halfMoveClock": 0
|
|
||||||
},
|
|
||||||
"moves": [],
|
|
||||||
"moveHistory": "",
|
|
||||||
"capturedPieces": {"byWhite": [], "byBlack": []},
|
|
||||||
"timestamp": "2026-04-06T00:00:00Z"
|
|
||||||
}"""
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.isRight)
|
|
||||||
assert(result.map(_.board.pieces.isEmpty) == Right(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: returns error on invalid JSON") {
|
|
||||||
val result = JsonParser.importGameContext("not valid json {{{")
|
|
||||||
|
|
||||||
assert(result.isLeft)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles missing fields with defaults") {
|
|
||||||
val json =
|
|
||||||
"{\"metadata\": {}, \"gameState\": {\"board\": [], \"turn\": \"White\", \"castlingRights\": {\"whiteKingSide\": true, \"whiteQueenSide\": true, \"blackKingSide\": true, \"blackQueenSide\": true}, \"enPassantSquare\": null, \"halfMoveClock\": 0}, \"moves\": [], \"moveHistory\": \"\", \"capturedPieces\": {\"byWhite\": [], \"byBlack\": []}, \"timestamp\": \"2026-01-01T00:00:00Z\"}"
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.isRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles castling rights") {
|
|
||||||
val newCastling = GameContext.initial.castlingRights.copy(whiteKingSide = false)
|
|
||||||
val context = GameContext.initial.withCastlingRights(newCastling)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.castlingRights.whiteKingSide) == Right(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: round-trip consistency") {
|
|
||||||
val move1 = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
|
||||||
val move2 = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R5))
|
|
||||||
val context = GameContext.initial
|
|
||||||
.withMove(move1)
|
|
||||||
.withMove(move2)
|
|
||||||
.withTurn(Color.White)
|
|
||||||
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val restored = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(restored.map(_.moves.length) == Right(2))
|
|
||||||
assert(restored.map(_.turn) == Right(Color.White))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles half-move clock") {
|
|
||||||
val context = GameContext.initial.withHalfMoveClock(5)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.halfMoveClock) == Right(5))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: parses en passant square") {
|
|
||||||
// Create a context with en passant square
|
|
||||||
val epSquare = Some(Square(File.E, Rank.R3))
|
|
||||||
val context = GameContext.initial.copy(enPassantSquare = epSquare)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.enPassantSquare) == Right(epSquare))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles black turn") {
|
|
||||||
val context = GameContext.initial.withTurn(Color.Black)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.turn) == Right(Color.Black))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: preserves basic moves in JSON round-trip") {
|
|
||||||
// Use simple move without explicit moveType to let system handle it
|
|
||||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
|
||||||
val context = GameContext.initial.withMove(move)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.isRight)
|
|
||||||
assert(result.map(_.moves.length) == Right(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles all castling rights disabled") {
|
|
||||||
val noCastling = CastlingRights(false, false, false, false)
|
|
||||||
val context = GameContext.initial.withCastlingRights(noCastling)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.castlingRights) == Right(noCastling))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("importGameContext: handles mixed castling rights") {
|
|
||||||
val mixed = CastlingRights(true, false, false, true)
|
|
||||||
val context = GameContext.initial.withCastlingRights(mixed)
|
|
||||||
val json = JsonExporter.exportGameContext(context)
|
|
||||||
val result = JsonParser.importGameContext(json)
|
|
||||||
|
|
||||||
assert(result.map(_.castlingRights) == Right(mixed))
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
package de.nowchess.io.json
|
||||||
|
|
||||||
|
import de.nowchess.api.game.GameContext
|
||||||
|
import de.nowchess.api.board.{CastlingRights, Color, File, PieceType, Rank, Square}
|
||||||
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
|
class JsonParserTest extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
|
// Basic import tests
|
||||||
|
test("importGameContext: parses valid JSON") {
|
||||||
|
val json = JsonExporter.exportGameContext(GameContext.initial)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: restores board state") {
|
||||||
|
val context = GameContext.initial
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result == Right(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: restores turn") {
|
||||||
|
val context = GameContext.initial.withTurn(Color.Black)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.turn) == Right(Color.Black))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: restores moves") {
|
||||||
|
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||||
|
val context = GameContext.initial.withMove(move)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.moves.length) == Right(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: handles castling rights") {
|
||||||
|
val newCastling = GameContext.initial.castlingRights.copy(whiteKingSide = false)
|
||||||
|
val context = GameContext.initial.withCastlingRights(newCastling)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.castlingRights.whiteKingSide) == Right(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: round-trip consistency with multiple moves") {
|
||||||
|
val move1 = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||||
|
val move2 = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R5))
|
||||||
|
val context = GameContext.initial
|
||||||
|
.withMove(move1)
|
||||||
|
.withMove(move2)
|
||||||
|
.withTurn(Color.White)
|
||||||
|
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val restored = JsonParser.importGameContext(json)
|
||||||
|
assert(restored.map(_.moves.length) == Right(2))
|
||||||
|
assert(restored.map(_.turn) == Right(Color.White))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: handles half-move clock") {
|
||||||
|
val context = GameContext.initial.withHalfMoveClock(5)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.halfMoveClock) == Right(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: parses en passant square") {
|
||||||
|
val epSquare = Some(Square(File.E, Rank.R3))
|
||||||
|
val context = GameContext.initial.copy(enPassantSquare = epSquare)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.enPassantSquare) == Right(epSquare))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: handles all castling rights disabled") {
|
||||||
|
val noCastling = CastlingRights(false, false, false, false)
|
||||||
|
val context = GameContext.initial.withCastlingRights(noCastling)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.castlingRights) == Right(noCastling))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: handles mixed castling rights") {
|
||||||
|
val mixed = CastlingRights(true, false, false, true)
|
||||||
|
val context = GameContext.initial.withCastlingRights(mixed)
|
||||||
|
val json = JsonExporter.exportGameContext(context)
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.map(_.castlingRights) == Right(mixed))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling tests
|
||||||
|
test("parse completely invalid JSON returns error") {
|
||||||
|
val invalidJson = "{ this is not valid json at all }"
|
||||||
|
val result = JsonParser.importGameContext(invalidJson)
|
||||||
|
assert(result.isLeft)
|
||||||
|
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse empty string returns error") {
|
||||||
|
val result = JsonParser.importGameContext("")
|
||||||
|
assert(result.isLeft)
|
||||||
|
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse number value returns error") {
|
||||||
|
val result = JsonParser.importGameContext("123")
|
||||||
|
assert(result.isLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse malformed JSON object returns error") {
|
||||||
|
val malformed = """{"metadata": {"unclosed": """
|
||||||
|
val result = JsonParser.importGameContext(malformed)
|
||||||
|
assert(result.isLeft)
|
||||||
|
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse invalid JSON array returns error") {
|
||||||
|
val invalidArray = "[1, 2, 3"
|
||||||
|
val result = JsonParser.importGameContext(invalidArray)
|
||||||
|
assert(result.isLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse JSON with missing required fields") {
|
||||||
|
val json = """{"metadata": {}}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge cases with defaults
|
||||||
|
test("parse invalid turn color returns error") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"turn": "Invalid", "board": []},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isLeft)
|
||||||
|
assert(result.left.toOption.get.contains("Invalid turn color"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse invalid piece type filters it out") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {
|
||||||
|
"turn": "White",
|
||||||
|
"board": [
|
||||||
|
{"square": "a1", "color": "White", "piece": "InvalidPiece"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.board.pieces.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse invalid color in board filters piece") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {
|
||||||
|
"turn": "White",
|
||||||
|
"board": [
|
||||||
|
{"square": "a1", "color": "InvalidColor", "piece": "Pawn"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.board.pieces.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse with missing turn uses default") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"board": []},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.turn == Color.White)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse with missing board uses empty") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"turn": "White"},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.board.pieces.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse with missing moves uses empty list") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"turn": "White", "board": []}
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.moves.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse invalid square in board filters it") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {
|
||||||
|
"turn": "White",
|
||||||
|
"board": [
|
||||||
|
{"square": "invalid99", "color": "White", "piece": "Pawn"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.board.pieces.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse all valid piece types") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {
|
||||||
|
"turn": "White",
|
||||||
|
"board": [
|
||||||
|
{"square": "a1", "color": "White", "piece": "Pawn"},
|
||||||
|
{"square": "b1", "color": "White", "piece": "Knight"},
|
||||||
|
{"square": "c1", "color": "White", "piece": "Bishop"},
|
||||||
|
{"square": "d1", "color": "White", "piece": "Rook"},
|
||||||
|
{"square": "e1", "color": "White", "piece": "Queen"},
|
||||||
|
{"square": "f1", "color": "White", "piece": "King"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.board.pieces.size == 6)
|
||||||
|
assert(
|
||||||
|
ctx.board
|
||||||
|
.pieceAt(de.nowchess.api.board.Square(de.nowchess.api.board.File.A, de.nowchess.api.board.Rank.R1))
|
||||||
|
.get
|
||||||
|
.pieceType == PieceType.Pawn,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse with all castling rights false") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {
|
||||||
|
"turn": "White",
|
||||||
|
"board": [],
|
||||||
|
"castlingRights": {
|
||||||
|
"whiteKingSide": false,
|
||||||
|
"whiteQueenSide": false,
|
||||||
|
"blackKingSide": false,
|
||||||
|
"blackQueenSide": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"moves": []
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.castlingRights.whiteKingSide == false)
|
||||||
|
assert(ctx.castlingRights.blackQueenSide == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move type parsing tests
|
||||||
|
test("parse all move type variations") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {"event": "Game", "result": "*"},
|
||||||
|
"gameState": {"turn": "White", "board": []},
|
||||||
|
"moves": [
|
||||||
|
{"from": "e2", "to": "e4", "type": {"type": "normal", "isCapture": false}},
|
||||||
|
{"from": "e1", "to": "g1", "type": {"type": "castleKingside"}},
|
||||||
|
{"from": "e1", "to": "c1", "type": {"type": "castleQueenside"}},
|
||||||
|
{"from": "e5", "to": "d4", "type": {"type": "enPassant"}},
|
||||||
|
{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "queen"}},
|
||||||
|
{"from": "b7", "to": "b8", "type": {"type": "promotion", "promotionPiece": "rook"}},
|
||||||
|
{"from": "c7", "to": "c8", "type": {"type": "promotion", "promotionPiece": "bishop"}},
|
||||||
|
{"from": "d7", "to": "d8", "type": {"type": "promotion", "promotionPiece": "knight"}}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.moves.length == 8)
|
||||||
|
assert(ctx.moves(0).moveType == MoveType.Normal(false))
|
||||||
|
assert(ctx.moves(1).moveType == MoveType.CastleKingside)
|
||||||
|
assert(ctx.moves(2).moveType == MoveType.CastleQueenside)
|
||||||
|
assert(ctx.moves(3).moveType == MoveType.EnPassant)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse invalid move type defaults to None") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {"event": "Game"},
|
||||||
|
"gameState": {"turn": "White", "board": []},
|
||||||
|
"moves": [{"from": "e2", "to": "e4", "type": {"type": "unknown"}}]
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse promotion with invalid piece uses default") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"turn": "White", "board": []},
|
||||||
|
"moves": [{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "invalid"}}]
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse move with invalid from/to skips it") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"turn": "White", "board": []},
|
||||||
|
"moves": [{"from": "e2", "to": "invalid", "type": {"type": "normal"}}]
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.moves.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse normal move with isCapture true") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {"turn": "White", "board": []},
|
||||||
|
"moves": [{"from": "e4", "to": "d5", "type": {"type": "normal", "isCapture": true}}]
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
val move = ctx.moves.head
|
||||||
|
assert(move.moveType == MoveType.Normal(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse board with invalid pieces filters them") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {},
|
||||||
|
"gameState": {
|
||||||
|
"turn": "White",
|
||||||
|
"board": [
|
||||||
|
{"square": "a1", "color": "White", "piece": "Rook"},
|
||||||
|
{"square": "invalid", "color": "White", "piece": "King"},
|
||||||
|
{"square": "a2", "color": "Invalid", "piece": "Pawn"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
val ctx = result.toOption.get
|
||||||
|
assert(ctx.board.pieces.size == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parse with empty board") {
|
||||||
|
val json = """{
|
||||||
|
"metadata": {"event": "Game", "players": {"white": "A", "black": "B"}, "date": "2026-04-06", "result": "*"},
|
||||||
|
"gameState": {
|
||||||
|
"board": [],
|
||||||
|
"turn": "White",
|
||||||
|
"castlingRights": {"whiteKingSide": true, "whiteQueenSide": true, "blackKingSide": true, "blackQueenSide": true},
|
||||||
|
"enPassantSquare": null,
|
||||||
|
"halfMoveClock": 0
|
||||||
|
},
|
||||||
|
"moves": [],
|
||||||
|
"moveHistory": "",
|
||||||
|
"capturedPieces": {"byWhite": [], "byBlack": []},
|
||||||
|
"timestamp": "2026-04-06T00:00:00Z"
|
||||||
|
}"""
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
assert(result.map(_.board.pieces.isEmpty) == Right(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: returns error on invalid JSON") {
|
||||||
|
val result = JsonParser.importGameContext("not valid json {{{")
|
||||||
|
assert(result.isLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("importGameContext: handles missing fields with defaults") {
|
||||||
|
val json =
|
||||||
|
"{\"metadata\": {}, \"gameState\": {\"board\": [], \"turn\": \"White\", \"castlingRights\": {\"whiteKingSide\": true, \"whiteQueenSide\": true, \"blackKingSide\": true, \"blackQueenSide\": true}, \"enPassantSquare\": null, \"halfMoveClock\": 0}, \"moves\": [], \"moveHistory\": \"\", \"capturedPieces\": {\"byWhite\": [], \"byBlack\": []}, \"timestamp\": \"2026-01-01T00:00:00Z\"}"
|
||||||
|
val result = JsonParser.importGameContext(json)
|
||||||
|
assert(result.isRight)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
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
|
MAJOR=0
|
||||||
MINOR=10
|
MINOR=12
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -79,3 +79,29 @@
|
|||||||
### Bug Fixes
|
### 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))
|
* 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))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=5
|
MINOR=7
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
Reference in New Issue
Block a user