Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e22924053 | |||
| 97c3ff5e67 | |||
| b8f5c8eb77 | |||
| 5a4fcb1b55 | |||
| 83c7d3a46b | |||
| 7855f0c136 | |||
| f61ffce22a | |||
| 381c3f06a1 |
@@ -20,4 +20,5 @@ When invoked BEFORE scala-implementer (no implementation exists yet):
|
|||||||
|
|
||||||
When invoked AFTER scala-implementer (implementation exists):
|
When invoked AFTER scala-implementer (implementation exists):
|
||||||
Run python3 jacoco-reporter/jacoco_coverage_gaps.py modules/{service-name}/build/reports/jacoco/test/jacocoTestReport.xml --output agent
|
Run python3 jacoco-reporter/jacoco_coverage_gaps.py modules/{service-name}/build/reports/jacoco/test/jacocoTestReport.xml --output agent
|
||||||
|
Use the jacoco-coverage-gaps skill — close coverage gaps revealed by the report.
|
||||||
To regenerate the report run the tests first.
|
To regenerate the report run the tests first.
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Memory Index
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
- [feedback_keep_structure_updated.md](feedback_keep_structure_updated.md) — Update structure memory files whenever source files are added, removed, or changed
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
- [project_structure_root.md](project_structure_root.md) — Top-level layout, modules list, VERSIONS map, navigation rules (skip `build/`, `.gradle/`, `.idea/`)
|
||||||
|
- [project_structure_api.md](project_structure_api.md) — `modules/api`: all files and types (Board, Piece, Square, GameState, Move, ApiResponse, PlayerInfo)
|
||||||
|
- [project_structure_core.md](project_structure_core.md) — `modules/core`: all files and types (GameContext, GameRules, MoveValidator, GameController, Parser, Renderer)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: keep-structure-memory-updated
|
||||||
|
description: Always update the project structure memory files when adding, removing, or changing source files
|
||||||
|
type: feedback
|
||||||
|
---
|
||||||
|
|
||||||
|
After any change that adds, removes, renames, or significantly alters a source file, update the relevant structure memory file:
|
||||||
|
|
||||||
|
- New/renamed/deleted file in `modules/api` → update `project_structure_api.md`
|
||||||
|
- New/renamed/deleted file in `modules/core` → update `project_structure_core.md`
|
||||||
|
- New module, dependency version change, or new top-level directory → update `project_structure_root.md`
|
||||||
|
- New module added → create a new `project_structure_<module>.md` and add it to `MEMORY.md`
|
||||||
|
|
||||||
|
**Why:** Structure memories are the primary navigation aid. Stale entries cause wasted exploration.
|
||||||
|
|
||||||
|
**How to apply:** Treat the structure memory update as part of completing any implementation task — do it in the same session, not as a follow-up.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: module-api-structure
|
||||||
|
description: File and type overview for the modules/api module (shared domain types)
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
# Module: `modules/api`
|
||||||
|
|
||||||
|
**Purpose:** Shared domain model — pure data types with no game logic. Depended on by `modules/core`.
|
||||||
|
|
||||||
|
**Gradle:** `id("scala")`, no `application` plugin. No Quarkus. Uses scoverage plugin.
|
||||||
|
|
||||||
|
**Package root:** `de.nowchess.api`
|
||||||
|
|
||||||
|
## Source files (`src/main/scala/de/nowchess/api/`)
|
||||||
|
|
||||||
|
### `board/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `Board.scala` | `opaque type Board = Map[Square, Piece]` — extensions: `pieceAt`, `withMove`, `pieces`; `Board.initial` sets up start position |
|
||||||
|
| `Color.scala` | `enum Color { White, Black }` — `.opposite`, `.label` |
|
||||||
|
| `Piece.scala` | `case class Piece(color, pieceType)` — convenience vals `WhitePawn`…`BlackKing` |
|
||||||
|
| `PieceType.scala` | `enum PieceType { Pawn, Knight, Bishop, Rook, Queen, King }` — `.label` |
|
||||||
|
| `Square.scala` | `enum File { A–H }`, `enum Rank { R1–R8 }`, `case class Square(file, rank)` — `.toString` algebraic, `Square.fromAlgebraic(s)` |
|
||||||
|
|
||||||
|
### `game/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `GameState.scala` | `case class CastlingRights(kingSide, queenSide)` + `.None`/`.Both`; `enum GameResult { WhiteWins, BlackWins, Draw }`; `enum GameStatus { NotStarted, InProgress, Finished(result) }`; `case class GameState(piecePlacement, activeColor, castlingWhite, castlingBlack, enPassantTarget, halfMoveClock, fullMoveNumber, status)` — FEN-compatible snapshot |
|
||||||
|
|
||||||
|
### `move/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `Move.scala` | `enum PromotionPiece { Knight, Bishop, Rook, Queen }`; `enum MoveType { Normal, CastleKingside, CastleQueenside, EnPassant, Promotion(piece) }`; `case class Move(from, to, moveType = Normal)` |
|
||||||
|
|
||||||
|
### `player/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `PlayerInfo.scala` | `opaque type PlayerId = String`; `case class PlayerInfo(id: PlayerId, displayName: String)` |
|
||||||
|
|
||||||
|
### `response/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `ApiResponse.scala` | `sealed trait ApiResponse[+A]` → `Success[A](data)` / `Failure(errors)`; `case class ApiError(code, message, field?)`; `case class Pagination(page, pageSize, totalItems)` + `.totalPages`; `case class PagedResponse[A](items, pagination)` |
|
||||||
|
|
||||||
|
## Test files (`src/test/scala/de/nowchess/api/`)
|
||||||
|
Mirror of main structure — one `*Test.scala` per source file using `AnyFunSuite with Matchers`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- `GameState` is FEN-style but `Board` (in `core`) is a `Map[Square,Piece]` — the two are separate representations
|
||||||
|
- `CastlingRights` is defined here in `api`; the castling logic lives in `core`
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: module-core-structure
|
||||||
|
description: File and type overview for the modules/core module (TUI chess engine)
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
# Module: `modules/core`
|
||||||
|
|
||||||
|
**Purpose:** Standalone TUI chess application. All game logic, move validation, rendering. Depends on `modules/api`.
|
||||||
|
|
||||||
|
**Gradle:** `id("scala")` + `application` plugin. Main class: `de.nowchess.chess.Main`. Uses scoverage plugin.
|
||||||
|
|
||||||
|
**Package root:** `de.nowchess.chess`
|
||||||
|
|
||||||
|
## Source files (`src/main/scala/de/nowchess/chess/`)
|
||||||
|
|
||||||
|
### Root
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `Main.scala` | Entry point — prints welcome, starts `GameController.gameLoop(GameContext.initial, Color.White)` |
|
||||||
|
|
||||||
|
### `controller/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `GameController.scala` | `sealed trait MoveResult` ADT: `Quit`, `InvalidFormat`, `NoPiece`, `WrongColor`, `IllegalMove`, `Moved`, `MovedInCheck`, `Checkmate`, `Stalemate`; `object GameController` — `processMove(ctx, turn, raw): MoveResult` (pure), `gameLoop(ctx, turn)` (I/O loop), `applyRightsRevocation(...)` (castling rights bookkeeping) |
|
||||||
|
| `Parser.scala` | `object Parser` — `parseMove(input): Option[(Square, Square)]` parses coordinate notation e.g. `"e2e4"` |
|
||||||
|
|
||||||
|
### `logic/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `GameContext.scala` | `enum CastleSide { Kingside, Queenside }`; `case class GameContext(board, whiteCastling, blackCastling)` — `.castlingFor(color)`, `.withUpdatedRights(color, rights)`; `GameContext.initial`; `extension (Board).withCastle(color, side)` moves king+rook atomically |
|
||||||
|
| `GameRules.scala` | `enum PositionStatus { Normal, InCheck, Mated, Drawn }`; `object GameRules` — `isInCheck(board, color)`, `legalMoves(ctx, color): Set[(Square,Square)]`, `gameStatus(ctx, color): PositionStatus` |
|
||||||
|
| `MoveValidator.scala` | `object MoveValidator` — `isLegal(board, from, to)`, `legalTargets(board, from): Set[Square]` (board-only, no castling), `legalTargets(ctx, from)` (context-aware, includes castling), `isCastle`, `castleSide`, `castlingTargets(ctx, color)` — full castling legality (empty squares, no check through transit) |
|
||||||
|
|
||||||
|
### `view/`
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `Renderer.scala` | `object Renderer` — `render(board): String` outputs ANSI-colored board with file/rank labels |
|
||||||
|
| `PieceUnicode.scala` | `extension (Piece).unicode: String` maps each piece to its Unicode chess symbol |
|
||||||
|
|
||||||
|
## Test files (`src/test/scala/de/nowchess/chess/`)
|
||||||
|
Mirror of main structure — one `*Test.scala` per source file using `AnyFunSuite with Matchers`.
|
||||||
|
|
||||||
|
## Key design notes
|
||||||
|
- `MoveValidator` has two overloaded `legalTargets`: one takes `Board` (geometry only), one takes `GameContext` (adds castling)
|
||||||
|
- `GameRules.legalMoves` filters by check — it calls `MoveValidator.legalTargets(ctx, from)` then simulates each move
|
||||||
|
- Castling rights revocation is in `GameController.applyRightsRevocation`, triggered after every move
|
||||||
|
- No `@QuarkusTest` — this module is a plain Scala application, not a Quarkus service
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
name: project-root-structure
|
||||||
|
description: Top-level project structure, modules list, and navigation notes for NowChessSystems
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
# NowChessSystems — Root Structure
|
||||||
|
|
||||||
|
## Directory layout (skip `build/`, `.gradle/`, `.idea/`)
|
||||||
|
|
||||||
|
```
|
||||||
|
NowChessSystems/
|
||||||
|
├── build.gradle.kts # Root: sonarqube plugin, VERSIONS map
|
||||||
|
├── settings.gradle.kts # include(":modules:core", ":modules:api")
|
||||||
|
├── gradlew / gradlew.bat
|
||||||
|
├── CLAUDE.md # Project instructions for Claude Code
|
||||||
|
├── .claude/
|
||||||
|
│ ├── CLAUDE.MD # Working agreement (plan/verify/unresolved)
|
||||||
|
│ ├── settings.json
|
||||||
|
│ └── agents/ # architect, code-reviewer, gradle-builder, scala-implementer, test-writer
|
||||||
|
├── docs/
|
||||||
|
│ ├── Claude-Skills.md
|
||||||
|
│ ├── Security.md
|
||||||
|
│ └── unresolved.md
|
||||||
|
├── jacoco-reporter/ # Python scripts for coverage gap reporting
|
||||||
|
└── modules/
|
||||||
|
├── api/ # Shared domain types (no logic)
|
||||||
|
└── core/ # TUI chess engine + game logic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
| Module | Gradle path | Purpose |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| `api` | `:modules:api` | Shared domain model: Board, Piece, Move, GameState, ApiResponse |
|
||||||
|
| `core` | `:modules:core` | TUI chess app: game logic, move validation, rendering |
|
||||||
|
|
||||||
|
`core` depends on `api` via `implementation(project(":modules:api"))`.
|
||||||
|
|
||||||
|
## VERSIONS (root `build.gradle.kts`)
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-----|-------|
|
||||||
|
| `QUARKUS_SCALA3` | 1.0.0 |
|
||||||
|
| `SCALA3` | 3.5.1 |
|
||||||
|
| `SCALA_LIBRARY` | 2.13.18 |
|
||||||
|
| `SCALATEST` | 3.2.19 |
|
||||||
|
| `SCALATEST_JUNIT` | 0.1.11 |
|
||||||
|
| `SCOVERAGE` | 2.1.1 |
|
||||||
|
|
||||||
|
## Navigation rules
|
||||||
|
- **Always skip** `build/`, `.gradle/`, `.idea/` when exploring — they are generated artifacts
|
||||||
|
- Tests use `AnyFunSuite with Matchers` (ScalaTest), not JUnit `@Test`
|
||||||
|
- No Quarkus in current modules — Quarkus is planned for future services
|
||||||
|
- Agent workflow: architect → scala-implementer → test-writer → gradle-builder → code-reviewer
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"enabledPlugins": {
|
"enabledPlugins": {
|
||||||
"superpowers@claude-plugins-official": false,
|
"superpowers@claude-plugins-official": true,
|
||||||
"ui-ux-pro-max@ui-ux-pro-max-skill": false
|
"ui-ux-pro-max@ui-ux-pro-max-skill": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,340 +0,0 @@
|
|||||||
# NowChessSystems — AI Context Map
|
|
||||||
|
|
||||||
> **Stack:** raw-http | none | unknown | scala
|
|
||||||
|
|
||||||
> 0 routes | 0 models | 0 components | 63 lib files | 1 env vars | 1 middleware
|
|
||||||
> **Token savings:** this file is ~0 tokens. Without it, AI exploration would cost ~0 tokens. **Saves ~0 tokens per conversation.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
|
|
||||||
- `jacoco-reporter/scoverage_coverage_gaps.py`
|
|
||||||
- function parse_scoverage_xml: (xml_path) -> tuple[dict, list[ClassGap]]
|
|
||||||
- function format_agent: (project_stats, classes) -> str
|
|
||||||
- function format_json: (project_stats, classes) -> str
|
|
||||||
- function format_markdown: (project_stats, classes) -> str
|
|
||||||
- function format_module_gaps: (module_name, classes, stmt_pct) -> str
|
|
||||||
- function run_scan_modules: (modules_dir, package_filter, min_coverage) -> None
|
|
||||||
- _...4 more_
|
|
||||||
- `jacoco-reporter/test_gaps.py`
|
|
||||||
- function parse_suite_xml: (xml_path) -> SuiteResult
|
|
||||||
- function load_module: (module_dir, results_subdir) -> Optional[ModuleResult]
|
|
||||||
- function format_module: (mod) -> str
|
|
||||||
- function run: (modules_dir, results_subdir, module_filter) -> None
|
|
||||||
- function main: () -> None
|
|
||||||
- class TestCase
|
|
||||||
- _...2 more_
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala`
|
|
||||||
- class Board
|
|
||||||
- function apply
|
|
||||||
- function pieceAt
|
|
||||||
- function updated
|
|
||||||
- function removed
|
|
||||||
- function withMove
|
|
||||||
- _...2 more_
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala`
|
|
||||||
- function hasAnyRights
|
|
||||||
- function hasRights
|
|
||||||
- function revokeColor
|
|
||||||
- function revokeKingSide
|
|
||||||
- function revokeQueenSide
|
|
||||||
- class CastlingRights
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — function opposite, function label
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — class Piece
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — function label
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala`
|
|
||||||
- class Square
|
|
||||||
- function fromAlgebraic
|
|
||||||
- function offset
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`
|
|
||||||
- class Bot
|
|
||||||
- function name
|
|
||||||
- function nextMove
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/dto/ErrorEventDto.scala` — class ErrorEventDto, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/dto/GameFullEventDto.scala` — class GameFullEventDto, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/dto/GameStateEventDto.scala` — class GameStateEventDto, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`
|
|
||||||
- function kingSquare
|
|
||||||
- function withBoard
|
|
||||||
- function withTurn
|
|
||||||
- function withCastlingRights
|
|
||||||
- function withEnPassantSquare
|
|
||||||
- function withHalfMoveClock
|
|
||||||
- _...4 more_
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
|
||||||
- class ApiResponse
|
|
||||||
- function error
|
|
||||||
- function totalPages
|
|
||||||
- `modules/bot/python/nnue.py`
|
|
||||||
- function get_weights_dir: ()
|
|
||||||
- function get_data_dir: ()
|
|
||||||
- function list_checkpoints: ()
|
|
||||||
- function migrate_legacy_data: ()
|
|
||||||
- function show_header: ()
|
|
||||||
- function show_checkpoints_table: ()
|
|
||||||
- _...10 more_
|
|
||||||
- `modules/bot/python/src/dataset.py`
|
|
||||||
- function get_datasets_dir: () -> Path
|
|
||||||
- function next_dataset_version: () -> int
|
|
||||||
- function list_datasets: () -> List[Tuple[int, Dict]]
|
|
||||||
- function load_dataset_metadata: (version) -> Optional[Dict]
|
|
||||||
- function save_dataset_metadata: (version, metadata) -> None
|
|
||||||
- function create_dataset: (version, labeled_jsonl_path, sources, stockfish_depth) -> Path
|
|
||||||
- _...4 more_
|
|
||||||
- `modules/bot/python/src/export.py` — function export_to_nbai: (weights_file, output_file, trained_by, train_loss)
|
|
||||||
- `modules/bot/python/src/generate.py` — function play_random_game_and_collect_positions: (output_file, total_positions, samples_per_game, min_move, max_move, num_workers)
|
|
||||||
- `modules/bot/python/src/label.py` — function normalize_evaluation: (cp_value, method, scale), function label_positions_with_stockfish: (positions_file, output_file, stockfish_path, batch_size, depth, verbose, normalize, num_workers)
|
|
||||||
- `modules/bot/python/src/lichess_importer.py` — function import_lichess_evals: (input_path, output_file, max_positions, min_depth, verbose) -> int
|
|
||||||
- `modules/bot/python/src/tactical_positions_extractor.py`
|
|
||||||
- function download_and_extract_puzzle_db: (url, output_dir)
|
|
||||||
- function extract_puzzle_positions: (puzzle_csv, max_puzzles) -> Set[str]
|
|
||||||
- function load_positions_from_file: (file_path) -> Set[str]
|
|
||||||
- function merge_positions: (tactical, other, output_file)
|
|
||||||
- function extract_tactical_only: (puzzle_csv, output_file, max_puzzles) -> int
|
|
||||||
- function interactive_merge_positions: (puzzle_csv, output_file, max_puzzles)
|
|
||||||
- `modules/bot/python/src/train.py`
|
|
||||||
- function fen_to_features: (fen)
|
|
||||||
- function find_next_version: (base_name)
|
|
||||||
- function save_metadata: (weights_file, metadata)
|
|
||||||
- function train_nnue: (data_file, output_file, epochs, batch_size, lr, checkpoint, stockfish_depth, use_versioning, early_stopping_patience, weight_decay, subsample_ratio, hidden_sizes)
|
|
||||||
- function burst_train: (data_file, output_file, duration_minutes, epochs_per_season, early_stopping_patience, batch_size, lr, initial_checkpoint, stockfish_depth, use_versioning, weight_decay, subsample_ratio, hidden_sizes)
|
|
||||||
- class NNUEDataset
|
|
||||||
- _...1 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/BotController.scala`
|
|
||||||
- class BotController
|
|
||||||
- function getBot
|
|
||||||
- function listBots
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala`
|
|
||||||
- class BotMoveRepetition
|
|
||||||
- function blockedMoves
|
|
||||||
- function repeatedMove
|
|
||||||
- function filterAllowed
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/Config.scala` — class Config
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala`
|
|
||||||
- class Evaluation
|
|
||||||
- class CHECKMATE_SCORE
|
|
||||||
- class DRAW_SCORE
|
|
||||||
- function evaluate
|
|
||||||
- function initAccumulator
|
|
||||||
- function copyAccumulator
|
|
||||||
- _...2 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`
|
|
||||||
- class EvaluationClassic
|
|
||||||
- function evaluate
|
|
||||||
- function countRay
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/EvaluationNNUE.scala` — class EvaluationNNUE, function evaluate
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`
|
|
||||||
- class NNUE
|
|
||||||
- function initAccumulator
|
|
||||||
- function pushAccumulator
|
|
||||||
- function copyAccumulator
|
|
||||||
- function recomputeAccumulator
|
|
||||||
- function validateAccumulator
|
|
||||||
- _...4 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiLoader.scala`
|
|
||||||
- class NbaiLoader
|
|
||||||
- function load
|
|
||||||
- function loadDefault
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiMigrator.scala` — class NbaiMigrator, function migrateFromBin
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiModel.scala`
|
|
||||||
- function toJson
|
|
||||||
- class NbaiMetadata
|
|
||||||
- function fromJson
|
|
||||||
- function str
|
|
||||||
- function num
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiWriter.scala` — class NbaiWriter, function write
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`
|
|
||||||
- function bestMove
|
|
||||||
- function bestMove
|
|
||||||
- function bestMoveWithTime
|
|
||||||
- function bestMoveWithTime
|
|
||||||
- function loop
|
|
||||||
- function loop
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`
|
|
||||||
- class MoveOrdering
|
|
||||||
- class OrderingContext
|
|
||||||
- function addKillerMove
|
|
||||||
- function getKillerMoves
|
|
||||||
- function addHistory
|
|
||||||
- function getHistory
|
|
||||||
- _...3 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/logic/TranspositionTable.scala`
|
|
||||||
- function advance
|
|
||||||
- function probe
|
|
||||||
- function store
|
|
||||||
- function clear
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala` — function probe, function select
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala` — class PolyglotHash, function hash
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`
|
|
||||||
- class ZobristHash
|
|
||||||
- function hash
|
|
||||||
- function nextHash
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`
|
|
||||||
- class Command
|
|
||||||
- function execute
|
|
||||||
- function undo
|
|
||||||
- function description
|
|
||||||
- class MoveResult
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/CommandInvoker.scala`
|
|
||||||
- class CommandInvoker
|
|
||||||
- function execute
|
|
||||||
- function undo
|
|
||||||
- function redo
|
|
||||||
- function history
|
|
||||||
- function getCurrentIndex
|
|
||||||
- _...3 more_
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — class Parser, function parseMove
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
|
||||||
- class GameEngine
|
|
||||||
- function board
|
|
||||||
- function turn
|
|
||||||
- function context
|
|
||||||
- function canUndo
|
|
||||||
- function canRedo
|
|
||||||
- _...11 more_
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
|
||||||
- function context
|
|
||||||
- class Observer
|
|
||||||
- function onGameEvent
|
|
||||||
- class Observable
|
|
||||||
- function subscribe
|
|
||||||
- function unsubscribe
|
|
||||||
- _...1 more_
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/registry/GameRegistry.scala`
|
|
||||||
- class GameRegistry
|
|
||||||
- function store
|
|
||||||
- function get
|
|
||||||
- function update
|
|
||||||
- function generateId
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.scala`
|
|
||||||
- class GameRegistryImpl
|
|
||||||
- function store
|
|
||||||
- function get
|
|
||||||
- function update
|
|
||||||
- function generateId
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala`
|
|
||||||
- function onGameEvent
|
|
||||||
- function createGame
|
|
||||||
- function getGame
|
|
||||||
- function streamGame
|
|
||||||
- function onGameEvent
|
|
||||||
- function resignGame
|
|
||||||
- _...9 more_
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameFileService.scala`
|
|
||||||
- class GameFileService
|
|
||||||
- function saveGameToFile
|
|
||||||
- function loadGameFromFile
|
|
||||||
- class FileSystemGameService
|
|
||||||
- function saveGameToFile
|
|
||||||
- function loadGameFromFile
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
|
||||||
- class FenExporter
|
|
||||||
- function boardToFen
|
|
||||||
- function gameContextToFen
|
|
||||||
- function exportGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`
|
|
||||||
- class FenParser
|
|
||||||
- function parseFen
|
|
||||||
- function importGameContext
|
|
||||||
- function parseBoard
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`
|
|
||||||
- class FenParserCombinators
|
|
||||||
- function parseFen
|
|
||||||
- function parseBoard
|
|
||||||
- function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala`
|
|
||||||
- class FenParserFastParse
|
|
||||||
- function parseFen
|
|
||||||
- function parseBoard
|
|
||||||
- function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
|
||||||
- class PgnExporter
|
|
||||||
- function exportGameContext
|
|
||||||
- function exportGame
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala`
|
|
||||||
- class PgnParser
|
|
||||||
- function validatePgn
|
|
||||||
- function importGameContext
|
|
||||||
- function parsePgn
|
|
||||||
- function parseAlgebraicMove
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala`
|
|
||||||
- class RuleSet
|
|
||||||
- function candidateMoves
|
|
||||||
- function legalMoves
|
|
||||||
- function allLegalMoves
|
|
||||||
- function isCheck
|
|
||||||
- function isCheckmate
|
|
||||||
- _...5 more_
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala`
|
|
||||||
- class DefaultRules
|
|
||||||
- function positionOf
|
|
||||||
- function loop
|
|
||||||
- function toMoves
|
|
||||||
- function loop
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Config
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
- `STOCKFISH_PATH` **required** — modules/bot/python/nnue.py
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Middleware
|
|
||||||
|
|
||||||
## custom
|
|
||||||
- generate — `modules/bot/python/src/generate.py`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Dependency Graph
|
|
||||||
|
|
||||||
## Most Imported Files (change these carefully)
|
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **64** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **44** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **40** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **35** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **19** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **18** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **17** files
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **17** files
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **11** files
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **10** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **9** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` — imported by **7** files
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **7** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala` — imported by **6** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala` — imported by **6** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **5** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala` — imported by **5** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` — imported by **4** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala` — imported by **4** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala` — imported by **4** files
|
|
||||||
|
|
||||||
## Import Map (who imports what)
|
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala`, `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala` +59 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala` +39 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala` +35 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala` +30 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +14 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +13 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala` +12 more
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +12 more
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +6 more
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` ← `modules/bot/src/test/scala/de/nowchess/bot/PolyglotHashTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala` +5 more
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_Generated by [codesight](https://github.com/Houseofmvps/codesight) — see your codebase clearly_
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Config
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
- `STOCKFISH_PATH` **required** — modules/bot/python/nnue.py
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Dependency Graph
|
|
||||||
|
|
||||||
## Most Imported Files (change these carefully)
|
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **64** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **44** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **40** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **35** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **19** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **18** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **17** files
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **17** files
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **11** files
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **10** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **9** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` — imported by **7** files
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **7** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala` — imported by **6** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala` — imported by **6** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **5** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala` — imported by **5** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` — imported by **4** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala` — imported by **4** files
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala` — imported by **4** files
|
|
||||||
|
|
||||||
## Import Map (who imports what)
|
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala`, `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala` +59 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala` +39 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala` +35 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala` +30 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +14 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +13 more
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala` +12 more
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +12 more
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +6 more
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` ← `modules/bot/src/test/scala/de/nowchess/bot/PolyglotHashTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala` +5 more
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
# Libraries
|
|
||||||
|
|
||||||
- `jacoco-reporter/scoverage_coverage_gaps.py`
|
|
||||||
- function parse_scoverage_xml: (xml_path) -> tuple[dict, list[ClassGap]]
|
|
||||||
- function format_agent: (project_stats, classes) -> str
|
|
||||||
- function format_json: (project_stats, classes) -> str
|
|
||||||
- function format_markdown: (project_stats, classes) -> str
|
|
||||||
- function format_module_gaps: (module_name, classes, stmt_pct) -> str
|
|
||||||
- function run_scan_modules: (modules_dir, package_filter, min_coverage) -> None
|
|
||||||
- _...4 more_
|
|
||||||
- `jacoco-reporter/test_gaps.py`
|
|
||||||
- function parse_suite_xml: (xml_path) -> SuiteResult
|
|
||||||
- function load_module: (module_dir, results_subdir) -> Optional[ModuleResult]
|
|
||||||
- function format_module: (mod) -> str
|
|
||||||
- function run: (modules_dir, results_subdir, module_filter) -> None
|
|
||||||
- function main: () -> None
|
|
||||||
- class TestCase
|
|
||||||
- _...2 more_
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala`
|
|
||||||
- class Board
|
|
||||||
- function apply
|
|
||||||
- function pieceAt
|
|
||||||
- function updated
|
|
||||||
- function removed
|
|
||||||
- function withMove
|
|
||||||
- _...2 more_
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala`
|
|
||||||
- function hasAnyRights
|
|
||||||
- function hasRights
|
|
||||||
- function revokeColor
|
|
||||||
- function revokeKingSide
|
|
||||||
- function revokeQueenSide
|
|
||||||
- class CastlingRights
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — function opposite, function label
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — class Piece
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — function label
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala`
|
|
||||||
- class Square
|
|
||||||
- function fromAlgebraic
|
|
||||||
- function offset
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`
|
|
||||||
- class Bot
|
|
||||||
- function name
|
|
||||||
- function nextMove
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/dto/ErrorEventDto.scala` — class ErrorEventDto, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/dto/GameFullEventDto.scala` — class GameFullEventDto, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/dto/GameStateEventDto.scala` — class GameStateEventDto, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`
|
|
||||||
- function kingSquare
|
|
||||||
- function withBoard
|
|
||||||
- function withTurn
|
|
||||||
- function withCastlingRights
|
|
||||||
- function withEnPassantSquare
|
|
||||||
- function withHalfMoveClock
|
|
||||||
- _...4 more_
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
|
||||||
- class ApiResponse
|
|
||||||
- function error
|
|
||||||
- function totalPages
|
|
||||||
- `modules/bot/python/nnue.py`
|
|
||||||
- function get_weights_dir: ()
|
|
||||||
- function get_data_dir: ()
|
|
||||||
- function list_checkpoints: ()
|
|
||||||
- function migrate_legacy_data: ()
|
|
||||||
- function show_header: ()
|
|
||||||
- function show_checkpoints_table: ()
|
|
||||||
- _...10 more_
|
|
||||||
- `modules/bot/python/src/dataset.py`
|
|
||||||
- function get_datasets_dir: () -> Path
|
|
||||||
- function next_dataset_version: () -> int
|
|
||||||
- function list_datasets: () -> List[Tuple[int, Dict]]
|
|
||||||
- function load_dataset_metadata: (version) -> Optional[Dict]
|
|
||||||
- function save_dataset_metadata: (version, metadata) -> None
|
|
||||||
- function create_dataset: (version, labeled_jsonl_path, sources, stockfish_depth) -> Path
|
|
||||||
- _...4 more_
|
|
||||||
- `modules/bot/python/src/export.py` — function export_to_nbai: (weights_file, output_file, trained_by, train_loss)
|
|
||||||
- `modules/bot/python/src/generate.py` — function play_random_game_and_collect_positions: (output_file, total_positions, samples_per_game, min_move, max_move, num_workers)
|
|
||||||
- `modules/bot/python/src/label.py` — function normalize_evaluation: (cp_value, method, scale), function label_positions_with_stockfish: (positions_file, output_file, stockfish_path, batch_size, depth, verbose, normalize, num_workers)
|
|
||||||
- `modules/bot/python/src/lichess_importer.py` — function import_lichess_evals: (input_path, output_file, max_positions, min_depth, verbose) -> int
|
|
||||||
- `modules/bot/python/src/tactical_positions_extractor.py`
|
|
||||||
- function download_and_extract_puzzle_db: (url, output_dir)
|
|
||||||
- function extract_puzzle_positions: (puzzle_csv, max_puzzles) -> Set[str]
|
|
||||||
- function load_positions_from_file: (file_path) -> Set[str]
|
|
||||||
- function merge_positions: (tactical, other, output_file)
|
|
||||||
- function extract_tactical_only: (puzzle_csv, output_file, max_puzzles) -> int
|
|
||||||
- function interactive_merge_positions: (puzzle_csv, output_file, max_puzzles)
|
|
||||||
- `modules/bot/python/src/train.py`
|
|
||||||
- function fen_to_features: (fen)
|
|
||||||
- function find_next_version: (base_name)
|
|
||||||
- function save_metadata: (weights_file, metadata)
|
|
||||||
- function train_nnue: (data_file, output_file, epochs, batch_size, lr, checkpoint, stockfish_depth, use_versioning, early_stopping_patience, weight_decay, subsample_ratio, hidden_sizes)
|
|
||||||
- function burst_train: (data_file, output_file, duration_minutes, epochs_per_season, early_stopping_patience, batch_size, lr, initial_checkpoint, stockfish_depth, use_versioning, weight_decay, subsample_ratio, hidden_sizes)
|
|
||||||
- class NNUEDataset
|
|
||||||
- _...1 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/BotController.scala`
|
|
||||||
- class BotController
|
|
||||||
- function getBot
|
|
||||||
- function listBots
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala`
|
|
||||||
- class BotMoveRepetition
|
|
||||||
- function blockedMoves
|
|
||||||
- function repeatedMove
|
|
||||||
- function filterAllowed
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/Config.scala` — class Config
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala`
|
|
||||||
- class Evaluation
|
|
||||||
- class CHECKMATE_SCORE
|
|
||||||
- class DRAW_SCORE
|
|
||||||
- function evaluate
|
|
||||||
- function initAccumulator
|
|
||||||
- function copyAccumulator
|
|
||||||
- _...2 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`
|
|
||||||
- class EvaluationClassic
|
|
||||||
- function evaluate
|
|
||||||
- function countRay
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/EvaluationNNUE.scala` — class EvaluationNNUE, function evaluate
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`
|
|
||||||
- class NNUE
|
|
||||||
- function initAccumulator
|
|
||||||
- function pushAccumulator
|
|
||||||
- function copyAccumulator
|
|
||||||
- function recomputeAccumulator
|
|
||||||
- function validateAccumulator
|
|
||||||
- _...4 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiLoader.scala`
|
|
||||||
- class NbaiLoader
|
|
||||||
- function load
|
|
||||||
- function loadDefault
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiMigrator.scala` — class NbaiMigrator, function migrateFromBin
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiModel.scala`
|
|
||||||
- function toJson
|
|
||||||
- class NbaiMetadata
|
|
||||||
- function fromJson
|
|
||||||
- function str
|
|
||||||
- function num
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiWriter.scala` — class NbaiWriter, function write
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`
|
|
||||||
- function bestMove
|
|
||||||
- function bestMove
|
|
||||||
- function bestMoveWithTime
|
|
||||||
- function bestMoveWithTime
|
|
||||||
- function loop
|
|
||||||
- function loop
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`
|
|
||||||
- class MoveOrdering
|
|
||||||
- class OrderingContext
|
|
||||||
- function addKillerMove
|
|
||||||
- function getKillerMoves
|
|
||||||
- function addHistory
|
|
||||||
- function getHistory
|
|
||||||
- _...3 more_
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/logic/TranspositionTable.scala`
|
|
||||||
- function advance
|
|
||||||
- function probe
|
|
||||||
- function store
|
|
||||||
- function clear
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala` — function probe, function select
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala` — class PolyglotHash, function hash
|
|
||||||
- `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`
|
|
||||||
- class ZobristHash
|
|
||||||
- function hash
|
|
||||||
- function nextHash
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`
|
|
||||||
- class Command
|
|
||||||
- function execute
|
|
||||||
- function undo
|
|
||||||
- function description
|
|
||||||
- class MoveResult
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/CommandInvoker.scala`
|
|
||||||
- class CommandInvoker
|
|
||||||
- function execute
|
|
||||||
- function undo
|
|
||||||
- function redo
|
|
||||||
- function history
|
|
||||||
- function getCurrentIndex
|
|
||||||
- _...3 more_
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — class Parser, function parseMove
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
|
||||||
- class GameEngine
|
|
||||||
- function board
|
|
||||||
- function turn
|
|
||||||
- function context
|
|
||||||
- function canUndo
|
|
||||||
- function canRedo
|
|
||||||
- _...11 more_
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
|
||||||
- function context
|
|
||||||
- class Observer
|
|
||||||
- function onGameEvent
|
|
||||||
- class Observable
|
|
||||||
- function subscribe
|
|
||||||
- function unsubscribe
|
|
||||||
- _...1 more_
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/registry/GameRegistry.scala`
|
|
||||||
- class GameRegistry
|
|
||||||
- function store
|
|
||||||
- function get
|
|
||||||
- function update
|
|
||||||
- function generateId
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.scala`
|
|
||||||
- class GameRegistryImpl
|
|
||||||
- function store
|
|
||||||
- function get
|
|
||||||
- function update
|
|
||||||
- function generateId
|
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala`
|
|
||||||
- function onGameEvent
|
|
||||||
- function createGame
|
|
||||||
- function getGame
|
|
||||||
- function streamGame
|
|
||||||
- function onGameEvent
|
|
||||||
- function resignGame
|
|
||||||
- _...9 more_
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameFileService.scala`
|
|
||||||
- class GameFileService
|
|
||||||
- function saveGameToFile
|
|
||||||
- function loadGameFromFile
|
|
||||||
- class FileSystemGameService
|
|
||||||
- function saveGameToFile
|
|
||||||
- function loadGameFromFile
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
|
||||||
- class FenExporter
|
|
||||||
- function boardToFen
|
|
||||||
- function gameContextToFen
|
|
||||||
- function exportGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`
|
|
||||||
- class FenParser
|
|
||||||
- function parseFen
|
|
||||||
- function importGameContext
|
|
||||||
- function parseBoard
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`
|
|
||||||
- class FenParserCombinators
|
|
||||||
- function parseFen
|
|
||||||
- function parseBoard
|
|
||||||
- function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala`
|
|
||||||
- class FenParserFastParse
|
|
||||||
- function parseFen
|
|
||||||
- function parseBoard
|
|
||||||
- function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
|
||||||
- class PgnExporter
|
|
||||||
- function exportGameContext
|
|
||||||
- function exportGame
|
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala`
|
|
||||||
- class PgnParser
|
|
||||||
- function validatePgn
|
|
||||||
- function importGameContext
|
|
||||||
- function parsePgn
|
|
||||||
- function parseAlgebraicMove
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala`
|
|
||||||
- class RuleSet
|
|
||||||
- function candidateMoves
|
|
||||||
- function legalMoves
|
|
||||||
- function allLegalMoves
|
|
||||||
- function isCheck
|
|
||||||
- function isCheckmate
|
|
||||||
- _...5 more_
|
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala`
|
|
||||||
- class DefaultRules
|
|
||||||
- function positionOf
|
|
||||||
- function loop
|
|
||||||
- function toMoves
|
|
||||||
- function loop
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Middleware
|
|
||||||
|
|
||||||
## custom
|
|
||||||
- generate — `modules/bot/python/src/generate.py`
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>NowChessSystems — codesight report</title>
|
|
||||||
<style>
|
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
|
||||||
:root{--bg:#0a0a0f;--card:#12121a;--border:#1e1e2e;--text:#e0e0e8;--muted:#6b6b80;--accent:#6366f1;--accent2:#22d3ee;--green:#22c55e;--orange:#f59e0b;--red:#ef4444;--pink:#ec4899}
|
|
||||||
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:2rem;max-width:1400px;margin:0 auto;line-height:1.6}
|
|
||||||
h1{font-size:2.5rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:.25rem}
|
|
||||||
.subtitle{color:var(--muted);font-size:1rem;margin-bottom:2rem}
|
|
||||||
.stack-badge{display:inline-block;background:var(--card);border:1px solid var(--border);border-radius:6px;padding:2px 10px;font-size:.85rem;color:var(--accent2);margin:0 4px 4px 0}
|
|
||||||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin:2rem 0}
|
|
||||||
.stat{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;text-align:center}
|
|
||||||
.stat-value{font-size:2rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
|
||||||
.stat-label{color:var(--muted);font-size:.85rem;margin-top:.25rem}
|
|
||||||
.token-hero{background:linear-gradient(135deg,#1a1a2e,#16213e);border:1px solid var(--accent);border-radius:16px;padding:2rem;margin:2rem 0;text-align:center}
|
|
||||||
.token-saved{font-size:3rem;font-weight:900;color:var(--green)}
|
|
||||||
.token-detail{color:var(--muted);font-size:.9rem;margin-top:.5rem}
|
|
||||||
.section{margin:2.5rem 0}
|
|
||||||
.section h2{font-size:1.4rem;font-weight:700;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid var(--border)}
|
|
||||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}
|
|
||||||
.card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:1rem;transition:border-color .2s}
|
|
||||||
.card:hover{border-color:var(--accent)}
|
|
||||||
.card-title{font-weight:700;font-size:1rem;margin-bottom:.5rem}
|
|
||||||
.card-meta{color:var(--muted);font-size:.8rem}
|
|
||||||
.tag{display:inline-block;background:rgba(99,102,241,.15);color:var(--accent);border-radius:4px;padding:1px 6px;font-size:.75rem;margin:1px}
|
|
||||||
.tag-auth{background:rgba(239,68,68,.15);color:var(--red)}
|
|
||||||
.tag-db{background:rgba(34,211,238,.15);color:var(--accent2)}
|
|
||||||
.tag-ai{background:rgba(236,72,153,.15);color:var(--pink)}
|
|
||||||
.tag-payment{background:rgba(245,158,11,.15);color:var(--orange)}
|
|
||||||
.tag-email{background:rgba(34,197,94,.15);color:var(--green)}
|
|
||||||
.tag-queue{background:rgba(168,85,247,.15);color:#a855f7}
|
|
||||||
.tag-cache{background:rgba(245,158,11,.15);color:var(--orange)}
|
|
||||||
.method{font-weight:700;font-size:.8rem;padding:2px 6px;border-radius:4px;margin-right:6px}
|
|
||||||
.method-GET{background:rgba(34,197,94,.2);color:var(--green)}
|
|
||||||
.method-POST{background:rgba(99,102,241,.2);color:var(--accent)}
|
|
||||||
.method-PUT{background:rgba(245,158,11,.2);color:var(--orange)}
|
|
||||||
.method-PATCH{background:rgba(245,158,11,.2);color:var(--orange)}
|
|
||||||
.method-DELETE{background:rgba(239,68,68,.2);color:var(--red)}
|
|
||||||
.method-ALL{background:rgba(107,107,128,.2);color:var(--muted)}
|
|
||||||
.route-path{font-family:'Fira Code',monospace;font-size:.9rem}
|
|
||||||
.route-contract{color:var(--muted);font-size:.8rem;font-style:italic;margin-left:.5rem}
|
|
||||||
.field{display:flex;gap:.5rem;padding:3px 0;font-size:.9rem}
|
|
||||||
.field-name{font-family:monospace;color:var(--accent2)}
|
|
||||||
.field-type{color:var(--muted);font-family:monospace}
|
|
||||||
.field-flags{display:flex;gap:3px}
|
|
||||||
.flag{font-size:.7rem;padding:0 4px;border-radius:3px;background:rgba(99,102,241,.1);color:var(--accent)}
|
|
||||||
.flag-pk{background:rgba(245,158,11,.2);color:var(--orange)}
|
|
||||||
.flag-fk{background:rgba(34,211,238,.2);color:var(--accent2)}
|
|
||||||
.flag-unique{background:rgba(236,72,153,.2);color:var(--pink)}
|
|
||||||
.hot-bar{height:8px;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:4px;margin-top:4px}
|
|
||||||
.component-props{color:var(--muted);font-size:.85rem}
|
|
||||||
.badge-client{background:rgba(34,197,94,.15);color:var(--green);font-size:.75rem;padding:1px 6px;border-radius:4px}
|
|
||||||
.badge-server{background:rgba(99,102,241,.15);color:var(--accent);font-size:.75rem;padding:1px 6px;border-radius:4px}
|
|
||||||
.env-required{color:var(--red);font-weight:600;font-size:.8rem}
|
|
||||||
.env-default{color:var(--green);font-size:.8rem}
|
|
||||||
.footer{text-align:center;color:var(--muted);margin-top:4rem;padding-top:2rem;border-top:1px solid var(--border);font-size:.85rem}
|
|
||||||
.footer a{color:var(--accent);text-decoration:none}
|
|
||||||
table{width:100%;border-collapse:collapse}
|
|
||||||
table td,table th{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border);font-size:.9rem}
|
|
||||||
table th{color:var(--muted);font-size:.8rem;font-weight:600;text-transform:uppercase}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>NowChessSystems</h1>
|
|
||||||
<div class="subtitle">AI Context Map — generated by codesight</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span class="stack-badge">raw-http</span>
|
|
||||||
|
|
||||||
<span class="stack-badge">unknown</span>
|
|
||||||
<span class="stack-badge">scala</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="token-hero">
|
|
||||||
<div class="token-saved">~20,573 tokens saved</div>
|
|
||||||
<div class="token-detail">
|
|
||||||
Output: 5,297 tokens — Exploration cost without codesight: ~25,870 tokens — 149 files scanned
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stats">
|
|
||||||
<div class="stat"><div class="stat-value">0</div><div class="stat-label">Routes</div></div>
|
|
||||||
<div class="stat"><div class="stat-value">0</div><div class="stat-label">Models</div></div>
|
|
||||||
<div class="stat"><div class="stat-value">0</div><div class="stat-label">Components</div></div>
|
|
||||||
<div class="stat"><div class="stat-value">63</div><div class="stat-label">Libraries</div></div>
|
|
||||||
<div class="stat"><div class="stat-value">1</div><div class="stat-label">Env Vars</div></div>
|
|
||||||
<div class="stat"><div class="stat-value">1</div><div class="stat-label">Middleware</div></div>
|
|
||||||
<div class="stat"><div class="stat-value">383</div><div class="stat-label">Import Links</div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2>Dependency Hot Files</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala</div>
|
|
||||||
<div class="card-meta">imported by 64 files</div>
|
|
||||||
<div class="hot-bar" style="width:100%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/move/Move.scala</div>
|
|
||||||
<div class="card-meta">imported by 44 files</div>
|
|
||||||
<div class="hot-bar" style="width:69%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Square.scala</div>
|
|
||||||
<div class="card-meta">imported by 40 files</div>
|
|
||||||
<div class="hot-bar" style="width:63%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Color.scala</div>
|
|
||||||
<div class="card-meta">imported by 35 files</div>
|
|
||||||
<div class="hot-bar" style="width:55%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Board.scala</div>
|
|
||||||
<div class="card-meta">imported by 19 files</div>
|
|
||||||
<div class="hot-bar" style="width:30%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Piece.scala</div>
|
|
||||||
<div class="card-meta">imported by 18 files</div>
|
|
||||||
<div class="hot-bar" style="width:28%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala</div>
|
|
||||||
<div class="card-meta">imported by 17 files</div>
|
|
||||||
<div class="hot-bar" style="width:27%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala</div>
|
|
||||||
<div class="card-meta">imported by 17 files</div>
|
|
||||||
<div class="hot-bar" style="width:27%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala</div>
|
|
||||||
<div class="card-meta">imported by 11 files</div>
|
|
||||||
<div class="hot-bar" style="width:17%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala</div>
|
|
||||||
<div class="card-meta">imported by 10 files</div>
|
|
||||||
<div class="hot-bar" style="width:16%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala</div>
|
|
||||||
<div class="card-meta">imported by 9 files</div>
|
|
||||||
<div class="hot-bar" style="width:14%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala</div>
|
|
||||||
<div class="card-meta">imported by 7 files</div>
|
|
||||||
<div class="hot-bar" style="width:11%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2>Environment Variables</h2>
|
|
||||||
<table>
|
|
||||||
<tr><th>Variable</th><th>Status</th><th>Source</th></tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>STOCKFISH_PATH</code></td>
|
|
||||||
<td><span class="env-required">required</span></td>
|
|
||||||
<td class="card-meta">modules/bot/python/nnue.py</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2>Middleware</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title">generate <span class="tag tag-custom">custom</span></div>
|
|
||||||
<div class="card-meta">modules/bot/python/src/generate.py</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
Generated by <a href="https://github.com/Houseofmvps/codesight">codesight</a> — see your codebase clearly
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# NowChessSystems — Wiki
|
|
||||||
|
|
||||||
_Generated 2026-04-12 — re-run `npx codesight --wiki` if the codebase has changed._
|
|
||||||
|
|
||||||
Structural map compiled from source code via AST. No LLM — deterministic, 200ms.
|
|
||||||
|
|
||||||
> **How to use safely:** These articles tell you WHERE things live and WHAT exists. They do not show full implementation logic. Always read the actual source files before implementing new features or making changes. Never infer how a function works from the wiki alone.
|
|
||||||
|
|
||||||
## Articles
|
|
||||||
|
|
||||||
- [Overview](./overview.md)
|
|
||||||
|
|
||||||
## Quick Stats
|
|
||||||
|
|
||||||
- Routes: **0**
|
|
||||||
- Models: **0**
|
|
||||||
- Components: **0**
|
|
||||||
- Env vars: **0** required, **0** with defaults
|
|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
- **New session:** read `index.md` (this file) for orientation — WHERE things are
|
|
||||||
- **Architecture question:** read `overview.md` (~500 tokens)
|
|
||||||
- **Domain question:** read the relevant article, then **read those source files**
|
|
||||||
- **Database question:** read `database.md`, then read the actual schema files
|
|
||||||
- **Before implementing anything:** read the source files listed in the article
|
|
||||||
- **Full source context:** read `.codesight/CODESIGHT.md`
|
|
||||||
|
|
||||||
## What the Wiki Does Not Cover
|
|
||||||
|
|
||||||
These exist in your codebase but are **not** reflected in wiki articles:
|
|
||||||
- Routes registered dynamically at runtime (loops, plugin factories, `app.use(dynamicRouter)`)
|
|
||||||
- Internal routes from npm packages (e.g. Better Auth's built-in `/api/auth/*` endpoints)
|
|
||||||
- WebSocket and SSE handlers
|
|
||||||
- Raw SQL tables not declared through an ORM
|
|
||||||
- Computed or virtual fields absent from schema declarations
|
|
||||||
- TypeScript types that are not actual database columns
|
|
||||||
- Routes marked `[inferred]` were detected via regex and may have lower precision
|
|
||||||
- gRPC, tRPC, and GraphQL resolvers may be partially captured
|
|
||||||
|
|
||||||
When in doubt, search the source. The wiki is a starting point, not a complete inventory.
|
|
||||||
|
|
||||||
---
|
|
||||||
_Last compiled: 2026-04-12 · 2 articles · [codesight](https://github.com/Houseofmvps/codesight)_
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Wiki Log
|
|
||||||
|
|
||||||
History of `npx codesight --wiki` runs. Capped at 20 entries.
|
|
||||||
|
|
||||||
## [2026-04-12 14:34:19] scan | 0 routes, 0 models, 0 components → 2 articles
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# NowChessSystems — Overview
|
|
||||||
|
|
||||||
> **Navigation aid.** This article shows WHERE things live (routes, models, files). Read actual source files before implementing new features or making changes.
|
|
||||||
|
|
||||||
**NowChessSystems** is a scala project built with raw-http.
|
|
||||||
|
|
||||||
## High-Impact Files
|
|
||||||
|
|
||||||
Changes to these files have the widest blast radius across the codebase:
|
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
|
|
||||||
|
|
||||||
---
|
|
||||||
_Back to [index.md](./index.md) · Generated 2026-04-12_
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Normalize text files in the repo
|
|
||||||
* text=auto eol=lf
|
|
||||||
|
|
||||||
# Keep Windows command scripts in CRLF
|
|
||||||
*.bat text eol=crlf
|
|
||||||
*.cmd text eol=crlf
|
|
||||||
|
|
||||||
# Keep Unix shell scripts in LF
|
|
||||||
*.sh text eol=lf
|
|
||||||
|
|
||||||
# Binary assets (no EOL normalization / textual diff)
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.webp binary
|
|
||||||
*.bmp binary
|
|
||||||
*.ico binary
|
|
||||||
|
|
||||||
# ML / model / numeric artifacts
|
|
||||||
*.bin binary
|
|
||||||
*.pt binary
|
|
||||||
*.pth binary
|
|
||||||
*.onnx binary
|
|
||||||
*.h5 binary
|
|
||||||
*.hdf5 binary
|
|
||||||
*.pb binary
|
|
||||||
*.tflite binary
|
|
||||||
*.npy binary
|
|
||||||
*.npz binary
|
|
||||||
*.safetensors binary
|
|
||||||
|
|
||||||
# Firmware / hex-like artifacts
|
|
||||||
*.hex binary
|
|
||||||
|
|
||||||
# Packaged binaries
|
|
||||||
*.jar binary
|
|
||||||
*.zip binary
|
|
||||||
*.7z binary
|
|
||||||
*.gz binary
|
|
||||||
|
|
||||||
@@ -38,8 +38,6 @@ bin/
|
|||||||
|
|
||||||
### VS Code ###
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
graphify-out/
|
|
||||||
.graphify_*.json
|
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -8,5 +8,3 @@
|
|||||||
/dataSources.local.xml
|
/dataSources.local.xml
|
||||||
# Editor-based HTTP Client requests
|
# Editor-based HTTP Client requests
|
||||||
/httpRequests/
|
/httpRequests/
|
||||||
|
|
||||||
sonarlint.xml
|
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<AndroidXmlCodeStyleSettings>
|
|
||||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
|
||||||
</AndroidXmlCodeStyleSettings>
|
|
||||||
<JetCodeStyleSettings>
|
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
|
||||||
</JetCodeStyleSettings>
|
|
||||||
<ScalaCodeStyleSettings>
|
|
||||||
<option name="FORMATTER" value="1" />
|
|
||||||
</ScalaCodeStyleSettings>
|
|
||||||
<XML>
|
|
||||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
|
||||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
|
||||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
|
||||||
</XML>
|
|
||||||
<codeStyleSettings language="XML">
|
|
||||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
|
||||||
<indentOptions>
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
|
||||||
</indentOptions>
|
|
||||||
<arrangement>
|
|
||||||
<rules>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>xmlns:android</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>xmlns:.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:id</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:name</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>name</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>style</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
</rules>
|
|
||||||
</arrangement>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="kotlin">
|
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
|
||||||
</codeStyleSettings>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<state>
|
<state>
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
</state>
|
</state>
|
||||||
</component>
|
</component>
|
||||||
@@ -11,10 +11,8 @@
|
|||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/modules" />
|
<option value="$PROJECT_DIR$/modules" />
|
||||||
<option value="$PROJECT_DIR$/modules/api" />
|
<option value="$PROJECT_DIR$/modules/api" />
|
||||||
<option value="$PROJECT_DIR$/modules/bot" />
|
|
||||||
<option value="$PROJECT_DIR$/modules/core" />
|
<option value="$PROJECT_DIR$/modules/core" />
|
||||||
<option value="$PROJECT_DIR$/modules/io" />
|
<option value="$PROJECT_DIR$/modules/ui" />
|
||||||
<option value="$PROJECT_DIR$/modules/rule" />
|
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="NowChessSystems.modules.core.main" type="QuarkusRunConfigurationType" factoryName="Quarkus" nameIsGenerated="true">
|
|
||||||
<module name="NowChessSystems.modules.core.main" />
|
|
||||||
<QsGradleRunConfiguration>
|
|
||||||
<ExternalSystemSettings>
|
|
||||||
<option name="executionName" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$/modules/core" />
|
|
||||||
<option name="externalSystemIdString" value="GRADLE" />
|
|
||||||
<option name="scriptParameters" />
|
|
||||||
<option name="taskDescriptions">
|
|
||||||
<list />
|
|
||||||
</option>
|
|
||||||
<option name="taskNames">
|
|
||||||
<list>
|
|
||||||
<option value="quarkusDev" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
<option name="vmOptions" />
|
|
||||||
</ExternalSystemSettings>
|
|
||||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
|
||||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
|
||||||
<DebugAllEnabled>false</DebugAllEnabled>
|
|
||||||
<RunAsTest>false</RunAsTest>
|
|
||||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
|
||||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
|
||||||
<profile>dev</profile>
|
|
||||||
</QsGradleRunConfiguration>
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<option name="deprecationWarnings" value="true" />
|
<option name="deprecationWarnings" value="true" />
|
||||||
<option name="uncheckedWarnings" value="true" />
|
<option name="uncheckedWarnings" value="true" />
|
||||||
</profile>
|
</profile>
|
||||||
<profile name="Gradle 2" modules="NowChessSystems.modules.bot.main,NowChessSystems.modules.bot.scoverage,NowChessSystems.modules.bot.test,NowChessSystems.modules.core.integrationTest,NowChessSystems.modules.core.main,NowChessSystems.modules.core.native-test,NowChessSystems.modules.core.quarkus-generated-sources,NowChessSystems.modules.core.quarkus-test-generated-sources,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.main,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
|
<profile name="Gradle 2" modules="NowChessSystems.modules.core.main,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
|
||||||
<option name="deprecationWarnings" value="true" />
|
<option name="deprecationWarnings" value="true" />
|
||||||
<option name="uncheckedWarnings" value="true" />
|
<option name="uncheckedWarnings" value="true" />
|
||||||
<parameters>
|
<parameters>
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
rules = [
|
|
||||||
DisableSyntax,
|
|
||||||
LeakingImplicitClassVal,
|
|
||||||
NoValInForComprehension,
|
|
||||||
ProcedureSyntax,
|
|
||||||
]
|
|
||||||
|
|
||||||
DisableSyntax.noVars = true
|
|
||||||
DisableSyntax.noThrows = true
|
|
||||||
DisableSyntax.noNulls = true
|
|
||||||
DisableSyntax.noReturns = true
|
|
||||||
DisableSyntax.noAsInstanceOf = true
|
|
||||||
DisableSyntax.noIsInstanceOf = true
|
|
||||||
DisableSyntax.noXml = true
|
|
||||||
DisableSyntax.noFinalize = true
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
version = 3.8.1
|
|
||||||
runner.dialect = scala3
|
|
||||||
maxColumn = 120
|
|
||||||
indent.main = 2
|
|
||||||
align.preset = more
|
|
||||||
trailingCommas = always
|
|
||||||
rewrite.rules = [SortImports, RedundantBraces]
|
|
||||||
rewrite.scala3.convertToNewSyntax = true
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Project Context
|
|
||||||
|
|
||||||
This is a scala project using raw-http.
|
|
||||||
|
|
||||||
Middleware includes: custom.
|
|
||||||
|
|
||||||
High-impact files (most imported, changes here affect many other files):
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala (imported by 50 files)
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/board/Square.scala (imported by 33 files)
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/board/Color.scala (imported by 30 files)
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/move/Move.scala (imported by 29 files)
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/board/Board.scala (imported by 19 files)
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala (imported by 18 files)
|
|
||||||
- modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala (imported by 17 files)
|
|
||||||
- modules/api/src/main/scala/de/nowchess/api/board/Piece.scala (imported by 15 files)
|
|
||||||
|
|
||||||
Required environment variables (no defaults):
|
|
||||||
- STOCKFISH_PATH (modules/bot/python/nnue.py)
|
|
||||||
|
|
||||||
Read .codesight/wiki/index.md for orientation (WHERE things live). Then read actual source files before implementing. Wiki articles are navigation aids, not implementation guides.
|
|
||||||
Read .codesight/CODESIGHT.md for the complete AI context map including all routes, schema, components, libraries, config, middleware, and dependency graph.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
YOU CAN:
|
|
||||||
- Edit and use the asset in any commercial or non commercial project
|
|
||||||
- Use the asset in any commercial or non commercial project
|
|
||||||
|
|
||||||
YOU CAN'T:
|
|
||||||
- Resell or distribute the asset to others
|
|
||||||
- Edit and resell the asset to others - - Credits required using This link: https://fatman200.itch.io/
|
|
||||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 907 B |
|
Before Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 818 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 161 B |
|
Before Width: | Height: | Size: 188 B |
|
Before Width: | Height: | Size: 188 B |
|
Before Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 243 B |
|
Before Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 238 B |
|
Before Width: | Height: | Size: 227 B |
|
Before Width: | Height: | Size: 267 B |
|
Before Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 218 B |
|
Before Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 286 B |
|
Before Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 266 B |
|
Before Width: | Height: | Size: 297 B |
|
Before Width: | Height: | Size: 258 B |
|
Before Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 313 B |
|
Before Width: | Height: | Size: 251 B |
|
Before Width: | Height: | Size: 275 B |
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 281 B |
|
Before Width: | Height: | Size: 280 B |
@@ -1,96 +1,58 @@
|
|||||||
# Now-Chess
|
# CLAUDE.md — NowChessSystems
|
||||||
|
|
||||||
Scala 3.5.1 · Gradle 9
|
## Stack
|
||||||
|
Scala 3.5.x · Quarkus + quarkus-scala3 · Hibernate/Jakarta · Lanterna TUI · K8s + ArgoCD + Kargo · Frontend TBD (Vite/React/Angular/Vue)
|
||||||
|
|
||||||
|
### Memory
|
||||||
|
|
||||||
|
Your memory is saved under .claude/memory/MEMORY.md.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
```
|
||||||
|
build.gradle.kts / settings.gradle.kts # root; include(":modules:<svc>") per service
|
||||||
|
modules/<svc>/build.gradle.kts + src/
|
||||||
|
docs/adr/ docs/api/ docs/unresolved.md
|
||||||
|
```
|
||||||
|
Versions in root `extra["VERSIONS"]`; modules read via `rootProject.extra["VERSIONS"] as Map<String,String>`.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
./gradlew :modules:<svc>:build|test
|
||||||
|
./gradlew :modules:<svc>:test --tests "de.nowchess.<svc>.<Class>"
|
||||||
```
|
```
|
||||||
./clean # Clear build dirs — only when necessary
|
|
||||||
./compile # Compile all modules — always run
|
## Workflow
|
||||||
./test # Run all tests
|
1. **Plan** — restate requirement, list files, flag risks. Proceed unless genuine ambiguity.
|
||||||
./coverage # Check coverage
|
2. **Tests first** — cover only new behaviour.
|
||||||
|
3. **Implement** — no scope creep.
|
||||||
|
4. **Verify** — check each requirement; confirm green build.
|
||||||
|
|
||||||
|
## Scala/Quarkus Rules
|
||||||
|
- `given`/`using` only (no `implicit`); `Option`/`Either`/`Try` (no `null`/`.get`)
|
||||||
|
- `jakarta.*` only; reactive I/O (`Uni`/`Multi`), no blocking on event loop
|
||||||
|
- Always exclude `org.scala-lang:scala-library` from Quarkus BOM
|
||||||
|
- Unit tests: `extends AnyFunSuite with Matchers` — no `@Test`, no `: Unit`
|
||||||
|
- Integration tests: `@QuarkusTest` + JUnit 5 — `@Test` methods need explicit `: Unit`
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
Line = 100% · Branch = 100% · Method = 100% · Regression tests · document exceptions
|
||||||
|
Check: `jacoco-reporter/scoverage_coverage_gaps.py modules/{svc}/build/reports/scoverageTest/scoverage.xml`
|
||||||
|
⚠️ Use `scoverageTest/`, NOT `scoverage/`.
|
||||||
|
|
||||||
|
## Bug Fixing
|
||||||
|
Fix failures immediately without asking. After 3 failed attempts → log in `docs/unresolved.md` + surface summary.
|
||||||
|
|
||||||
|
## Agents (new service)
|
||||||
|
Sequential: architect → scala-implementer → test-writer → gradle-builder → code-reviewer (review only, no self-fix)
|
||||||
|
Parallel: only when services are fully independent (no shared contracts/state).
|
||||||
|
|
||||||
|
## Unresolved (`docs/unresolved.md`)
|
||||||
|
Append only, never delete:
|
||||||
|
```
|
||||||
|
## [YYYY-MM-DD] <title>
|
||||||
|
**Requirement/Bug:** **Root Cause:** **Attempted Fixes:** **Next Step:**
|
||||||
```
|
```
|
||||||
Use consistently.
|
|
||||||
|
|
||||||
## Modules
|
## Done Checklist
|
||||||
|
- [ ] Plan written · files created/modified · tests green · requirements verified · unresolved logged
|
||||||
| Module | Role | Depends on |
|
|
||||||
|--------|------|-----------|
|
|
||||||
| `api` | Model / shared types | (none) |
|
|
||||||
| `core` | Primary business logic | api, rule |
|
|
||||||
| `rule` | Game rules | api |
|
|
||||||
| `bot` | Bots and AI | api,rule,io |
|
|
||||||
| `io` | Export formats | api, core |
|
|
||||||
| `ui` | Entrypoint & UI | core, io |
|
|
||||||
|
|
||||||
## Style
|
|
||||||
|
|
||||||
- Immutable data, pure functions.
|
|
||||||
- Functions under 30 lines. Need "and"? Split it.
|
|
||||||
- Cyclomatic complexity under 15.
|
|
||||||
- No comments. Names carry intent. Comment non-obvious algorithms only.
|
|
||||||
- Scan duplicated logic. Extract.
|
|
||||||
- Follow default Sonar style for Scala.
|
|
||||||
- `Option`/`Either` for fallible ops. Skip exceptions for control flow.
|
|
||||||
- Naming: types PascalCase, functions/values camelCase.
|
|
||||||
|
|
||||||
## Code Quality
|
|
||||||
|
|
||||||
- **Coverage:** 100% condition coverage required in `api`, `core`, `rule`, `io` (mandatory); `ui` exempt.
|
|
||||||
|
|
||||||
### Linters
|
|
||||||
|
|
||||||
- **scalafmt** — Enforces formatting. Check: `./gradlew spotlessScalaCheck`. Refactor: `./gradlew spotlessScalaApply`.
|
|
||||||
- **scalafix** — Enforces style, detects unused imports/code. Run: `./gradlew scalafix`.
|
|
||||||
|
|
||||||
## Architecture Decisions
|
|
||||||
|
|
||||||
- **Immutable state as primary model:** GameContext (api) holds board, history, player state—immutable throughout. Each move → new GameContext. Enables undo/redo without side effects.
|
|
||||||
- **Observer pattern for UI decoupling:** GameEngine publishes move/state events; CommandInvoker queues moves; UI listens (no polling). GameEngine never imports UI.
|
|
||||||
- **RuleSet trait encapsulates rules:** Move generation, check, castling, en passant all in RuleSet impl. GameEngine calls rules as black box; rules don't know rest of core.
|
|
||||||
- **Polyglot hash must follow spec index layout:** Piece keys use interleaved mapping `(pieceType * 2 + colorBit)` (black=0, white=1). Castling keys: `768..771`. En-passant file keys: `772..779`, XORed only if side-to-move has capturable en passant. Side-to-move key: `780` (white).
|
|
||||||
- **Alpha-beta uses sequential PV search by default:** Parallel split disabled (fixed-window futures removed pruning effectiveness). Sequential PV default. Correctness + pruning quality > speculative parallelism.
|
|
||||||
- **Search hash is updated incrementally per move:** Bot search updates Zobrist keys from parent hash with move deltas, not recomputing piece scans per node.
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- **Tests are the spec.** Don't modify to pass. Fix requirements/code. Update only if requirements change.
|
|
||||||
- Never read build folders. Ask permission if needed.
|
|
||||||
- Keep file current with decisions + conventions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Instructions for Claude Code
|
|
||||||
|
|
||||||
### Two-Step Rule (mandatory)
|
|
||||||
**Step 1 — Orient:** Use wiki articles to find WHERE things live.
|
|
||||||
**Step 2 — Verify:** Read source files from wiki BEFORE coding.
|
|
||||||
|
|
||||||
Wiki = structural summaries (routes, models, file locations). No function logic, middleware internals, runtime behavior. Don't code from wiki alone—read sources.
|
|
||||||
|
|
||||||
Read in order at session start:
|
|
||||||
1. `.codesight/wiki/index.md` — orientation map (~200 tokens)
|
|
||||||
2. `.codesight/wiki/overview.md` — architecture overview (~500 tokens)
|
|
||||||
3. Domain article (e.g. `.codesight/wiki/auth.md`) → check "Source Files" section → read those files
|
|
||||||
4. `.codesight/CODESIGHT.md` — full context map for deep exploration
|
|
||||||
|
|
||||||
`[inferred]` routes = regex-detected. Verify sources. ⚠ in wiki? Re-run `codesight --wiki`.
|
|
||||||
|
|
||||||
Or use the codesight MCP server for on-demand queries:
|
|
||||||
- `codesight_get_wiki_article` — read a specific wiki article by name
|
|
||||||
- `codesight_get_wiki_index` — get the wiki index
|
|
||||||
- `codesight_get_summary` — quick project overview
|
|
||||||
- `codesight_get_routes --prefix /api/users` — filtered routes
|
|
||||||
- `codesight_get_blast_radius --file src/lib/db.ts` — impact analysis before changes
|
|
||||||
- `codesight_get_schema --model users` — specific model details
|
|
||||||
|
|
||||||
Consult codesight context first. Saves ~16.893 tokens/conversation.
|
|
||||||
|
|
||||||
## graphify
|
|
||||||
|
|
||||||
graphify knowledge graph at graphify-out/.
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- Architecture/codebase questions? Read graphify-out/GRAPH_REPORT.md (god nodes, communities).
|
|
||||||
- graphify-out/wiki/index.md exists? Use it (not raw files).
|
|
||||||
- Code modified? Run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to sync graph.
|
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
# Now-Chess
|
|
||||||
|
|
||||||
Scala 3.5.1 · Gradle 9
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
```
|
|
||||||
./clean # Clear build dirs — only when necessary
|
|
||||||
./compile # Compile all modules — always run
|
|
||||||
./test # Run all tests
|
|
||||||
./coverage # Check coverage
|
|
||||||
```
|
|
||||||
Try to stick to these commands for consistency.
|
|
||||||
|
|
||||||
## Modules
|
|
||||||
|
|
||||||
| Module | Role | Depends on |
|
|
||||||
|--------|------|-----------|
|
|
||||||
| `api` | Model / shared types | (none) |
|
|
||||||
| `core` | Primary business logic | api, rule |
|
|
||||||
| `rule` | Game rules | api |
|
|
||||||
| `bot` | Bots and AI | api,rule,io |
|
|
||||||
| `io` | Export formats | api, core |
|
|
||||||
| `ui` | Entrypoint & UI | core, io |
|
|
||||||
|
|
||||||
## Style
|
|
||||||
|
|
||||||
- Use immutable data and pure functions.
|
|
||||||
- Keep functions under 30 lines. If you need "and" to describe it, split it.
|
|
||||||
- Keep cyclomatic complexity under 15.
|
|
||||||
- Avoid comments. Let names carry intent; comment only non-obvious algorithms.
|
|
||||||
- Scan for duplicated logic before finishing. Extract it.
|
|
||||||
- Follow default Sonar style for Scala.
|
|
||||||
- Use `Option` or `Either` for fallible operations; avoid exceptions for control flow.
|
|
||||||
- Naming: types are PascalCase, functions/values are camelCase.
|
|
||||||
|
|
||||||
## Code Quality
|
|
||||||
|
|
||||||
- **Coverage:** 100% condition coverage required in `api`, `core`, `rule`, `io` (mandatory); `ui` exempt.
|
|
||||||
|
|
||||||
### Linters
|
|
||||||
|
|
||||||
- **scalafmt** — enforces formatting; run `./gradlew spotlessScalaCheck` to check and `./gradlew spotlessScalaApply` to refactor.
|
|
||||||
- **scalafix** — enforces style and detects unused imports/code; run `./gradlew scalafix` to apply rules.
|
|
||||||
|
|
||||||
## Architecture Decisions
|
|
||||||
|
|
||||||
- **Immutable state as primary model:** GameContext (api) holds board, history, player state — immutable, passed through the system. Each move creates a new GameContext, enabling undo/redo without side effects.
|
|
||||||
- **Observer pattern for UI decoupling:** GameEngine publishes move/state events; CommandInvoker queues moves; UI listens to events, not polling. GameEngine never imports UI code.
|
|
||||||
- **RuleSet trait encapsulates rules:** Move generation, check, castling, en passant all in RuleSet impl. GameEngine calls rules as a black box; rules don't know about the rest of core.
|
|
||||||
- **Polyglot hash must follow spec index layout:** piece keys use interleaved mapping `(pieceType * 2 + colorBit)` with black=0/white=1, castling keys are `768..771`, en-passant file keys are `772..779` and are XORed only if side-to-move has a pawn that can capture en passant, side-to-move key is `780` for white.
|
|
||||||
- **Alpha-beta uses sequential PV search by default:** parallel split was disabled because fixed-window futures removed pruning effectiveness; correctness and pruning quality take priority over speculative parallelism.
|
|
||||||
- **Search hash is updated incrementally per move:** bot search now updates Zobrist keys from parent hash with move deltas instead of recomputing piece scans at every node.
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- **Tests are the spec.** Never modify tests to pass; modify requirements or code. Update tests only if requirements change.
|
|
||||||
- Never read build folders. Ask permission if needed.
|
|
||||||
- Keep this file up to date with any important decisions or conventions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Instructions for Claude Code
|
|
||||||
|
|
||||||
### Two-Step Rule (mandatory)
|
|
||||||
**Step 1 — Orient:** Use wiki articles to find WHERE things live.
|
|
||||||
**Step 2 — Verify:** Read the actual source files listed in the wiki article BEFORE writing any code.
|
|
||||||
|
|
||||||
Wiki articles are structural summaries extracted by AST. They show routes, models, and file locations.
|
|
||||||
They do NOT show full function logic, middleware internals, or dynamic runtime behavior.
|
|
||||||
**Never write or modify code based solely on wiki content — always read source files first.**
|
|
||||||
|
|
||||||
Read in order at session start:
|
|
||||||
1. `.codesight/wiki/index.md` — orientation map (~200 tokens)
|
|
||||||
2. `.codesight/wiki/overview.md` — architecture overview (~500 tokens)
|
|
||||||
3. Domain article (e.g. `.codesight/wiki/auth.md`) → check "Source Files" section → read those files
|
|
||||||
4. `.codesight/CODESIGHT.md` — full context map for deep exploration
|
|
||||||
|
|
||||||
Routes marked `[inferred]` in wiki articles were detected via regex — verify against source before trusting.
|
|
||||||
If any source file shows ⚠ in the wiki, re-run `codesight --wiki` before proceeding.
|
|
||||||
|
|
||||||
Or use the codesight MCP server for on-demand queries:
|
|
||||||
- `codesight_get_wiki_article` — read a specific wiki article by name
|
|
||||||
- `codesight_get_wiki_index` — get the wiki index
|
|
||||||
- `codesight_get_summary` — quick project overview
|
|
||||||
- `codesight_get_routes --prefix /api/users` — filtered routes
|
|
||||||
- `codesight_get_blast_radius --file src/lib/db.ts` — impact analysis before changes
|
|
||||||
- `codesight_get_schema --model users` — specific model details
|
|
||||||
|
|
||||||
Only open specific files after consulting codesight context. This saves ~16.893 tokens per conversation.
|
|
||||||
|
|
||||||
## graphify
|
|
||||||
|
|
||||||
This project has a graphify knowledge graph at graphify-out/.
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
|
|
||||||
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
|
|
||||||
- After modifying code files in this session, run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1",
|
|
||||||
"name": "NowChess API",
|
|
||||||
"type": "collection",
|
|
||||||
"ignore": []
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Offer Draw
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/offer
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Accept Draw
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/accept
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Decline Draw
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/decline
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Claim Draw
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/claim
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: draw
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
vars {
|
|
||||||
baseUrl: http://localhost:8080
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Export FEN
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: GET
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/export/fen
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Export PGN
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: GET
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/export/pgn
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: export
|
|
||||||
seq: 6
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Create Game
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
headers {
|
|
||||||
Content-Type: application/json
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"white": {"id": "p1", "displayName": "Alice"},
|
|
||||||
"black": {"id": "p2", "displayName": "Bob"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get Game
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: GET
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Stream Game
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/stream
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
headers {
|
|
||||||
Accept: application/x-ndjson
|
|
||||||
}
|
|
||||||
|
|
||||||
vars:pre-request {
|
|
||||||
gameId: tjOgyEcS
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Resign
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/resign
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: game
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Import FEN
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/import/fen
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
headers {
|
|
||||||
Content-Type: application/json
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
|
|
||||||
"white": {"id": "p1", "displayName": "Alice"},
|
|
||||||
"black": {"id": "p2", "displayName": "Bob"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Import PGN
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/import/pgn
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
headers {
|
|
||||||
Content-Type: application/json
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"pgn": "1. e4 e5 2. Nf3 Nc6 *"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: import
|
|
||||||
seq: 5
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Make Move
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/move/b1c3
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
vars:pre-request {
|
|
||||||
gameId: tjOgyEcS
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get Legal Moves
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/moves
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
params:query {
|
|
||||||
square: e2
|
|
||||||
}
|
|
||||||
|
|
||||||
vars:pre-request {
|
|
||||||
gameId: tjOgyEcS
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Undo Move
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/undo
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Redo Move
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
method: POST
|
|
||||||
url: {{baseUrl}}/api/board/game/{{gameId}}/redo
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: move
|
|
||||||
}
|
|
||||||
@@ -1,56 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("org.sonarqube") version "7.2.3.7755"
|
id("org.sonarqube") version "7.2.3.7755"
|
||||||
id("org.scoverage") version "8.1" apply false
|
|
||||||
id("com.diffplug.spotless") version "8.4.0" apply false
|
|
||||||
id("io.github.cosmicsilence.scalafix") version "0.2.6" apply false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "de.nowchess"
|
group = "de.nowchess"
|
||||||
version = "1.0-SNAPSHOT"
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
// Canonical coverage exclusions — glob patterns consumed by Sonar directly;
|
|
||||||
// converted to scoverage regexes via globToScoverageRegex for instrumentation-time exclusion.
|
|
||||||
val coverageExclusions = listOf(
|
|
||||||
// UI renders JavaFX components; headless test environments cannot exercise rendering paths
|
|
||||||
"modules/ui/**",
|
|
||||||
// FastParse macro-generated combinators produce synthetic branches that scoverage marks as uncovered
|
|
||||||
"modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse*",
|
|
||||||
// NNUE inference pipeline — coverage requires a trained model file not present in CI
|
|
||||||
"**/bot/**/NNUE.scala",
|
|
||||||
"**/bot/**/NNUEBot.scala",
|
|
||||||
"**/bot/**/EvaluationNNUE.scala",
|
|
||||||
// NBAI binary format loader/writer — error paths require crafted corrupt files; migrator is a one-shot tool
|
|
||||||
"**/bot/**/NbaiLoader.scala",
|
|
||||||
"**/bot/**/NbaiModel.scala",
|
|
||||||
"**/bot/**/NbaiMigrator.scala",
|
|
||||||
"**/bot/**/NbaiWriter.scala",
|
|
||||||
// PolyglotBook — binary I/O and dead-code guards (bit-masked fields can never exceed valid range)
|
|
||||||
"**/bot/**/PolyglotBook.scala",
|
|
||||||
"**/bot/**/MoveOrdering.scala",
|
|
||||||
"**/bot/**/AlphaBetaSearch.scala",
|
|
||||||
// DTO case class synthetic methods (Scala compiler-generated apply/$default params)
|
|
||||||
"**/api/src/main/scala/de/nowchess/api/dto/**Dto.scala",
|
|
||||||
// Core infrastructure: exception classes, config, registry implementation, game entry
|
|
||||||
"**/core/src/main/scala/de/nowchess/chess/exception/**",
|
|
||||||
"**/core/src/main/scala/de/nowchess/chess/config/**",
|
|
||||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala",
|
|
||||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.scala",
|
|
||||||
// GameResource — REST integration layer with @Inject var fields; mocking dependencies for unit tests is infeasible with Quarkus DI; integration tests would require @QuarkusTest which Scoverage doesn't instrument
|
|
||||||
"**/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
|
|
||||||
// Order matters: protect ** before converting lone *, escape dots last.
|
|
||||||
fun globToScoverageRegex(glob: String): String =
|
|
||||||
glob
|
|
||||||
.replace("**", "^@")
|
|
||||||
.replace("*", "[^/]*")
|
|
||||||
.replace(".", "\\.")
|
|
||||||
.replace("^@", ".*")
|
|
||||||
.let { ".*$it" }
|
|
||||||
|
|
||||||
extra["SCOVERAGE_EXCLUDED"] = coverageExclusions.map(::globToScoverageRegex)
|
|
||||||
|
|
||||||
sonar {
|
sonar {
|
||||||
properties {
|
properties {
|
||||||
property("sonar.projectKey", "Now-Chess-Systems")
|
property("sonar.projectKey", "Now-Chess-Systems")
|
||||||
@@ -65,7 +19,6 @@ sonar {
|
|||||||
}.joinToString(",")
|
}.joinToString(",")
|
||||||
|
|
||||||
property("sonar.scala.coverage.reportPaths", scoverageReports)
|
property("sonar.scala.coverage.reportPaths", scoverageReports)
|
||||||
property("sonar.coverage.exclusions", coverageExclusions.joinToString(","))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,39 +28,7 @@ val versions = mapOf(
|
|||||||
"SCALA_LIBRARY" to "2.13.18",
|
"SCALA_LIBRARY" to "2.13.18",
|
||||||
"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"
|
||||||
"SCALAFX" to "21.0.0-R32",
|
|
||||||
"JAVAFX" to "21.0.1",
|
|
||||||
"JUNIT_BOM" to "5.13.4",
|
|
||||||
"ONNXRUNTIME" to "1.19.2",
|
|
||||||
"SCALA_PARSER_COMBINATORS" to "2.4.0",
|
|
||||||
"FASTPARSE" to "3.0.2",
|
|
||||||
"JACKSON" to "2.17.2",
|
|
||||||
"JACKSON_SCALA" to "2.17.2"
|
|
||||||
)
|
)
|
||||||
extra["VERSIONS"] = versions
|
extra["VERSIONS"] = versions
|
||||||
|
|
||||||
subprojects {
|
|
||||||
apply(plugin = "com.diffplug.spotless")
|
|
||||||
|
|
||||||
pluginManager.withPlugin("scala") {
|
|
||||||
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
|
|
||||||
scala {
|
|
||||||
scalafmt().configFile(rootProject.file(".scalafmt.conf"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(plugin = "io.github.cosmicsilence.scalafix")
|
|
||||||
configure<io.github.cosmicsilence.scalafix.ScalafixExtension> {
|
|
||||||
configFile.set(rootProject.file(".scalafix.conf"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable SemanticDB config for the scoverage source set — it sets -sourceroot to
|
|
||||||
// the root project dir, which conflicts with scoverage's own -sourceroot and causes
|
|
||||||
// reportTestScoverage to fail with "No source root found".
|
|
||||||
tasks.matching { it.name in setOf("configSemanticDBScoverage", "checkScalafixScoverage", "checkScalafixTest") }.configureEach {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
#! /usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
if [ "$#" -eq 0 ]; then
|
|
||||||
PYTHONUTF8=1 python3 jacoco-reporter/scoverage_coverage_gaps.py
|
|
||||||
else
|
|
||||||
PYTHONUTF8=1 python3 jacoco-reporter/scoverage_coverage_gaps.py "modules/$1/build/reports/scoverageTest/scoverage.xml"
|
|
||||||
fi
|
|
||||||
@@ -1,771 +0,0 @@
|
|||||||
openapi: 3.0.3
|
|
||||||
info:
|
|
||||||
title: NowChess Board API
|
|
||||||
description: |
|
|
||||||
REST API for the NowChess application. Designed to feel familiar to users
|
|
||||||
of the [lichess API](https://lichess.org/api).
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
Most endpoints require a Bearer token:
|
|
||||||
```
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
Authentication is reserved for future implementation — endpoints are currently
|
|
||||||
open unless noted otherwise.
|
|
||||||
|
|
||||||
## Move notation
|
|
||||||
Moves are expressed in **UCI notation**: `{from}{to}[promotion]`
|
|
||||||
- Normal move: `e2e4`
|
|
||||||
- Capture: `d5e6`
|
|
||||||
- Promotion: `e7e8q` (q=queen, r=rook, b=bishop, n=knight)
|
|
||||||
- Castling: `e1g1` (kingside white), `e1c1` (queenside white)
|
|
||||||
|
|
||||||
## Streaming
|
|
||||||
Endpoints that support streaming return **NDJSON** (newline-delimited JSON).
|
|
||||||
Request them with:
|
|
||||||
```
|
|
||||||
Accept: application/x-ndjson
|
|
||||||
```
|
|
||||||
Each line of the response is a complete JSON object. Empty lines are
|
|
||||||
keep-alive heartbeats.
|
|
||||||
|
|
||||||
## Rate limiting
|
|
||||||
Requests that exceed the rate limit receive `429 Too Many Requests`.
|
|
||||||
Honour the `Retry-After` response header and wait before retrying.
|
|
||||||
version: 1.0.0
|
|
||||||
contact:
|
|
||||||
name: NowChess
|
|
||||||
license:
|
|
||||||
name: MIT
|
|
||||||
|
|
||||||
servers:
|
|
||||||
- url: http://localhost:8080
|
|
||||||
description: Local development server
|
|
||||||
|
|
||||||
tags:
|
|
||||||
- name: game
|
|
||||||
description: Create and manage chess games
|
|
||||||
- name: move
|
|
||||||
description: Make moves and navigate game history
|
|
||||||
- name: draw
|
|
||||||
description: Draw offers and claims
|
|
||||||
- name: import
|
|
||||||
description: Load a game from FEN or PGN
|
|
||||||
- name: export
|
|
||||||
description: Export a game as FEN or PGN
|
|
||||||
|
|
||||||
paths:
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Game lifecycle
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game:
|
|
||||||
post:
|
|
||||||
operationId: createGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Create a new game
|
|
||||||
description: |
|
|
||||||
Creates a new chess game starting from the initial position.
|
|
||||||
Returns the full game state including the generated `gameId`.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
requestBody:
|
|
||||||
required: false
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CreateGameRequest'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Game created
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'401':
|
|
||||||
$ref: '#/components/responses/Unauthorized'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}:
|
|
||||||
get:
|
|
||||||
operationId: getGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Get game state
|
|
||||||
description: Returns the full current state of a game.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Current game state
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/stream:
|
|
||||||
get:
|
|
||||||
operationId: streamGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Stream game events
|
|
||||||
description: |
|
|
||||||
Opens a persistent NDJSON stream for a game. The first object sent is
|
|
||||||
a `gameFull` event containing the complete game state. Subsequent
|
|
||||||
objects are `gameState` events sent whenever the game changes (move
|
|
||||||
made, draw offered, game over, etc.).
|
|
||||||
|
|
||||||
Empty lines are heartbeats to keep the connection alive.
|
|
||||||
|
|
||||||
Connect with:
|
|
||||||
```
|
|
||||||
Accept: application/x-ndjson
|
|
||||||
```
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: NDJSON event stream
|
|
||||||
content:
|
|
||||||
application/x-ndjson:
|
|
||||||
schema:
|
|
||||||
oneOf:
|
|
||||||
- $ref: '#/components/schemas/GameFullEvent'
|
|
||||||
- $ref: '#/components/schemas/GameStateEvent'
|
|
||||||
- $ref: '#/components/schemas/ErrorEvent'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/resign:
|
|
||||||
post:
|
|
||||||
operationId: resignGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Resign the game
|
|
||||||
description: The active player resigns. The game ends immediately.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Resignation accepted
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/OkResponse'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Move-making
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/move/{uci}:
|
|
||||||
post:
|
|
||||||
operationId: makeMove
|
|
||||||
tags: [move]
|
|
||||||
summary: Make a move
|
|
||||||
description: |
|
|
||||||
Submit a move in UCI notation. The move must be legal for the side
|
|
||||||
currently to move.
|
|
||||||
|
|
||||||
For promotion moves include the target piece as the fifth character:
|
|
||||||
`e7e8q`, `a2a1r`, etc. Promotion moves without the fifth character
|
|
||||||
are rejected with `400 INVALID_MOVE`.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
- name: uci
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: Move in UCI notation (e.g. `e2e4`, `e7e8q`)
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
pattern: '^[a-h][1-8][a-h][1-8][qrbn]?$'
|
|
||||||
example: e2e4
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Move applied — returns updated game state
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/moves:
|
|
||||||
get:
|
|
||||||
operationId: getLegalMoves
|
|
||||||
tags: [move]
|
|
||||||
summary: Get legal moves
|
|
||||||
description: |
|
|
||||||
Returns all legal moves for the side currently to move.
|
|
||||||
Optionally filter to moves originating from a single square.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
- name: square
|
|
||||||
in: query
|
|
||||||
required: false
|
|
||||||
description: Filter to moves from this square (e.g. `e2`)
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
pattern: '^[a-h][1-8]$'
|
|
||||||
example: e2
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: List of legal moves
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/LegalMovesResponse'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/undo:
|
|
||||||
post:
|
|
||||||
operationId: undoMove
|
|
||||||
tags: [move]
|
|
||||||
summary: Undo the last move
|
|
||||||
description: Reverts the most recent move. Returns the updated game state.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Move undone
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
'400':
|
|
||||||
description: No moves to undo
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/redo:
|
|
||||||
post:
|
|
||||||
operationId: redoMove
|
|
||||||
tags: [move]
|
|
||||||
summary: Redo a previously undone move
|
|
||||||
description: Re-applies the next move in the undo stack. Returns the updated game state.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Move redone
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
'400':
|
|
||||||
description: No moves to redo
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Draw handling
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/draw/{action}:
|
|
||||||
post:
|
|
||||||
operationId: drawAction
|
|
||||||
tags: [draw]
|
|
||||||
summary: Offer, accept, decline, or claim a draw
|
|
||||||
description: |
|
|
||||||
Perform a draw-related action:
|
|
||||||
|
|
||||||
| Action | Description |
|
|
||||||
|-----------|-------------|
|
|
||||||
| `offer` | Offer a draw to the opponent |
|
|
||||||
| `accept` | Accept the opponent's draw offer |
|
|
||||||
| `decline` | Decline the opponent's draw offer |
|
|
||||||
| `claim` | Claim a draw under the fifty-move rule (only valid when `status` is `fiftyMoveAvailable`) |
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
- name: action
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
enum: [offer, accept, decline, claim]
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Action accepted
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/OkResponse'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Import
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/import/fen:
|
|
||||||
post:
|
|
||||||
operationId: importFen
|
|
||||||
tags: [import]
|
|
||||||
summary: Load a position from FEN
|
|
||||||
description: |
|
|
||||||
Creates a new game from a FEN string. The game starts at the position
|
|
||||||
described by the FEN; move history prior to that position is not
|
|
||||||
available.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ImportFenRequest'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Game created from FEN
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/import/pgn:
|
|
||||||
post:
|
|
||||||
operationId: importPgn
|
|
||||||
tags: [import]
|
|
||||||
summary: Load a game from PGN
|
|
||||||
description: |
|
|
||||||
Creates a new game by replaying all moves in a PGN string. The game
|
|
||||||
starts at the position after the final move in the PGN; undo is
|
|
||||||
available for every replayed move.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ImportPgnRequest'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Game created from PGN
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Export
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/export/fen:
|
|
||||||
get:
|
|
||||||
operationId: exportFen
|
|
||||||
tags: [export]
|
|
||||||
summary: Export current position as FEN
|
|
||||||
description: Returns the FEN string representing the current board position.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: FEN string
|
|
||||||
content:
|
|
||||||
text/plain:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/export/pgn:
|
|
||||||
get:
|
|
||||||
operationId: exportPgn
|
|
||||||
tags: [export]
|
|
||||||
summary: Export game as PGN
|
|
||||||
description: Returns the full PGN for the game including headers and move text.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: PGN text
|
|
||||||
content:
|
|
||||||
application/x-chess-pgn:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: |
|
|
||||||
[Event "NowChess game"]
|
|
||||||
[White "Player1"]
|
|
||||||
[Black "Player2"]
|
|
||||||
[Result "*"]
|
|
||||||
|
|
||||||
1. e4 e5 2. Nf3 *
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Components
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
components:
|
|
||||||
|
|
||||||
securitySchemes:
|
|
||||||
bearerAuth:
|
|
||||||
type: http
|
|
||||||
scheme: bearer
|
|
||||||
description: 'Personal access token — `Authorization: Bearer <token>`'
|
|
||||||
|
|
||||||
parameters:
|
|
||||||
gameId:
|
|
||||||
name: gameId
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: 8-character alphanumeric game ID (e.g. `Qa7FJNk2`)
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
pattern: '^[A-Za-z0-9]{8}$'
|
|
||||||
example: Qa7FJNk2
|
|
||||||
|
|
||||||
responses:
|
|
||||||
BadRequest:
|
|
||||||
description: Invalid input
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
Unauthorized:
|
|
||||||
description: Missing or invalid authentication token
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
NotFound:
|
|
||||||
description: Game not found
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
TooManyRequests:
|
|
||||||
description: Rate limit exceeded — see `Retry-After` header
|
|
||||||
headers:
|
|
||||||
Retry-After:
|
|
||||||
description: Seconds to wait before retrying
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
|
|
||||||
schemas:
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Requests
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
CreateGameRequest:
|
|
||||||
type: object
|
|
||||||
description: Parameters for creating a new game. All fields are optional.
|
|
||||||
properties:
|
|
||||||
white:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
black:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
|
|
||||||
ImportFenRequest:
|
|
||||||
type: object
|
|
||||||
required: [fen]
|
|
||||||
properties:
|
|
||||||
fen:
|
|
||||||
type: string
|
|
||||||
description: Complete FEN string (6 fields)
|
|
||||||
example: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
|
||||||
white:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
black:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
|
|
||||||
ImportPgnRequest:
|
|
||||||
type: object
|
|
||||||
required: [pgn]
|
|
||||||
properties:
|
|
||||||
pgn:
|
|
||||||
type: string
|
|
||||||
description: PGN text (headers and move list)
|
|
||||||
example: "1. e4 e5 2. Nf3 Nc6 *"
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Game state
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
GameFull:
|
|
||||||
type: object
|
|
||||||
description: Complete game information including players and current state.
|
|
||||||
required: [gameId, white, black, state]
|
|
||||||
properties:
|
|
||||||
gameId:
|
|
||||||
type: string
|
|
||||||
description: Unique 8-character game identifier
|
|
||||||
example: Qa7FJNk2
|
|
||||||
white:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
black:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
state:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
|
|
||||||
GameState:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
The current game state. Included in `GameFull` and returned by move
|
|
||||||
endpoints and stream events.
|
|
||||||
required: [fen, pgn, turn, status, moves, undoAvailable, redoAvailable]
|
|
||||||
properties:
|
|
||||||
fen:
|
|
||||||
type: string
|
|
||||||
description: FEN string for the current position
|
|
||||||
example: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
|
||||||
pgn:
|
|
||||||
type: string
|
|
||||||
description: PGN move text for the full game so far
|
|
||||||
example: "1. e4"
|
|
||||||
turn:
|
|
||||||
type: string
|
|
||||||
enum: [white, black]
|
|
||||||
description: The side to move
|
|
||||||
status:
|
|
||||||
$ref: '#/components/schemas/GameStatus'
|
|
||||||
winner:
|
|
||||||
type: string
|
|
||||||
enum: [white, black]
|
|
||||||
description: Set when `status` is `checkmate` or `resign`
|
|
||||||
nullable: true
|
|
||||||
moves:
|
|
||||||
type: array
|
|
||||||
description: All moves played so far, in UCI notation
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
example: [e2e4, e7e5, g1f3]
|
|
||||||
undoAvailable:
|
|
||||||
type: boolean
|
|
||||||
description: Whether `POST /undo` is currently valid
|
|
||||||
redoAvailable:
|
|
||||||
type: boolean
|
|
||||||
description: Whether `POST /redo` is currently valid
|
|
||||||
|
|
||||||
GameStatus:
|
|
||||||
type: string
|
|
||||||
description: |
|
|
||||||
Current game status:
|
|
||||||
|
|
||||||
| Value | Meaning |
|
|
||||||
|-------|---------|
|
|
||||||
| `started` | Game in progress, no special condition |
|
|
||||||
| `check` | Side to move is in check |
|
|
||||||
| `checkmate` | Side to move is checkmated — game over |
|
|
||||||
| `stalemate` | Side to move has no legal moves, not in check — game over (draw) |
|
|
||||||
| `resign` | A player resigned — game over |
|
|
||||||
| `draw` | Draw agreed or claimed — game over |
|
|
||||||
| `drawOffered` | Waiting for the opponent to accept or decline a draw offer |
|
|
||||||
| `fiftyMoveAvailable` | Fifty-move rule threshold reached; active player may claim draw |
|
|
||||||
| `insufficientMaterial` | Neither side has enough pieces to deliver checkmate — game over (draw) |
|
|
||||||
enum:
|
|
||||||
- started
|
|
||||||
- check
|
|
||||||
- checkmate
|
|
||||||
- stalemate
|
|
||||||
- resign
|
|
||||||
- draw
|
|
||||||
- drawOffered
|
|
||||||
- fiftyMoveAvailable
|
|
||||||
- insufficientMaterial
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Moves
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
LegalMovesResponse:
|
|
||||||
type: object
|
|
||||||
required: [moves]
|
|
||||||
properties:
|
|
||||||
moves:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/LegalMove'
|
|
||||||
|
|
||||||
LegalMove:
|
|
||||||
type: object
|
|
||||||
required: [from, to, uci, moveType]
|
|
||||||
properties:
|
|
||||||
from:
|
|
||||||
type: string
|
|
||||||
description: Origin square in algebraic notation
|
|
||||||
example: e2
|
|
||||||
to:
|
|
||||||
type: string
|
|
||||||
description: Destination square in algebraic notation
|
|
||||||
example: e4
|
|
||||||
uci:
|
|
||||||
type: string
|
|
||||||
description: Full move in UCI notation
|
|
||||||
example: e2e4
|
|
||||||
moveType:
|
|
||||||
$ref: '#/components/schemas/MoveType'
|
|
||||||
promotion:
|
|
||||||
type: string
|
|
||||||
enum: [queen, rook, bishop, knight]
|
|
||||||
description: Target piece for promotion moves
|
|
||||||
nullable: true
|
|
||||||
|
|
||||||
MoveType:
|
|
||||||
type: string
|
|
||||||
description: Classification of the move
|
|
||||||
enum:
|
|
||||||
- normal
|
|
||||||
- capture
|
|
||||||
- castleKingside
|
|
||||||
- castleQueenside
|
|
||||||
- enPassant
|
|
||||||
- promotion
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Streaming events
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
GameFullEvent:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
First event on a game stream. Contains the complete game snapshot.
|
|
||||||
required: [type, game]
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum: [gameFull]
|
|
||||||
game:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
|
|
||||||
GameStateEvent:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
Emitted on a game stream whenever the game state changes (move played,
|
|
||||||
draw offered, game over, etc.).
|
|
||||||
required: [type, state]
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum: [gameState]
|
|
||||||
state:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
|
|
||||||
ErrorEvent:
|
|
||||||
type: object
|
|
||||||
description: Emitted on a game stream when an error occurs.
|
|
||||||
required: [type, error]
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum: [error]
|
|
||||||
error:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Shared types
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
PlayerInfo:
|
|
||||||
type: object
|
|
||||||
required: [id, displayName]
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
description: Unique player identifier
|
|
||||||
example: player1
|
|
||||||
displayName:
|
|
||||||
type: string
|
|
||||||
description: Human-readable display name
|
|
||||||
example: Alice
|
|
||||||
|
|
||||||
OkResponse:
|
|
||||||
type: object
|
|
||||||
required: [ok]
|
|
||||||
properties:
|
|
||||||
ok:
|
|
||||||
type: boolean
|
|
||||||
enum: [true]
|
|
||||||
|
|
||||||
ApiError:
|
|
||||||
type: object
|
|
||||||
required: [code, message]
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
type: string
|
|
||||||
description: Machine-readable error code
|
|
||||||
example: INVALID_MOVE
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
description: Human-readable error description
|
|
||||||
example: e2e5 is not a legal move
|
|
||||||
field:
|
|
||||||
type: string
|
|
||||||
description: Request field that caused the error, if applicable
|
|
||||||
example: uci
|
|
||||||
nullable: true
|
|
||||||
@@ -0,0 +1,649 @@
|
|||||||
|
# 50-Move Rule Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Implement the FIDE 50-move rule: track the half-move clock in `GameHistory`, let the player claim a draw by typing `draw` once the clock reaches 100, notify observers when the threshold is crossed, fix PGN to use the `Result` header as its termination marker, and verify FEN round-trips the clock correctly.
|
||||||
|
|
||||||
|
**Architecture:** `GameHistory` gains a `halfMoveClock: Int` field updated by `GameController`; `GameEngine` handles the `"draw"` text command and fires two new events (`FiftyMoveRuleAvailableEvent`, `DrawClaimedEvent`); `PgnExporter` derives its termination marker from the `Result` header instead of hardcoding `*`. No changes to `MoveValidator`, `GameRules`, `EnPassantCalculator`, or `CastlingRightsCalculator`.
|
||||||
|
|
||||||
|
**Tech Stack:** Scala 3.5.x, ScalaTest `AnyFunSuite with Matchers`, Gradle
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Map
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/logic/GameHistory.scala` | Add `halfMoveClock` field; extend `addMove` with clock-reset flags |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala` | Use `Result` header as PGN termination marker |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala` | Compute and pass `wasPawnMove`/`wasCapture` flags in `applyNormalMove` and `completePromotion` |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` | Add `FiftyMoveRuleAvailableEvent` and `DrawClaimedEvent` |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` | Handle `"draw"` input; fire `FiftyMoveRuleAvailableEvent` after eligible moves |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/logic/GameHistoryTest.scala` | Clock update rules |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala` | `1/2-1/2` termination marker |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala` | Clock values from `applyNormalMove` / `completePromotion` |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala` | `"draw"` command and `FiftyMoveRuleAvailableEvent` |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/notation/FenExporterTest.scala` | FEN round-trip for non-zero `halfMoveClock` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: `GameHistory` — half-move clock field and `addMove` flags
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `modules/core/src/main/scala/de/nowchess/chess/logic/GameHistory.scala`
|
||||||
|
- Modify: `modules/core/src/test/scala/de/nowchess/chess/logic/GameHistoryTest.scala`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
|
Add to `GameHistoryTest.scala` (after the existing tests):
|
||||||
|
|
||||||
|
```scala
|
||||||
|
// ──── half-move clock ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
test("halfMoveClock starts at 0"):
|
||||||
|
GameHistory.empty.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock increments on a non-pawn non-capture move"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.G, Rank.R1), sq(File.F, Rank.R3))
|
||||||
|
h.halfMoveClock shouldBe 1
|
||||||
|
|
||||||
|
test("halfMoveClock resets to 0 on a pawn move"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.E, Rank.R2), sq(File.E, Rank.R4), wasPawnMove = true)
|
||||||
|
h.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock resets to 0 on a capture"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.E, Rank.R5), sq(File.D, Rank.R6), wasCapture = true)
|
||||||
|
h.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock resets to 0 when both wasPawnMove and wasCapture are true"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.E, Rank.R5), sq(File.D, Rank.R6), wasPawnMove = true, wasCapture = true)
|
||||||
|
h.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock carries across multiple moves"):
|
||||||
|
val h = GameHistory.empty
|
||||||
|
.addMove(sq(File.G, Rank.R1), sq(File.F, Rank.R3)) // +1 → 1
|
||||||
|
.addMove(sq(File.G, Rank.R8), sq(File.F, Rank.R6)) // +1 → 2
|
||||||
|
.addMove(sq(File.E, Rank.R2), sq(File.E, Rank.R4), wasPawnMove = true) // reset → 0
|
||||||
|
.addMove(sq(File.B, Rank.R1), sq(File.C, Rank.R3)) // +1 → 1
|
||||||
|
h.halfMoveClock shouldBe 1
|
||||||
|
|
||||||
|
test("GameHistory can be initialised with a non-zero halfMoveClock"):
|
||||||
|
val h = GameHistory(halfMoveClock = 42)
|
||||||
|
h.halfMoveClock shouldBe 42
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test --tests "de.nowchess.chess.logic.GameHistoryTest" 2>&1 | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: compile error — `halfMoveClock` not a field of `GameHistory`, `wasPawnMove`/`wasCapture` not params of `addMove`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the changes in `GameHistory.scala`**
|
||||||
|
|
||||||
|
Replace the entire file with:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
package de.nowchess.chess.logic
|
||||||
|
|
||||||
|
import de.nowchess.api.board.Square
|
||||||
|
import de.nowchess.api.move.PromotionPiece
|
||||||
|
|
||||||
|
/** A single move recorded in the game history. Distinct from api.move.Move which represents user intent. */
|
||||||
|
case class HistoryMove(
|
||||||
|
from: Square,
|
||||||
|
to: Square,
|
||||||
|
castleSide: Option[CastleSide],
|
||||||
|
promotionPiece: Option[PromotionPiece] = None
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Complete game history: ordered list of moves plus the half-move clock for the 50-move rule.
|
||||||
|
*
|
||||||
|
* @param moves moves played so far, oldest first
|
||||||
|
* @param halfMoveClock plies since the last pawn move or capture (FIDE 50-move rule counter)
|
||||||
|
*/
|
||||||
|
case class GameHistory(moves: List[HistoryMove] = List.empty, halfMoveClock: Int = 0):
|
||||||
|
|
||||||
|
/** Add a raw HistoryMove record. Clock increments by 1.
|
||||||
|
* Use the coordinate overload when you know whether the move is a pawn move or capture.
|
||||||
|
*/
|
||||||
|
def addMove(move: HistoryMove): GameHistory =
|
||||||
|
GameHistory(moves :+ move, halfMoveClock + 1)
|
||||||
|
|
||||||
|
/** Add a move by coordinates.
|
||||||
|
*
|
||||||
|
* @param wasPawnMove true when the moving piece is a pawn — resets the clock to 0
|
||||||
|
* @param wasCapture true when a piece was captured (including en passant) — resets the clock to 0
|
||||||
|
*
|
||||||
|
* If neither flag is set the clock increments by 1.
|
||||||
|
*/
|
||||||
|
def addMove(
|
||||||
|
from: Square,
|
||||||
|
to: Square,
|
||||||
|
castleSide: Option[CastleSide] = None,
|
||||||
|
promotionPiece: Option[PromotionPiece] = None,
|
||||||
|
wasPawnMove: Boolean = false,
|
||||||
|
wasCapture: Boolean = false
|
||||||
|
): GameHistory =
|
||||||
|
val newClock = if wasPawnMove || wasCapture then 0 else halfMoveClock + 1
|
||||||
|
GameHistory(moves :+ HistoryMove(from, to, castleSide, promotionPiece), newClock)
|
||||||
|
|
||||||
|
object GameHistory:
|
||||||
|
val empty: GameHistory = GameHistory()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test 2>&1 | tail -10
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: BUILD SUCCESSFUL — all existing tests pass (the new `halfMoveClock` field defaults to 0 so no existing construction sites break), new clock tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add modules/core/src/main/scala/de/nowchess/chess/logic/GameHistory.scala \
|
||||||
|
modules/core/src/test/scala/de/nowchess/chess/logic/GameHistoryTest.scala
|
||||||
|
git commit -m "feat: NCS-11 add halfMoveClock to GameHistory with addMove reset flags"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: `PgnExporter` — use `Result` header as termination marker
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala`
|
||||||
|
- Modify: `modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing tests**
|
||||||
|
|
||||||
|
Add to `PgnExporterTest.scala` (after the existing tests):
|
||||||
|
|
||||||
|
```scala
|
||||||
|
test("exportGame uses Result header as termination marker"):
|
||||||
|
val history = GameHistory()
|
||||||
|
.addMove(HistoryMove(Square(File.E, Rank.R2), Square(File.E, Rank.R4), None))
|
||||||
|
val pgn = PgnExporter.exportGame(Map("Result" -> "1/2-1/2"), history)
|
||||||
|
pgn should endWith("1/2-1/2")
|
||||||
|
|
||||||
|
test("exportGame with no Result header still uses * as default"):
|
||||||
|
val history = GameHistory()
|
||||||
|
.addMove(HistoryMove(Square(File.E, Rank.R2), Square(File.E, Rank.R4), None))
|
||||||
|
val pgn = PgnExporter.exportGame(Map.empty, history)
|
||||||
|
pgn shouldBe "1. e2e4 *"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test --tests "de.nowchess.chess.notation.PgnExporterTest" 2>&1 | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `exportGame` with `"Result" -> "1/2-1/2"` still produces `*`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the fix in `PgnExporter.scala`**
|
||||||
|
|
||||||
|
Find the line that builds the move text — currently:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
moveLines.mkString(" ") + " *"
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace it with:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val termination = headers.getOrElse("Result", "*")
|
||||||
|
moveLines.mkString(" ") + s" $termination"
|
||||||
|
```
|
||||||
|
|
||||||
|
The full updated `exportGame` method:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
def exportGame(headers: Map[String, String], history: GameHistory): String =
|
||||||
|
val headerLines = headers.map { case (key, value) =>
|
||||||
|
s"""[$key "$value"]"""
|
||||||
|
}.mkString("\n")
|
||||||
|
|
||||||
|
val moveText = if history.moves.isEmpty then ""
|
||||||
|
else
|
||||||
|
val groupedMoves = history.moves.zipWithIndex.groupBy(_._2 / 2)
|
||||||
|
val moveLines = for (moveNumber, movePairs) <- groupedMoves.toList.sortBy(_._1) yield
|
||||||
|
val moveNum = moveNumber + 1
|
||||||
|
val whiteMoveStr = movePairs.find(_._2 % 2 == 0).map(p => moveToAlgebraic(p._1)).getOrElse("")
|
||||||
|
val blackMoveStr = movePairs.find(_._2 % 2 == 1).map(p => moveToAlgebraic(p._1)).getOrElse("")
|
||||||
|
if blackMoveStr.isEmpty then s"$moveNum. $whiteMoveStr"
|
||||||
|
else s"$moveNum. $whiteMoveStr $blackMoveStr"
|
||||||
|
|
||||||
|
val termination = headers.getOrElse("Result", "*")
|
||||||
|
moveLines.mkString(" ") + s" $termination"
|
||||||
|
|
||||||
|
if headerLines.isEmpty then moveText
|
||||||
|
else if moveText.isEmpty then headerLines
|
||||||
|
else s"$headerLines\n\n$moveText"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test --tests "de.nowchess.chess.notation.PgnExporterTest" 2>&1 | tail -10
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: BUILD SUCCESSFUL, all PGN exporter tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run full test suite**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test 2>&1 | tail -10
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: BUILD SUCCESSFUL.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala \
|
||||||
|
modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala
|
||||||
|
git commit -m "feat: NCS-11 derive PGN termination marker from Result header"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: `GameController` — pass clock flags in `applyNormalMove` and `completePromotion`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala`
|
||||||
|
- Modify: `modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing tests**
|
||||||
|
|
||||||
|
Add to `GameControllerTest.scala` (after the existing tests):
|
||||||
|
|
||||||
|
```scala
|
||||||
|
// ──── half-move clock propagation ────────────────────────────────────
|
||||||
|
|
||||||
|
test("processMove: non-pawn non-capture increments halfMoveClock"):
|
||||||
|
// g1f3 is a knight move — not a pawn, not a capture
|
||||||
|
processMove(Board.initial, GameHistory.empty, Color.White, "g1f3") match
|
||||||
|
case MoveResult.Moved(_, newHistory, _, _) =>
|
||||||
|
newHistory.halfMoveClock shouldBe 1
|
||||||
|
case other => fail(s"Expected Moved, got $other")
|
||||||
|
|
||||||
|
test("processMove: pawn move resets halfMoveClock to 0"):
|
||||||
|
processMove(Board.initial, GameHistory.empty, Color.White, "e2e4") match
|
||||||
|
case MoveResult.Moved(_, newHistory, _, _) =>
|
||||||
|
newHistory.halfMoveClock shouldBe 0
|
||||||
|
case other => fail(s"Expected Moved, got $other")
|
||||||
|
|
||||||
|
test("processMove: capture resets halfMoveClock to 0"):
|
||||||
|
// White pawn on e5, Black pawn on d6 — exd6 is a capture
|
||||||
|
val board = Board(Map(
|
||||||
|
sq(File.E, Rank.R5) -> Piece.WhitePawn,
|
||||||
|
sq(File.D, Rank.R6) -> Piece.BlackPawn,
|
||||||
|
sq(File.E, Rank.R1) -> Piece.WhiteKing,
|
||||||
|
sq(File.E, Rank.R8) -> Piece.BlackKing
|
||||||
|
))
|
||||||
|
val history = GameHistory(halfMoveClock = 10)
|
||||||
|
processMove(board, history, Color.White, "e5d6") match
|
||||||
|
case MoveResult.Moved(_, newHistory, _, _) =>
|
||||||
|
newHistory.halfMoveClock shouldBe 0
|
||||||
|
case other => fail(s"Expected Moved, got $other")
|
||||||
|
|
||||||
|
test("processMove: clock carries from previous history on non-pawn non-capture"):
|
||||||
|
val history = GameHistory(halfMoveClock = 5)
|
||||||
|
processMove(Board.initial, history, Color.White, "g1f3") match
|
||||||
|
case MoveResult.Moved(_, newHistory, _, _) =>
|
||||||
|
newHistory.halfMoveClock shouldBe 6
|
||||||
|
case other => fail(s"Expected Moved, got $other")
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test --tests "de.nowchess.chess.controller.GameControllerTest" 2>&1 | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — clock tests show 1 where 0 is expected (pawn/capture not resetting) and 0 where 1 is expected (knight not incrementing from initial empty history).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the fix in `GameController.scala`**
|
||||||
|
|
||||||
|
Update `applyNormalMove` and `completePromotion`. Replace the entire file with:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
package de.nowchess.chess.controller
|
||||||
|
|
||||||
|
import de.nowchess.api.board.{Board, Color, File, Piece, PieceType, Rank, Square}
|
||||||
|
import de.nowchess.api.move.PromotionPiece
|
||||||
|
import de.nowchess.chess.logic.*
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Result ADT returned by the pure processMove function
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
sealed trait MoveResult
|
||||||
|
object MoveResult:
|
||||||
|
case object Quit extends MoveResult
|
||||||
|
case class InvalidFormat(raw: String) extends MoveResult
|
||||||
|
case object NoPiece extends MoveResult
|
||||||
|
case object WrongColor extends MoveResult
|
||||||
|
case object IllegalMove extends MoveResult
|
||||||
|
case class PromotionRequired(
|
||||||
|
from: Square,
|
||||||
|
to: Square,
|
||||||
|
boardBefore: Board,
|
||||||
|
historyBefore: GameHistory,
|
||||||
|
captured: Option[Piece],
|
||||||
|
turn: Color
|
||||||
|
) extends MoveResult
|
||||||
|
case class Moved(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult
|
||||||
|
case class MovedInCheck(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], newTurn: Color) extends MoveResult
|
||||||
|
case class Checkmate(winner: Color) extends MoveResult
|
||||||
|
case object Stalemate extends MoveResult
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Controller
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
object GameController:
|
||||||
|
|
||||||
|
/** Pure function: interprets one raw input line against the current game context.
|
||||||
|
* Has no I/O side effects — all output must be handled by the caller.
|
||||||
|
*/
|
||||||
|
def processMove(board: Board, history: GameHistory, turn: Color, raw: String): MoveResult =
|
||||||
|
raw.trim match
|
||||||
|
case "quit" | "q" => MoveResult.Quit
|
||||||
|
case trimmed =>
|
||||||
|
Parser.parseMove(trimmed) match
|
||||||
|
case None => MoveResult.InvalidFormat(trimmed)
|
||||||
|
case Some((from, to)) => validateAndApply(board, history, turn, from, to)
|
||||||
|
|
||||||
|
/** Apply a previously detected promotion move with the chosen piece.
|
||||||
|
* Called after processMove returned PromotionRequired.
|
||||||
|
*/
|
||||||
|
def completePromotion(
|
||||||
|
board: Board,
|
||||||
|
history: GameHistory,
|
||||||
|
from: Square,
|
||||||
|
to: Square,
|
||||||
|
piece: PromotionPiece,
|
||||||
|
turn: Color
|
||||||
|
): MoveResult =
|
||||||
|
val (boardAfterMove, captured) = board.withMove(from, to)
|
||||||
|
val promotedPieceType = piece match
|
||||||
|
case PromotionPiece.Queen => PieceType.Queen
|
||||||
|
case PromotionPiece.Rook => PieceType.Rook
|
||||||
|
case PromotionPiece.Bishop => PieceType.Bishop
|
||||||
|
case PromotionPiece.Knight => PieceType.Knight
|
||||||
|
val newBoard = boardAfterMove.updated(to, Piece(turn, promotedPieceType))
|
||||||
|
// Promotion is always a pawn move → clock resets
|
||||||
|
val newHistory = history.addMove(from, to, None, Some(piece), wasPawnMove = true)
|
||||||
|
toMoveResult(newBoard, newHistory, captured, turn)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private def validateAndApply(board: Board, history: GameHistory, turn: Color, from: Square, to: Square): MoveResult =
|
||||||
|
board.pieceAt(from) match
|
||||||
|
case None => MoveResult.NoPiece
|
||||||
|
case Some(piece) if piece.color != turn => MoveResult.WrongColor
|
||||||
|
case Some(_) =>
|
||||||
|
if !MoveValidator.isLegal(board, history, from, to) then MoveResult.IllegalMove
|
||||||
|
else if MoveValidator.isPromotionMove(board, from, to) then
|
||||||
|
MoveResult.PromotionRequired(from, to, board, history, board.pieceAt(to), turn)
|
||||||
|
else applyNormalMove(board, history, turn, from, to)
|
||||||
|
|
||||||
|
private def applyNormalMove(board: Board, history: GameHistory, turn: Color, from: Square, to: Square): MoveResult =
|
||||||
|
val castleOpt = Option.when(MoveValidator.isCastle(board, from, to))(MoveValidator.castleSide(from, to))
|
||||||
|
val isEP = EnPassantCalculator.isEnPassant(board, history, from, to)
|
||||||
|
val (newBoard, captured) = castleOpt match
|
||||||
|
case Some(side) => (board.withCastle(turn, side), None)
|
||||||
|
case None =>
|
||||||
|
val (b, cap) = board.withMove(from, to)
|
||||||
|
if isEP then
|
||||||
|
val capturedSq = EnPassantCalculator.capturedPawnSquare(to, turn)
|
||||||
|
(b.removed(capturedSq), board.pieceAt(capturedSq))
|
||||||
|
else (b, cap)
|
||||||
|
val wasPawnMove = board.pieceAt(from).exists(_.pieceType == PieceType.Pawn)
|
||||||
|
val wasCapture = captured.isDefined
|
||||||
|
val newHistory = history.addMove(from, to, castleOpt, wasPawnMove = wasPawnMove, wasCapture = wasCapture)
|
||||||
|
toMoveResult(newBoard, newHistory, captured, turn)
|
||||||
|
|
||||||
|
private def toMoveResult(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], turn: Color): MoveResult =
|
||||||
|
GameRules.gameStatus(newBoard, newHistory, turn.opposite) match
|
||||||
|
case PositionStatus.Normal => MoveResult.Moved(newBoard, newHistory, captured, turn.opposite)
|
||||||
|
case PositionStatus.InCheck => MoveResult.MovedInCheck(newBoard, newHistory, captured, turn.opposite)
|
||||||
|
case PositionStatus.Mated => MoveResult.Checkmate(turn)
|
||||||
|
case PositionStatus.Drawn => MoveResult.Stalemate
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test 2>&1 | tail -10
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: BUILD SUCCESSFUL.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala \
|
||||||
|
modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala
|
||||||
|
git commit -m "feat: NCS-11 propagate half-move clock flags through GameController"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: `Observer` events + `GameEngine` draw command
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
||||||
|
- Modify: `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
||||||
|
- Modify: `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala`
|
||||||
|
- Modify: `modules/core/src/test/scala/de/nowchess/chess/notation/FenExporterTest.scala`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing tests**
|
||||||
|
|
||||||
|
Add the following to `GameEngineTest.scala`. The mock observer class is already present at the bottom of that file — add these tests before it:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
// ──── 50-move rule ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
test("GameEngine: 'draw' rejected when halfMoveClock < 100"):
|
||||||
|
val engine = new GameEngine()
|
||||||
|
val observer = new MockObserver()
|
||||||
|
engine.subscribe(observer)
|
||||||
|
engine.processUserInput("draw")
|
||||||
|
observer.events.size shouldBe 1
|
||||||
|
observer.events.head shouldBe a[InvalidMoveEvent]
|
||||||
|
|
||||||
|
test("GameEngine: 'draw' accepted and fires DrawClaimedEvent when halfMoveClock >= 100"):
|
||||||
|
val engine = new GameEngine(initialHistory = GameHistory(halfMoveClock = 100))
|
||||||
|
val observer = new MockObserver()
|
||||||
|
engine.subscribe(observer)
|
||||||
|
engine.processUserInput("draw")
|
||||||
|
observer.events.size shouldBe 1
|
||||||
|
observer.events.head shouldBe a[DrawClaimedEvent]
|
||||||
|
|
||||||
|
test("GameEngine: state resets to initial after draw claimed"):
|
||||||
|
val engine = new GameEngine(initialHistory = GameHistory(halfMoveClock = 100))
|
||||||
|
engine.processUserInput("draw")
|
||||||
|
engine.board shouldBe Board.initial
|
||||||
|
engine.history shouldBe GameHistory.empty
|
||||||
|
engine.turn shouldBe Color.White
|
||||||
|
|
||||||
|
test("GameEngine: FiftyMoveRuleAvailableEvent fired when move brings clock to 100"):
|
||||||
|
// Start at clock 99; a knight move (non-pawn, non-capture) increments to 100
|
||||||
|
val engine = new GameEngine(initialHistory = GameHistory(halfMoveClock = 99))
|
||||||
|
val observer = new MockObserver()
|
||||||
|
engine.subscribe(observer)
|
||||||
|
engine.processUserInput("g1f3") // knight move on initial board
|
||||||
|
// Should receive MoveExecutedEvent AND FiftyMoveRuleAvailableEvent
|
||||||
|
observer.events.exists(_.isInstanceOf[FiftyMoveRuleAvailableEvent]) shouldBe true
|
||||||
|
|
||||||
|
test("GameEngine: FiftyMoveRuleAvailableEvent not fired when clock is below 100 after move"):
|
||||||
|
val engine = new GameEngine(initialHistory = GameHistory(halfMoveClock = 5))
|
||||||
|
val observer = new MockObserver()
|
||||||
|
engine.subscribe(observer)
|
||||||
|
engine.processUserInput("g1f3")
|
||||||
|
observer.events.exists(_.isInstanceOf[FiftyMoveRuleAvailableEvent]) shouldBe false
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add the import to `GameEngineTest.scala`'s import block:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
import de.nowchess.chess.observer.{Observer, GameEvent, MoveExecutedEvent, CheckDetectedEvent, BoardResetEvent, InvalidMoveEvent, FiftyMoveRuleAvailableEvent, DrawClaimedEvent}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the following to `FenExporterTest.scala`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
test("halfMoveClock round-trips through FEN export and import"):
|
||||||
|
import de.nowchess.chess.logic.GameHistory
|
||||||
|
import de.nowchess.chess.notation.FenParser
|
||||||
|
val history = GameHistory(halfMoveClock = 42)
|
||||||
|
val gameState = GameState(
|
||||||
|
piecePlacement = FenExporter.boardToFen(de.nowchess.api.board.Board.initial),
|
||||||
|
activeColor = Color.White,
|
||||||
|
castlingWhite = CastlingRights.Both,
|
||||||
|
castlingBlack = CastlingRights.Both,
|
||||||
|
enPassantTarget = None,
|
||||||
|
halfMoveClock = history.halfMoveClock,
|
||||||
|
fullMoveNumber = 1,
|
||||||
|
status = GameStatus.InProgress
|
||||||
|
)
|
||||||
|
val fen = FenExporter.gameStateToFen(gameState)
|
||||||
|
val parsed = FenParser.parseFen(fen).get
|
||||||
|
parsed.halfMoveClock shouldBe 42
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test --tests "de.nowchess.chess.engine.GameEngineTest" 2>&1 | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: compile error — `DrawClaimedEvent` and `FiftyMoveRuleAvailableEvent` not yet defined.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add new events to `Observer.scala`**
|
||||||
|
|
||||||
|
Add the two new event cases to `Observer.scala`, after `BoardResetEvent`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
/** Fired after any move where the half-move clock reaches 100 — the 50-move rule is now claimable. */
|
||||||
|
case class FiftyMoveRuleAvailableEvent(
|
||||||
|
board: Board,
|
||||||
|
history: GameHistory,
|
||||||
|
turn: Color
|
||||||
|
) extends GameEvent
|
||||||
|
|
||||||
|
/** Fired when a player successfully claims a draw under the 50-move rule. */
|
||||||
|
case class DrawClaimedEvent(
|
||||||
|
board: Board,
|
||||||
|
history: GameHistory,
|
||||||
|
turn: Color
|
||||||
|
) extends GameEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Update `GameEngine.scala` — add `"draw"` case and `FiftyMoveRuleAvailableEvent` notification**
|
||||||
|
|
||||||
|
In `processUserInput`, add the `"draw"` case immediately before the `case moveInput =>` fallthrough (after `case "redo" =>`):
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case "draw" =>
|
||||||
|
if currentHistory.halfMoveClock >= 100 then
|
||||||
|
currentBoard = Board.initial
|
||||||
|
currentHistory = GameHistory.empty
|
||||||
|
currentTurn = Color.White
|
||||||
|
invoker.clear()
|
||||||
|
notifyObservers(DrawClaimedEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
else
|
||||||
|
notifyObservers(InvalidMoveEvent(
|
||||||
|
currentBoard, currentHistory, currentTurn,
|
||||||
|
"Draw cannot be claimed: the 50-move rule has not been triggered."
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
In the same method, in both the `MoveResult.Moved` and `MoveResult.MovedInCheck` handling branches, add a `FiftyMoveRuleAvailableEvent` check **after** the existing `notifyObservers` call for that branch.
|
||||||
|
|
||||||
|
The `Moved` branch currently reads:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
updateGameState(newBoard, newHistory, newTurn)
|
||||||
|
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace it with:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case MoveResult.Moved(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
updateGameState(newBoard, newHistory, newTurn)
|
||||||
|
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
||||||
|
if newHistory.halfMoveClock >= 100 then
|
||||||
|
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
```
|
||||||
|
|
||||||
|
The `MovedInCheck` branch currently reads:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case MoveResult.MovedInCheck(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
updateGameState(newBoard, newHistory, newTurn)
|
||||||
|
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
||||||
|
notifyObservers(CheckDetectedEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace it with:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case MoveResult.MovedInCheck(newBoard, newHistory, captured, newTurn) =>
|
||||||
|
val updatedCmd = cmd.copy(moveResult = Some(de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured)))
|
||||||
|
invoker.execute(updatedCmd)
|
||||||
|
updateGameState(newBoard, newHistory, newTurn)
|
||||||
|
emitMoveEvent(from.toString, to.toString, captured, newTurn)
|
||||||
|
notifyObservers(CheckDetectedEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
if newHistory.halfMoveClock >= 100 then
|
||||||
|
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add `FiftyMoveRuleAvailableEvent` and `DrawClaimedEvent` to the import at the top of `GameEngine.scala`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
import de.nowchess.chess.observer.*
|
||||||
|
```
|
||||||
|
|
||||||
|
(Already a wildcard import — no change needed there.)
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :modules:core:test 2>&1 | tail -10
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: BUILD SUCCESSFUL, all tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala \
|
||||||
|
modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala \
|
||||||
|
modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala \
|
||||||
|
modules/core/src/test/scala/de/nowchess/chess/notation/FenExporterTest.scala
|
||||||
|
git commit -m "feat: NCS-11 implement 50-move rule draw claim and observer events"
|
||||||
|
```
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
# 50-Move Rule — Design Spec
|
||||||
|
**Branch:** feat/NCS-11
|
||||||
|
**Date:** 2026-03-31
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Implement the FIDE 50-move rule: when 100 consecutive half-moves (plies) have been played without a pawn move or capture, the player whose turn it is may claim a draw by typing `draw`. The engine notifies observers when the threshold is reached so the UI can prompt the player.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
The 50-move rule prevents games from continuing indefinitely in positions where neither side can force checkmate. Under FIDE rules it is a player-claimed draw, not automatic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 1: Data Model — `GameHistory`
|
||||||
|
|
||||||
|
`GameHistory` gains one new field:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case class GameHistory(moves: List[HistoryMove] = List.empty, halfMoveClock: Int = 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
The default value `0` means all existing construction sites compile unchanged.
|
||||||
|
|
||||||
|
### Clock update rule
|
||||||
|
|
||||||
|
The clock resets to 0 on any pawn move or capture; otherwise it increments by 1.
|
||||||
|
|
||||||
|
The main `addMove` overload gains two optional boolean flags:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
def addMove(
|
||||||
|
from: Square,
|
||||||
|
to: Square,
|
||||||
|
castleSide: Option[CastleSide] = None,
|
||||||
|
promotionPiece: Option[PromotionPiece] = None,
|
||||||
|
wasPawnMove: Boolean = false,
|
||||||
|
wasCapture: Boolean = false
|
||||||
|
): GameHistory =
|
||||||
|
val newClock = if wasPawnMove || wasCapture then 0 else halfMoveClock + 1
|
||||||
|
GameHistory(moves :+ HistoryMove(from, to, castleSide, promotionPiece), newClock)
|
||||||
|
```
|
||||||
|
|
||||||
|
The base `addMove(HistoryMove)` overload is made **private**; all public call sites route through the flagged overload above.
|
||||||
|
|
||||||
|
The no-argument overload `addMove(from, to)` used in tests and en passant history recording defaults both flags to `false` (clock increments) and remains for backward compatibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 2: Clock Update in `GameController`
|
||||||
|
|
||||||
|
### `applyNormalMove`
|
||||||
|
|
||||||
|
Two flags are derived from already-available data before calling `history.addMove`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val wasPawnMove = board.pieceAt(from).exists(_.pieceType == PieceType.Pawn)
|
||||||
|
val wasCapture = captured.isDefined // computed earlier in the same method
|
||||||
|
val newHistory = history.addMove(from, to, castleOpt,
|
||||||
|
wasPawnMove = wasPawnMove, wasCapture = wasCapture)
|
||||||
|
```
|
||||||
|
|
||||||
|
En passant moves are pawn captures, so both flags are `true` — the clock resets.
|
||||||
|
|
||||||
|
### `completePromotion`
|
||||||
|
|
||||||
|
Pawn promotion is always a pawn move, so `wasPawnMove = true`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val newHistory = history.addMove(from, to, None, Some(piece), wasPawnMove = true)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 3: Claim Mechanism and New Events
|
||||||
|
|
||||||
|
### New events (`Observer.scala`)
|
||||||
|
|
||||||
|
```scala
|
||||||
|
/** Fired after any move where the 50-move rule threshold is reached (halfMoveClock >= 100). */
|
||||||
|
case class FiftyMoveRuleAvailableEvent(
|
||||||
|
board: Board,
|
||||||
|
history: GameHistory,
|
||||||
|
turn: Color
|
||||||
|
) extends GameEvent
|
||||||
|
|
||||||
|
/** Fired when a player successfully claims a draw under the 50-move rule. */
|
||||||
|
case class DrawClaimedEvent(
|
||||||
|
board: Board,
|
||||||
|
history: GameHistory,
|
||||||
|
turn: Color
|
||||||
|
) extends GameEvent
|
||||||
|
```
|
||||||
|
|
||||||
|
### Claim handling in `GameEngine.processUserInput`
|
||||||
|
|
||||||
|
A new `"draw"` case is added before the move-parsing fallthrough:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
case "draw" =>
|
||||||
|
if currentHistory.halfMoveClock >= 100 then
|
||||||
|
currentBoard = Board.initial
|
||||||
|
currentHistory = GameHistory.empty
|
||||||
|
currentTurn = Color.White
|
||||||
|
invoker.clear()
|
||||||
|
notifyObservers(DrawClaimedEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
else
|
||||||
|
notifyObservers(InvalidMoveEvent(
|
||||||
|
currentBoard, currentHistory, currentTurn,
|
||||||
|
"Draw cannot be claimed: the 50-move rule has not been triggered."
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
The game state resets to initial (same pattern as `Checkmate` and `Stalemate`). The command invoker is cleared so undo/redo history does not survive the draw claim.
|
||||||
|
|
||||||
|
### Availability notification in `GameEngine`
|
||||||
|
|
||||||
|
After any move that results in `Moved` or `MovedInCheck`, the engine checks whether the threshold has been crossed:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
if newHistory.halfMoveClock >= 100 then
|
||||||
|
notifyObservers(FiftyMoveRuleAvailableEvent(currentBoard, currentHistory, currentTurn))
|
||||||
|
```
|
||||||
|
|
||||||
|
This fires immediately after the `MoveExecutedEvent` (or `CheckDetectedEvent`) for that move.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 4: FEN Integration
|
||||||
|
|
||||||
|
`FenExporter.gameStateToFen` and `FenParser.parseFen` already handle `halfMoveClock` at the `GameState` level — no changes to those files are needed.
|
||||||
|
|
||||||
|
The bridge between `GameHistory.halfMoveClock` and `GameState.halfMoveClock` is a caller responsibility:
|
||||||
|
|
||||||
|
**FEN export (writing):** When constructing a `GameState` for FEN export, pass `halfMoveClock = history.halfMoveClock`. Since `GameEngine` already exposes `def history: GameHistory`, this works automatically once the field is populated:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
GameState(
|
||||||
|
piecePlacement = FenExporter.boardToFen(engine.board),
|
||||||
|
activeColor = engine.turn,
|
||||||
|
...,
|
||||||
|
halfMoveClock = engine.history.halfMoveClock,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**FEN import (reading):** When loading from a parsed `GameState`, initialise the engine with a `GameHistory` carrying the parsed clock:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val gs = FenParser.parseFen(fenString).get
|
||||||
|
new GameEngine(
|
||||||
|
initialBoard = FenParser.parseBoard(gs.piecePlacement).get,
|
||||||
|
initialHistory = GameHistory(halfMoveClock = gs.halfMoveClock),
|
||||||
|
initialTurn = gs.activeColor
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
A round-trip test is added to `FenExporterTest` / `FenParserTest` verifying that a non-zero clock survives export → import.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 5: PGN Integration
|
||||||
|
|
||||||
|
`PgnExporter.exportGame` currently hardcodes `" *"` as the game termination marker. PGN standard requires the marker to match the `Result` header (`1-0`, `0-1`, `1/2-1/2`, or `*`).
|
||||||
|
|
||||||
|
### Change to `PgnExporter`
|
||||||
|
|
||||||
|
Replace the hardcoded `" *"` with the value from the `Result` header:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val termination = headers.getOrElse("Result", "*")
|
||||||
|
moveLines.mkString(" ") + s" $termination"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Draw claim result
|
||||||
|
|
||||||
|
When `DrawClaimedEvent` is handled by a caller that exports PGN, it should pass:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
Map("Result" -> "1/2-1/2", ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
The move text will then end with `1/2-1/2`, which is correct per PGN standard for a drawn game.
|
||||||
|
|
||||||
|
A test is added to `PgnExporterTest` verifying that `exportGame` with `"Result" -> "1/2-1/2"` produces a move text ending in `1/2-1/2`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 6: Files Changed
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/logic/GameHistory.scala` | Add `halfMoveClock` field; extend `addMove` with `wasPawnMove`/`wasCapture` flags; make base overload private |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala` | Compute and pass flags in `applyNormalMove` and `completePromotion` |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` | Add `FiftyMoveRuleAvailableEvent` and `DrawClaimedEvent` |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` | Handle `"draw"` input; fire `FiftyMoveRuleAvailableEvent` after eligible moves |
|
||||||
|
| `modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala` | Derive termination marker from `Result` header instead of hardcoding `*` |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/logic/GameHistoryTest.scala` | New test suite for clock update rules |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala` | Tests for clock values in `applyNormalMove` and `completePromotion` |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala` | Tests for `"draw"` command and `FiftyMoveRuleAvailableEvent` |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala` | Test for `1/2-1/2` termination marker |
|
||||||
|
| `modules/core/src/test/scala/de/nowchess/chess/notation/FenExporterTest.scala` | Round-trip test: non-zero `halfMoveClock` survives FEN export → import |
|
||||||
|
|
||||||
|
`EnPassantCalculator`, `CastlingRightsCalculator`, `MoveValidator`, `GameRules`, and their tests are **not** touched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 7: Testing
|
||||||
|
|
||||||
|
### `GameHistoryTest`
|
||||||
|
- Clock starts at 0
|
||||||
|
- Clock increments on a normal (non-pawn, non-capture) move
|
||||||
|
- Clock resets to 0 on a pawn move (`wasPawnMove = true`)
|
||||||
|
- Clock resets to 0 on a capture (`wasCapture = true`)
|
||||||
|
- Clock resets to 0 when both flags are true (en passant)
|
||||||
|
- Clock carries correctly across multiple sequential moves
|
||||||
|
|
||||||
|
### `GameControllerTest`
|
||||||
|
- `applyNormalMove` with a non-pawn, non-capture produces `history.halfMoveClock = 1`
|
||||||
|
- `applyNormalMove` with a pawn move produces `history.halfMoveClock = 0`
|
||||||
|
- `applyNormalMove` with a capture produces `history.halfMoveClock = 0`
|
||||||
|
- `completePromotion` always produces `history.halfMoveClock = 0`
|
||||||
|
|
||||||
|
### `GameEngineTest`
|
||||||
|
- `processUserInput("draw")` fires `DrawClaimedEvent` and resets state when `halfMoveClock >= 100`
|
||||||
|
- `processUserInput("draw")` fires `InvalidMoveEvent` when `halfMoveClock < 100`
|
||||||
|
- A successful non-pawn, non-capture move that brings the clock to exactly 100 fires `FiftyMoveRuleAvailableEvent`
|
||||||
|
- A successful move that does not reach 100 does not fire `FiftyMoveRuleAvailableEvent`
|
||||||
|
|
||||||
|
### `PgnExporterTest`
|
||||||
|
- `exportGame` with `"Result" -> "1/2-1/2"` produces move text ending in `1/2-1/2`
|
||||||
|
- `exportGame` with no `Result` header still produces `*` as before (backward-compatible)
|
||||||
|
|
||||||
|
### `FenExporterTest`
|
||||||
|
- Round-trip: a `GameHistory` with `halfMoveClock = 42` exported to FEN and re-parsed yields `halfMoveClock = 42`
|
||||||