Compare commits

..

13 Commits

Author SHA1 Message Date
Janis a386f57c21 fix: IO microservice (#38)
Reviewed-on: #38
2026-04-22 08:36:54 +02:00
TeamCity 3ca2afbb4b ci: bump version with Build-45 2026-04-21 13:49:26 +00:00
Janis b5a2966ada feat: NCS-53 changed IO to MicroService for easier scaling (#37)
Reviewed-on: #37
Reviewed-by: Shahd Lala <shosho996@blackhole.local>
2026-04-21 15:38:58 +02:00
TeamCity 74a4fce0ca ci: bump version with Build-44 2026-04-21 11:13:30 +00:00
Janis 8fc97bde02 refactor: align JSON string formatting in JsonParserTest 2026-04-21 13:02:18 +02:00
shosho996 2d75b2e80e test: NCS-45 IO Test reduction (#32)
Co-authored-by: shahdlala66 <shahd.lala66@gmail.com>
Reviewed-on: #32
Co-authored-by: Shahd Lala <shosho996@blackhole.local>
Co-committed-by: Shahd Lala <shosho996@blackhole.local>
2026-04-21 12:39:19 +02:00
Janis f088c4e9ff feat: NCS-37 Quarkus integration (#35)
Reviewed-on: #35
Reviewed-by: Leon Hermann <lq@blackhole.local>
2026-04-21 12:35:20 +02:00
TeamCity 8a1cf909d4 ci: bump version with Build-43 2026-04-19 20:53:56 +00:00
Janis 33e785d22a feat: NCS-40 Rework Draw System (#34)
Reviewed-on: #34
Reviewed-by: Shahd Lala <shosho996@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2026-04-19 22:44:48 +02:00
TeamCity d16cec176b ci: bump version with Build-42 2026-04-19 14:01:11 +00:00
Janis 8744bee2dd feat: NCS-41 Bot Platform (#33)
Co-authored-by: Janis <janis@nowchess.de>
Reviewed-on: #33
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2026-04-19 15:52:08 +02:00
TeamCity 5f4d33f3ca ci: bump version with Build-41 2026-04-16 16:55:00 +00:00
Janis 767d3051a7 feat: NCS-13 Implement Threefold Repetition (#31)
Build & Test (NowChessSystems) TeamCity build finished
Reviewed-on: #31
2026-04-16 18:49:20 +02:00
281 changed files with 12804 additions and 3430 deletions
+193 -56
View File
@@ -2,8 +2,8 @@
> **Stack:** raw-http | none | unknown | scala
> 0 routes | 0 models | 0 components | 35 lib files | 0 env vars | 0 middleware
> **Token savings:** this file is ~3.700 tokens. Without it, AI exploration would cost ~18.200 tokens. **Saves ~14.500 tokens per conversation.**
> 0 routes | 0 models | 0 components | 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.**
---
@@ -47,19 +47,130 @@
- 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
- function withMove
- _...2 more_
- _...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
@@ -74,15 +185,16 @@
- 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 isPendingPromotion
- function board
- function turn
- function context
- function canUndo
- _...10 more_
- function canRedo
- _...11 more_
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
- function context
- class Observer
@@ -91,8 +203,35 @@
- 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
@@ -114,6 +253,8 @@
- function parseBoard
- function importGameContext
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
- class PgnExporter
- function exportGameContext
@@ -131,32 +272,28 @@
- function allLegalMoves
- function isCheck
- function isCheckmate
- _...4 more_
- _...5 more_
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala`
- class DefaultRules
- function positionOf
- function loop
- function toMoves
- function loop
- `modules/ui/src/main/scala/de/nowchess/ui/Main.scala` — class Main, function main
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala`
- class ChessBoardView
- function updateBoard
- function updateUndoRedoButtons
- function showMessage
- function showPromotionDialog
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala`
- class ChessGUIApp
- class ChessGUILauncher
- function getEngine
- function launch
- `modules/ui/src/main/scala/de/nowchess/ui/gui/GUIObserver.scala` — class GUIObserver
- `modules/ui/src/main/scala/de/nowchess/ui/gui/PieceSprites.scala`
- class PieceSprites
- function loadPieceImage
- class SquareColors
- `modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala` — class TerminalUI, function start
- `modules/ui/src/main/scala/de/nowchess/ui/utils/PieceUnicode.scala` — function unicode
- `modules/ui/src/main/scala/de/nowchess/ui/utils/Renderer.scala` — class Renderer, function render
---
# Config
## Environment Variables
- `STOCKFISH_PATH` **required** — modules/bot/python/nnue.py
---
# Middleware
## custom
- generate — `modules/bot/python/src/generate.py`
---
@@ -164,39 +301,39 @@
## Most Imported Files (change these carefully)
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **9** files
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **9** files
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **8** files
- `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/board/CastlingRights.scala` — imported by **4** files
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **4** files
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **4** files
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **4** files
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` — imported by **4** files
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala` — imported by **2** files
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **2** files
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala` — imported by **2** files
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — imported by **2** files
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — imported by **1** files
- `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/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +23 more
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +16 more
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala` +14 more
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala` +8 more
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala``modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala` +5 more
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala``modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala` +4 more
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala``modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala` +4 more
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala``modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` +3 more
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala``modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +2 more
- `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
---
+5
View File
@@ -0,0 +1,5 @@
# Config
## Environment Variables
- `STOCKFISH_PATH` **required** — modules/bot/python/nnue.py
+29 -29
View File
@@ -2,36 +2,36 @@
## Most Imported Files (change these carefully)
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **9** files
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **9** files
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **8** files
- `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/board/CastlingRights.scala` — imported by **4** files
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **4** files
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **4** files
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **4** files
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` — imported by **4** files
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala` — imported by **2** files
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **2** files
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala` — imported by **2** files
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — imported by **2** files
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — imported by **1** files
- `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/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +23 more
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +16 more
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala` +14 more
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala``modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala` +8 more
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala``modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala` +5 more
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala``modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala` +4 more
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala``modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala` +4 more
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala``modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` +3 more
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala``modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +2 more
- `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
+147 -25
View File
@@ -38,19 +38,130 @@
- 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
- function withMove
- _...2 more_
- _...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
@@ -65,15 +176,16 @@
- 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 isPendingPromotion
- function board
- function turn
- function context
- function canUndo
- _...10 more_
- function canRedo
- _...11 more_
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
- function context
- class Observer
@@ -82,8 +194,35 @@
- 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
@@ -105,6 +244,8 @@
- function parseBoard
- function importGameContext
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
- class PgnExporter
- function exportGameContext
@@ -122,29 +263,10 @@
- function allLegalMoves
- function isCheck
- function isCheckmate
- _...4 more_
- _...5 more_
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala`
- class DefaultRules
- function positionOf
- function loop
- function toMoves
- function loop
- `modules/ui/src/main/scala/de/nowchess/ui/Main.scala` — class Main, function main
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala`
- class ChessBoardView
- function updateBoard
- function updateUndoRedoButtons
- function showMessage
- function showPromotionDialog
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala`
- class ChessGUIApp
- class ChessGUILauncher
- function getEngine
- function launch
- `modules/ui/src/main/scala/de/nowchess/ui/gui/GUIObserver.scala` — class GUIObserver
- `modules/ui/src/main/scala/de/nowchess/ui/gui/PieceSprites.scala`
- class PieceSprites
- function loadPieceImage
- class SquareColors
- `modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala` — class TerminalUI, function start
- `modules/ui/src/main/scala/de/nowchess/ui/utils/PieceUnicode.scala` — function unicode
- `modules/ui/src/main/scala/de/nowchess/ui/utils/Renderer.scala` — class Renderer, function render
+4
View File
@@ -0,0 +1,4 @@
# Middleware
## custom
- generate — `modules/bot/python/src/generate.py`
+198
View File
@@ -0,0 +1,198 @@
<!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>
+87
View File
@@ -0,0 +1,87 @@
name: Build & Push Native Image
on:
push:
branches:
- main
workflow_dispatch:
jobs:
check-actor:
runs-on: ubuntu-latest
outputs:
allowed: ${{ steps.check.outputs.allowed }}
steps:
- id: check
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.actor }}" == "TeamCity" ]]; then
echo "allowed=true" >> "$GITHUB_OUTPUT"
else
echo "allowed=false" >> "$GITHUB_OUTPUT"
fi
build-and-push:
needs: check-actor
if: needs.check-actor.outputs.allowed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
module:
- core
- io
steps:
- uses: actions/checkout@v4
- name: Set up GraalVM
uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm-community'
native-image-job-reports: 'true'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: gradle-${{ runner.os }}-
- name: Build native binary
run: ./gradlew :modules:${{ matrix.module }}:build -Dquarkus.native.enabled=true --no-daemon
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/now-chess/now-chess-systems/${{ matrix.module }}
tags: |
type=sha,prefix=,format=short
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: modules/${{ matrix.module }}/src/main/docker/Dockerfile.native
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.module }}
cache-to: type=gha,mode=max,scope=${{ matrix.module }}
+2
View File
@@ -8,3 +8,5 @@
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
sonarlint.xml
+133
View File
@@ -0,0 +1,133 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<ScalaCodeStyleSettings>
<option name="FORMATTER" value="1" />
</ScalaCodeStyleSettings>
<XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
+1 -1
View File
@@ -1,5 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
+1 -2
View File
@@ -11,11 +11,10 @@
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/modules" />
<option value="$PROJECT_DIR$/modules/api" />
<option value="$PROJECT_DIR$/modules/backcore" />
<option value="$PROJECT_DIR$/modules/bot" />
<option value="$PROJECT_DIR$/modules/core" />
<option value="$PROJECT_DIR$/modules/io" />
<option value="$PROJECT_DIR$/modules/rule" />
<option value="$PROJECT_DIR$/modules/ui" />
</set>
</option>
</GradleProjectSettings>
@@ -0,0 +1,33 @@
<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>
+1 -1
View File
@@ -5,7 +5,7 @@
<option name="deprecationWarnings" value="true" />
<option name="uncheckedWarnings" value="true" />
</profile>
<profile name="Gradle 2" modules="NowChessSystems.modules.backcore.integrationTest,NowChessSystems.modules.backcore.main,NowChessSystems.modules.backcore.native-test,NowChessSystems.modules.backcore.quarkus-generated-sources,NowChessSystems.modules.backcore.quarkus-test-generated-sources,NowChessSystems.modules.backcore.scoverage,NowChessSystems.modules.backcore.test,NowChessSystems.modules.core.main,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.main,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
<profile name="Gradle 2" modules="NowChessSystems.modules.bot.main,NowChessSystems.modules.bot.scoverage,NowChessSystems.modules.bot.test,NowChessSystems.modules.core.integrationTest,NowChessSystems.modules.core.main,NowChessSystems.modules.core.native-test,NowChessSystems.modules.core.quarkus-generated-sources,NowChessSystems.modules.core.quarkus-test-generated-sources,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.integrationTest,NowChessSystems.modules.io.main,NowChessSystems.modules.io.native-test,NowChessSystems.modules.io.quarkus-generated-sources,NowChessSystems.modules.io.quarkus-test-generated-sources,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
<option name="deprecationWarnings" value="true" />
<option name="uncheckedWarnings" value="true" />
<parameters>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ScalaProjectSettings">
<option name="scala3DisclaimerShown" value="true" />
</component>
</project>
+28 -26
View File
@@ -9,8 +9,9 @@ Scala 3.5.1 · Gradle 9
./compile # Compile all modules — always run
./test # Run all tests
./coverage # Check coverage
./lint # Run linters
```
Try to stick to these commands for consistency.
Use consistently.
## Modules
@@ -19,19 +20,20 @@ Try to stick to these commands for consistency.
| `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.
- 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.
- Use `Option` or `Either` for fallible operations; avoid exceptions for control flow.
- Naming: types are PascalCase, functions/values are camelCase.
- `Option`/`Either` for fallible ops. Skip exceptions for control flow.
- Naming: types PascalCase, functions/values camelCase.
## Code Quality
@@ -39,20 +41,23 @@ Try to stick to these commands for consistency.
### 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.
- **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 stateimmutable, 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.
- **Immutable state as primary model:** GameContext (api) holds board, history, player stateimmutable 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.** Never modify tests to pass; modify requirements or code. Update tests only if requirements change.
- **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 this file up to date with any important decisions or conventions.
- Keep file current with decisions + conventions.
---
@@ -60,11 +65,9 @@ Try to stick to these commands for consistency.
### 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.
**Step 2 — Verify:** Read source files from wiki BEFORE coding.
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.**
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)
@@ -72,8 +75,7 @@ Read in order at session start:
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.
`[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
@@ -83,13 +85,13 @@ Or use the codesight MCP server for on-demand queries:
- `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.
Consult codesight context first. Saves ~16.893 tokens/conversation.
## graphify
This project has a graphify knowledge graph at graphify-out/.
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
- 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.
+100
View File
@@ -0,0 +1,100 @@
# 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
./lint # Run linters
```
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
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Offer Draw
type: http
seq: 1
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/offer
body: none
auth: none
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Accept Draw
type: http
seq: 2
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/accept
body: none
auth: none
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Decline Draw
type: http
seq: 3
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/decline
body: none
auth: none
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Claim Draw
type: http
seq: 4
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/claim
body: none
auth: none
}
+4
View File
@@ -0,0 +1,4 @@
meta {
name: draw
seq: 2
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Export FEN
type: http
seq: 1
}
http {
method: GET
url: {{baseUrl}}/api/board/game/{{gameId}}/export/fen
body: none
auth: none
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Export PGN
type: http
seq: 2
}
http {
method: GET
url: {{baseUrl}}/api/board/game/{{gameId}}/export/pgn
body: none
auth: none
}
+4
View File
@@ -0,0 +1,4 @@
meta {
name: export
seq: 6
}
+23
View File
@@ -0,0 +1,23 @@
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"}
}
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Get Game
type: http
seq: 2
}
http {
method: GET
url: {{baseUrl}}/api/board/game/{{gameId}}
body: none
auth: none
}
+19
View File
@@ -0,0 +1,19 @@
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
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Resign
type: http
seq: 4
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/resign
body: none
auth: none
}
+4
View File
@@ -0,0 +1,4 @@
meta {
name: game
seq: 3
}
+24
View File
@@ -0,0 +1,24 @@
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"}
}
}
+22
View File
@@ -0,0 +1,22 @@
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 *"
}
}
+4
View File
@@ -0,0 +1,4 @@
meta {
name: import
seq: 5
}
+15
View File
@@ -0,0 +1,15 @@
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
}
+19
View File
@@ -0,0 +1,19 @@
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
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Undo Move
type: http
seq: 3
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/undo
body: none
auth: none
}
+12
View File
@@ -0,0 +1,12 @@
meta {
name: Redo Move
type: http
seq: 4
}
http {
method: POST
url: {{baseUrl}}/api/board/game/{{gameId}}/redo
body: none
auth: none
}
+3
View File
@@ -0,0 +1,3 @@
meta {
name: move
}
+6
View File
@@ -0,0 +1,6 @@
{
"version": "1",
"name": "NowChess API",
"type": "collection",
"ignore": []
}
View File
+4
View File
@@ -0,0 +1,4 @@
vars {
baseUrl: http://localhost:8080
ioBaseUrl: http://localhost:8081
}
+100
View File
@@ -0,0 +1,100 @@
meta {
name: Export FEN
type: http
seq: 1
}
http {
method: POST
url: {{ioBaseUrl}}/io/export/fen
body: json
auth: none
}
headers {
Content-Type: application/json
}
body:json {
{
"board": {
"a1": {"color": "White", "pieceType": "Rook"},
"b1": {"color": "White", "pieceType": "Knight"},
"c1": {"color": "White", "pieceType": "Bishop"},
"d1": {"color": "White", "pieceType": "Queen"},
"e1": {"color": "White", "pieceType": "King"},
"f1": {"color": "White", "pieceType": "Bishop"},
"g1": {"color": "White", "pieceType": "Knight"},
"h1": {"color": "White", "pieceType": "Rook"},
"a2": {"color": "White", "pieceType": "Pawn"},
"b2": {"color": "White", "pieceType": "Pawn"},
"c2": {"color": "White", "pieceType": "Pawn"},
"d2": {"color": "White", "pieceType": "Pawn"},
"e2": {"color": "White", "pieceType": "Pawn"},
"f2": {"color": "White", "pieceType": "Pawn"},
"g2": {"color": "White", "pieceType": "Pawn"},
"h2": {"color": "White", "pieceType": "Pawn"},
"a7": {"color": "Black", "pieceType": "Pawn"},
"b7": {"color": "Black", "pieceType": "Pawn"},
"c7": {"color": "Black", "pieceType": "Pawn"},
"d7": {"color": "Black", "pieceType": "Pawn"},
"e7": {"color": "Black", "pieceType": "Pawn"},
"f7": {"color": "Black", "pieceType": "Pawn"},
"g7": {"color": "Black", "pieceType": "Pawn"},
"h7": {"color": "Black", "pieceType": "Pawn"},
"a8": {"color": "Black", "pieceType": "Rook"},
"b8": {"color": "Black", "pieceType": "Knight"},
"c8": {"color": "Black", "pieceType": "Bishop"},
"d8": {"color": "Black", "pieceType": "Queen"},
"e8": {"color": "Black", "pieceType": "King"},
"f8": {"color": "Black", "pieceType": "Bishop"},
"g8": {"color": "Black", "pieceType": "Knight"},
"h8": {"color": "Black", "pieceType": "Rook"}
},
"turn": "White",
"castlingRights": {
"whiteKingSide": true,
"whiteQueenSide": true,
"blackKingSide": true,
"blackQueenSide": true
},
"enPassantSquare": null,
"halfMoveClock": 0,
"moves": [],
"result": null,
"initialBoard": {
"a1": {"color": "White", "pieceType": "Rook"},
"b1": {"color": "White", "pieceType": "Knight"},
"c1": {"color": "White", "pieceType": "Bishop"},
"d1": {"color": "White", "pieceType": "Queen"},
"e1": {"color": "White", "pieceType": "King"},
"f1": {"color": "White", "pieceType": "Bishop"},
"g1": {"color": "White", "pieceType": "Knight"},
"h1": {"color": "White", "pieceType": "Rook"},
"a2": {"color": "White", "pieceType": "Pawn"},
"b2": {"color": "White", "pieceType": "Pawn"},
"c2": {"color": "White", "pieceType": "Pawn"},
"d2": {"color": "White", "pieceType": "Pawn"},
"e2": {"color": "White", "pieceType": "Pawn"},
"f2": {"color": "White", "pieceType": "Pawn"},
"g2": {"color": "White", "pieceType": "Pawn"},
"h2": {"color": "White", "pieceType": "Pawn"},
"a7": {"color": "Black", "pieceType": "Pawn"},
"b7": {"color": "Black", "pieceType": "Pawn"},
"c7": {"color": "Black", "pieceType": "Pawn"},
"d7": {"color": "Black", "pieceType": "Pawn"},
"e7": {"color": "Black", "pieceType": "Pawn"},
"f7": {"color": "Black", "pieceType": "Pawn"},
"g7": {"color": "Black", "pieceType": "Pawn"},
"h7": {"color": "Black", "pieceType": "Pawn"},
"a8": {"color": "Black", "pieceType": "Rook"},
"b8": {"color": "Black", "pieceType": "Knight"},
"c8": {"color": "Black", "pieceType": "Bishop"},
"d8": {"color": "Black", "pieceType": "Queen"},
"e8": {"color": "Black", "pieceType": "King"},
"f8": {"color": "Black", "pieceType": "Bishop"},
"g8": {"color": "Black", "pieceType": "Knight"},
"h8": {"color": "Black", "pieceType": "Rook"}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
meta {
name: Export PGN
type: http
seq: 2
}
http {
method: POST
url: {{ioBaseUrl}}/io/export/pgn
body: json
auth: none
}
headers {
Content-Type: application/json
}
body:json {
{
"board": {
"a1": {"color": "White", "pieceType": "Rook"},
"b1": {"color": "White", "pieceType": "Knight"},
"c1": {"color": "White", "pieceType": "Bishop"},
"d1": {"color": "White", "pieceType": "Queen"},
"e1": {"color": "White", "pieceType": "King"},
"f1": {"color": "White", "pieceType": "Bishop"},
"g1": {"color": "White", "pieceType": "Knight"},
"h1": {"color": "White", "pieceType": "Rook"},
"a2": {"color": "White", "pieceType": "Pawn"},
"b2": {"color": "White", "pieceType": "Pawn"},
"c2": {"color": "White", "pieceType": "Pawn"},
"d2": {"color": "White", "pieceType": "Pawn"},
"e2": {"color": "White", "pieceType": "Pawn"},
"f2": {"color": "White", "pieceType": "Pawn"},
"g2": {"color": "White", "pieceType": "Pawn"},
"h2": {"color": "White", "pieceType": "Pawn"},
"a7": {"color": "Black", "pieceType": "Pawn"},
"b7": {"color": "Black", "pieceType": "Pawn"},
"c7": {"color": "Black", "pieceType": "Pawn"},
"d7": {"color": "Black", "pieceType": "Pawn"},
"e7": {"color": "Black", "pieceType": "Pawn"},
"f7": {"color": "Black", "pieceType": "Pawn"},
"g7": {"color": "Black", "pieceType": "Pawn"},
"h7": {"color": "Black", "pieceType": "Pawn"},
"a8": {"color": "Black", "pieceType": "Rook"},
"b8": {"color": "Black", "pieceType": "Knight"},
"c8": {"color": "Black", "pieceType": "Bishop"},
"d8": {"color": "Black", "pieceType": "Queen"},
"e8": {"color": "Black", "pieceType": "King"},
"f8": {"color": "Black", "pieceType": "Bishop"},
"g8": {"color": "Black", "pieceType": "Knight"},
"h8": {"color": "Black", "pieceType": "Rook"}
},
"turn": "White",
"castlingRights": {
"whiteKingSide": true,
"whiteQueenSide": true,
"blackKingSide": true,
"blackQueenSide": true
},
"enPassantSquare": null,
"halfMoveClock": 0,
"moves": [],
"result": null,
"initialBoard": {
"a1": {"color": "White", "pieceType": "Rook"},
"b1": {"color": "White", "pieceType": "Knight"},
"c1": {"color": "White", "pieceType": "Bishop"},
"d1": {"color": "White", "pieceType": "Queen"},
"e1": {"color": "White", "pieceType": "King"},
"f1": {"color": "White", "pieceType": "Bishop"},
"g1": {"color": "White", "pieceType": "Knight"},
"h1": {"color": "White", "pieceType": "Rook"},
"a2": {"color": "White", "pieceType": "Pawn"},
"b2": {"color": "White", "pieceType": "Pawn"},
"c2": {"color": "White", "pieceType": "Pawn"},
"d2": {"color": "White", "pieceType": "Pawn"},
"e2": {"color": "White", "pieceType": "Pawn"},
"f2": {"color": "White", "pieceType": "Pawn"},
"g2": {"color": "White", "pieceType": "Pawn"},
"h2": {"color": "White", "pieceType": "Pawn"},
"a7": {"color": "Black", "pieceType": "Pawn"},
"b7": {"color": "Black", "pieceType": "Pawn"},
"c7": {"color": "Black", "pieceType": "Pawn"},
"d7": {"color": "Black", "pieceType": "Pawn"},
"e7": {"color": "Black", "pieceType": "Pawn"},
"f7": {"color": "Black", "pieceType": "Pawn"},
"g7": {"color": "Black", "pieceType": "Pawn"},
"h7": {"color": "Black", "pieceType": "Pawn"},
"a8": {"color": "Black", "pieceType": "Rook"},
"b8": {"color": "Black", "pieceType": "Knight"},
"c8": {"color": "Black", "pieceType": "Bishop"},
"d8": {"color": "Black", "pieceType": "Queen"},
"e8": {"color": "Black", "pieceType": "King"},
"f8": {"color": "Black", "pieceType": "Bishop"},
"g8": {"color": "Black", "pieceType": "Knight"},
"h8": {"color": "Black", "pieceType": "Rook"}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
meta {
name: export
seq: 2
}
+22
View File
@@ -0,0 +1,22 @@
meta {
name: Import FEN
type: http
seq: 1
}
http {
method: POST
url: {{ioBaseUrl}}/io/import/fen
body: json
auth: none
}
headers {
Content-Type: application/json
}
body:json {
{
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
}
}
+22
View File
@@ -0,0 +1,22 @@
meta {
name: Import PGN
type: http
seq: 2
}
http {
method: POST
url: {{ioBaseUrl}}/io/import/pgn
body: json
auth: none
}
headers {
Content-Type: application/json
}
body:json {
{
"pgn": "1. e4 e5 2. Nf3 Nc6 *"
}
}
+4
View File
@@ -0,0 +1,4 @@
meta {
name: import
seq: 1
}
+50 -9
View File
@@ -8,6 +8,53 @@ plugins {
group = "de.nowchess"
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",
// IoResource — same rationale as GameResource; @QuarkusTest not instrumented by Scoverage
"**/io/src/main/scala/de/nowchess/io/service/resource/IoResource.scala",
// JacksonConfig — Quarkus lifecycle hook, no testable logic beyond ObjectMapper registration
"**/io/src/main/scala/de/nowchess/io/service/config/JacksonConfig.scala",
)
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
// 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 {
properties {
property("sonar.projectKey", "Now-Chess-Systems")
@@ -21,28 +68,22 @@ sonar {
if (report.exists()) report.absolutePath else null
}.joinToString(",")
val jacocoReports = subprojects.mapNotNull { subproject ->
val report = subproject.file("build/reports/jacoco/test/jacocoTestReport.xml")
if (report.exists()) report.absolutePath else null
}.joinToString(",")
property("sonar.scala.coverage.reportPaths", scoverageReports)
if (jacocoReports.isNotEmpty()) {
property("sonar.coverage.jacoco.xmlReportPaths", jacocoReports)
}
property("sonar.coverage.exclusions", coverageExclusions.joinToString(","))
}
}
val versions = mapOf(
"QUARKUS_SCALA3" to "1.0.0",
"SCALA3" to "3.5.1",
"SCALA_LIBRARY" to "2.13.18",
"SCALA_LIBRARY" to "2.13.16",
"SCALATEST" to "3.2.19",
"SCALATEST_JUNIT" to "0.1.11",
"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",
Regular → Executable
View File
@@ -1,6 +1,6 @@
openapi: 3.0.3
info:
title: NowChess API
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).
@@ -186,11 +186,8 @@ paths:
currently to move.
For promotion moves include the target piece as the fifth character:
`e7e8q`, `a2a1r`, etc.
If the move results in a pawn reaching the back rank and no promotion
character is supplied, the game enters `promotionPending` status and
the move is not yet applied — resubmit with the promotion character.
`e7e8q`, `a2a1r`, etc. Promotion moves without the fifth character
are rejected with `400 INVALID_MOVE`.
security:
- bearerAuth: []
parameters:
@@ -630,7 +627,6 @@ components:
| `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 |
| `promotionPending` | A pawn reached the back rank; awaiting promotion piece selection |
| `insufficientMaterial` | Neither side has enough pieces to deliver checkmate — game over (draw) |
enum:
- started
@@ -641,7 +637,6 @@ components:
- draw
- drawOffered
- fiftyMoveAvailable
- promotionPending
- insufficientMaterial
# -------------------------------------------------------------------------
+1
View File
@@ -1,3 +1,4 @@
# Gradle properties
quarkusPluginId=io.quarkus
quarkusPluginVersion=3.32.4
quarkusPlatformGroupId=io.quarkus.platform
+1 -1
View File
@@ -1,5 +1,5 @@
import glob,re
mods=['api','core','io','rule','ui']
mods=['api','core','io','rule','ui', 'bot']
tot=0
for m in mods:
s=0
Executable
+3
View File
@@ -0,0 +1,3 @@
#! /usr/bin/env bash
./gradlew scalafix spotlessCheck
+46
View File
@@ -34,3 +34,49 @@
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
## (2026-04-16)
### Features
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
## (2026-04-19)
### Features
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
## (2026-04-19)
### Features
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
## (2026-04-21)
### Features
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
## (2026-04-21)
### Features
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
+4 -1
View File
@@ -8,6 +8,8 @@ version = "1.0-SNAPSHOT"
@Suppress("UNCHECKED_CAST")
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
@Suppress("UNCHECKED_CAST")
val scoverageExcluded = rootProject.extra["SCOVERAGE_EXCLUDED"] as List<String>
repositories {
mavenCentral()
@@ -19,6 +21,7 @@ scala {
scoverage {
scoverageVersion.set(versions["SCOVERAGE"]!!)
excludedFiles.set(scoverageExcluded)
}
configurations.scoverage {
@@ -31,7 +34,7 @@ configurations.scoverage {
dependencies {
implementation("org.scala-lang:scala3-compiler_3") {
compileOnly("org.scala-lang:scala3-compiler_3") {
version {
strictly(versions["SCALA3"]!!)
}
@@ -0,0 +1,11 @@
package de.nowchess.api.bot
import de.nowchess.api.game.GameContext
import de.nowchess.api.move.Move
trait Bot {
def name: String
def nextMove(context: GameContext): Option[Move]
}
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
final case class ApiErrorDto(code: String, message: String, field: Option[String])
@@ -0,0 +1,6 @@
package de.nowchess.api.dto
final case class CreateGameRequestDto(
white: Option[PlayerInfoDto],
black: Option[PlayerInfoDto],
)
@@ -0,0 +1,6 @@
package de.nowchess.api.dto
final case class ErrorEventDto(`type`: String, error: ApiErrorDto)
object ErrorEventDto:
def apply(error: ApiErrorDto): ErrorEventDto = ErrorEventDto("error", error)
@@ -0,0 +1,8 @@
package de.nowchess.api.dto
final case class GameFullDto(
gameId: String,
white: PlayerInfoDto,
black: PlayerInfoDto,
state: GameStateDto,
)
@@ -0,0 +1,6 @@
package de.nowchess.api.dto
final case class GameFullEventDto(`type`: String, game: GameFullDto)
object GameFullEventDto:
def apply(game: GameFullDto): GameFullEventDto = GameFullEventDto("gameFull", game)
@@ -0,0 +1,12 @@
package de.nowchess.api.dto
final case class GameStateDto(
fen: String,
pgn: String,
turn: String,
status: String,
winner: Option[String],
moves: List[String],
undoAvailable: Boolean,
redoAvailable: Boolean,
)
@@ -0,0 +1,6 @@
package de.nowchess.api.dto
final case class GameStateEventDto(`type`: String, state: GameStateDto)
object GameStateEventDto:
def apply(state: GameStateDto): GameStateEventDto = GameStateEventDto("gameState", state)
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
case class ImportFenRequest(fen: String)
@@ -0,0 +1,7 @@
package de.nowchess.api.dto
final case class ImportFenRequestDto(
fen: String,
white: Option[PlayerInfoDto],
black: Option[PlayerInfoDto],
)
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
case class ImportPgnRequest(pgn: String)
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
final case class ImportPgnRequestDto(pgn: String)
@@ -0,0 +1,9 @@
package de.nowchess.api.dto
final case class LegalMoveDto(
from: String,
to: String,
uci: String,
moveType: String,
promotion: Option[String],
)
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
final case class LegalMovesResponseDto(moves: List[LegalMoveDto])
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
final case class OkResponseDto(ok: Boolean = true)
@@ -0,0 +1,3 @@
package de.nowchess.api.dto
final case class PlayerInfoDto(id: String, displayName: String)
@@ -5,4 +5,5 @@ enum DrawReason:
case Stalemate
case InsufficientMaterial
case FiftyMoveRule
case ThreefoldRepetition
case Agreement
@@ -1,6 +1,6 @@
package de.nowchess.api.game
import de.nowchess.api.board.{Board, CastlingRights, Color, Square}
import de.nowchess.api.board.{Board, CastlingRights, Color, PieceType, Square}
import de.nowchess.api.move.Move
/** Immutable bundle of complete game state. All state changes produce new GameContext instances.
@@ -13,7 +13,15 @@ case class GameContext(
halfMoveClock: Int,
moves: List[Move],
result: Option[GameResult] = None,
initialBoard: Board = Board.initial,
):
private lazy val whiteKingSquare: Option[Square] =
board.pieces.find((_, p) => p.color == Color.White && p.pieceType == PieceType.King).map(_._1)
private lazy val blackKingSquare: Option[Square] =
board.pieces.find((_, p) => p.color == Color.Black && p.pieceType == PieceType.King).map(_._1)
def kingSquare(color: Color): Option[Square] =
if color == Color.White then whiteKingSquare else blackKingSquare
/** Create new context with updated board. */
def withBoard(newBoard: Board): GameContext = copy(board = newBoard)
@@ -0,0 +1,8 @@
package de.nowchess.api.game
import de.nowchess.api.bot.Bot
import de.nowchess.api.player.PlayerInfo
sealed trait Participant
final case class Human(playerInfo: PlayerInfo) extends Participant
final case class BotParticipant(bot: Bot) extends Participant
@@ -1,7 +1,6 @@
package de.nowchess.io
package de.nowchess.api.io
import de.nowchess.api.game.GameContext
trait GameContextExport:
def exportGameContext(context: GameContext): String
@@ -1,7 +1,6 @@
package de.nowchess.io
package de.nowchess.api.io
import de.nowchess.api.game.GameContext
trait GameContextImport:
def importGameContext(input: String): Either[String, GameContext]
@@ -71,3 +71,9 @@ class GameContextTest extends AnyFunSuite with Matchers:
test("withResult clears result"):
val ctx = GameContext.initial.withResult(Some(GameResult.Win(Color.Black)))
ctx.withResult(None).result shouldBe None
test("kingSquare returns white king position"):
GameContext.initial.kingSquare(Color.White) shouldBe Some(Square(File.E, Rank.R1))
test("kingSquare returns black king position"):
GameContext.initial.kingSquare(Color.Black) shouldBe Some(Square(File.E, Rank.R8))
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR=0
MINOR=5
MINOR=10
PATCH=0
-101
View File
@@ -1,101 +0,0 @@
plugins {
id("scala")
id("org.scoverage") version "8.1"
id("io.quarkus")
id("jacoco")
}
@Suppress("UNCHECKED_CAST")
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
repositories {
mavenCentral()
mavenLocal()
}
scala {
scalaVersion = versions["SCALA3"]!!
}
scoverage {
scoverageVersion.set(versions["SCOVERAGE"]!!)
}
tasks.withType<ScalaCompile> {
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
}
val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project
dependencies {
implementation(project(":modules:api"))
implementation(project(":modules:core"))
implementation(project(":modules:io"))
implementation(project(":modules:rule"))
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
implementation("io.quarkus:quarkus-rest")
implementation("io.quarkus:quarkus-rest-jackson")
implementation("io.quarkus:quarkus-config-yaml")
implementation("io.quarkus:quarkus-arc")
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
testImplementation("io.quarkus:quarkus-junit5")
testImplementation("io.quarkus:quarkus-jacoco")
testImplementation("io.rest-assured:rest-assured")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
}
configurations.scoverage {
resolutionStrategy.eachDependency {
if (requested.group == "org.scoverage" && requested.name.startsWith("scalac-scoverage-plugin_")) {
useTarget("${requested.group}:scalac-scoverage-plugin_2.13.16:2.3.0")
}
}
}
group = "de.nowchess"
version = "1.0-SNAPSHOT"
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
tasks.withType<Jar>().configureEach {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
tasks.test {
useJUnitPlatform {
includeEngines("scalatest", "junit-jupiter")
}
testLogging {
events("passed", "skipped", "failed")
}
finalizedBy(tasks.named("jacocoTestReport"))
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
executionData.setFrom(layout.buildDirectory.file("jacoco-quarkus.exec"))
sourceDirectories.setFrom(files("src/main/scala"))
classDirectories.setFrom(files(layout.buildDirectory.dir("classes/scala/main")))
reports {
xml.required.set(true)
xml.outputLocation.set(
layout.buildDirectory.file("reports/jacoco/test/jacocoTestReport.xml")
)
html.required.set(false)
}
}
@@ -1,6 +0,0 @@
quarkus:
http:
port: 8080
jacoco:
data-file: ${user.dir}/build/jacoco-quarkus.exec
report: false
@@ -1,11 +0,0 @@
package de.nowchess.backcore.config
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import io.quarkus.jackson.ObjectMapperCustomizer
import jakarta.inject.Singleton
@Singleton
class JacksonConfig extends ObjectMapperCustomizer:
def customize(mapper: ObjectMapper): Unit =
mapper.registerModule(DefaultScalaModule)
@@ -1,57 +0,0 @@
package de.nowchess.backcore.dto
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include
case class PlayerInfoDto(id: String, displayName: String)
case class GameStateResponse(
fen: String,
pgn: String,
turn: String,
status: String,
@JsonInclude(Include.NON_ABSENT) winner: Option[String],
moves: List[String],
undoAvailable: Boolean,
redoAvailable: Boolean,
)
case class GameFullResponse(
gameId: String,
white: PlayerInfoDto,
black: PlayerInfoDto,
state: GameStateResponse,
)
case class OkResponse(ok: Boolean = true)
@JsonInclude(Include.NON_ABSENT)
case class ApiErrorResponse(
code: String,
message: String,
field: Option[String] = None,
)
// Requests
case class CreateGameRequest(
white: Option[PlayerInfoDto] = None,
black: Option[PlayerInfoDto] = None,
)
case class ImportFenRequest(
fen: String = "",
white: Option[PlayerInfoDto] = None,
black: Option[PlayerInfoDto] = None,
)
case class ImportPgnRequest(pgn: String = "")
case class LegalMoveDto(
from: String,
to: String,
uci: String,
moveType: String,
@JsonInclude(Include.NON_ABSENT) promotion: Option[String] = None,
)
case class LegalMovesResponse(moves: List[LegalMoveDto])
@@ -1,10 +0,0 @@
package de.nowchess.backcore.game
import java.security.SecureRandom
object GameId:
private val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
private val random = SecureRandom()
def generate(): String =
(1 to 8).map(_ => chars(random.nextInt(chars.length))).mkString
@@ -1,80 +0,0 @@
package de.nowchess.backcore.game
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import de.nowchess.api.board.Color
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
import de.nowchess.backcore.dto.*
import de.nowchess.io.fen.FenExporter
import de.nowchess.io.pgn.PgnExporter
import de.nowchess.rules.sets.DefaultRules
object GameMapper:
private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
def toGameFullJson(session: GameSession): String =
mapper.writeValueAsString(toGameFull(session))
def toGameFull(session: GameSession): GameFullResponse =
GameFullResponse(
gameId = session.gameId,
white = toPlayerInfo(session.white),
black = toPlayerInfo(session.black),
state = toGameState(session),
)
def toGameState(session: GameSession): GameStateResponse =
val (status, winner) = computeStatus(session)
GameStateResponse(
fen = FenExporter.exportGameContext(session.context),
pgn = buildPgn(session.context.moves),
turn = if session.context.turn == Color.White then "white" else "black",
status = status,
winner = winner,
moves = session.context.moves.map(moveToUci),
undoAvailable = session.invoker.canUndo,
redoAvailable = session.invoker.canRedo,
)
private def toPlayerInfo(p: de.nowchess.api.player.PlayerInfo): PlayerInfoDto =
PlayerInfoDto(id = p.id.value, displayName = p.displayName)
private def computeStatus(session: GameSession): (String, Option[String]) =
session.result match
case Some(GameResult.Checkmate(winner)) =>
val w = if winner == Color.White then "white" else "black"
("checkmate", Some(w))
case Some(GameResult.Stalemate) =>
("stalemate", None)
case Some(GameResult.Resign(winner)) =>
val w = if winner == Color.White then "white" else "black"
("resign", Some(w))
case Some(GameResult.AgreedDraw) | Some(GameResult.FiftyMoveDraw) =>
("draw", None)
case Some(GameResult.InsufficientMaterial) =>
("insufficientMaterial", None)
case None =>
computeLiveStatus(session)
private def computeLiveStatus(session: GameSession): (String, Option[String]) =
val ctx = session.context
if DefaultRules.isCheck(ctx) then ("check", None)
else if session.drawOfferedBy.isDefined then ("drawOffered", None)
else if DefaultRules.isFiftyMoveRule(ctx) then ("fiftyMoveAvailable", None)
else ("started", None)
def moveToUci(move: Move): String =
val base = s"${move.from}${move.to}"
move.moveType match
case MoveType.Promotion(piece) =>
val suffix = piece match
case PromotionPiece.Queen => "q"
case PromotionPiece.Rook => "r"
case PromotionPiece.Bishop => "b"
case PromotionPiece.Knight => "n"
base + suffix
case _ => base
private def buildPgn(moves: List[Move]): String =
// Use PgnExporter with no headers to get move-text only (SAN notation)
PgnExporter.exportGame(Map.empty, moves)
@@ -1,12 +0,0 @@
package de.nowchess.backcore.game
import de.nowchess.api.board.Color
sealed trait GameResult
object GameResult:
case class Checkmate(winner: Color) extends GameResult
case object Stalemate extends GameResult
case class Resign(winner: Color) extends GameResult
case object AgreedDraw extends GameResult
case object FiftyMoveDraw extends GameResult
case object InsufficientMaterial extends GameResult
@@ -1,16 +0,0 @@
package de.nowchess.backcore.game
import de.nowchess.api.board.Color
import de.nowchess.api.game.GameContext
import de.nowchess.api.player.PlayerInfo
import de.nowchess.chess.command.CommandInvoker
case class GameSession(
gameId: String,
white: PlayerInfo,
black: PlayerInfo,
context: GameContext,
invoker: CommandInvoker,
drawOfferedBy: Option[Color] = None,
result: Option[GameResult] = None,
)
@@ -1,260 +0,0 @@
package de.nowchess.backcore.game
import de.nowchess.api.board.{Color, Square}
import de.nowchess.api.game.GameContext
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
import de.nowchess.api.player.{PlayerId, PlayerInfo}
import de.nowchess.backcore.dto.{CreateGameRequest, ImportFenRequest, PlayerInfoDto}
import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult}
import de.nowchess.io.fen.FenParser
import de.nowchess.io.pgn.PgnParser
import de.nowchess.rules.sets.DefaultRules
import jakarta.enterprise.context.ApplicationScoped
import scala.collection.mutable
@ApplicationScoped
class GameStore:
private val games: mutable.Map[String, GameSession] = mutable.Map.empty
// ─── Create / Get ────────────────────────────────────────────────
def create(req: CreateGameRequest): GameSession = synchronized:
val id = generateId()
val session = newSession(id, req.white, req.black, GameContext.initial)
games(id) = session
session
def get(id: String): Option[GameSession] = synchronized:
games.get(id)
// ─── Move-making ─────────────────────────────────────────────────
def applyMove(id: String, uci: String): Either[String, GameSession] = synchronized:
withSession(id): session =>
if session.result.isDefined then Left("Game is already over")
else
parseUci(uci) match
case None => Left(s"Invalid UCI notation: $uci")
case Some((from, to, promotion)) =>
val legalCandidates = DefaultRules.legalMoves(session.context)(from)
findMatchingMove(legalCandidates, to, promotion) match
case None => Left(s"$uci is not a legal move")
case Some(move) =>
val nextCtx = DefaultRules.applyMove(session.context)(move)
val prevCtx = session.context
val cmd = MoveCommand(
from = move.from,
to = move.to,
moveResult = Some(MoveResult.Successful(nextCtx, prevCtx.board.pieceAt(move.to))),
previousContext = Some(prevCtx),
)
session.invoker.execute(cmd)
val result = detectGameOver(nextCtx)
val updated = session.copy(context = nextCtx, result = result)
games(id) = updated
Right(updated)
def legalMoves(id: String, square: Option[Square]): Either[String, List[Move]] = synchronized:
withSession(id): session =>
val moves = square match
case Some(sq) => DefaultRules.legalMoves(session.context)(sq)
case None => DefaultRules.allLegalMoves(session.context)
Right(moves)
// ─── Undo / Redo ─────────────────────────────────────────────────
def undo(id: String): Either[String, GameSession] = synchronized:
withSession(id): session =>
if !session.invoker.canUndo then Left("No moves to undo")
else
val idx = session.invoker.getCurrentIndex
session.invoker.history(idx) match
case cmd: MoveCommand =>
cmd.previousContext match
case None => Left("Cannot undo: no previous context stored")
case Some(prevCtx) =>
session.invoker.undo()
val updated = session.copy(context = prevCtx, result = None, drawOfferedBy = None)
games(id) = updated
Right(updated)
case _ => Left("Cannot undo this command type")
def redo(id: String): Either[String, GameSession] = synchronized:
withSession(id): session =>
if !session.invoker.canRedo then Left("No moves to redo")
else
val idx = session.invoker.getCurrentIndex + 1
session.invoker.history(idx) match
case cmd: MoveCommand =>
cmd.moveResult match
case Some(MoveResult.Successful(nextCtx, _)) =>
session.invoker.redo()
val result = detectGameOver(nextCtx)
val updated = session.copy(context = nextCtx, result = result)
games(id) = updated
Right(updated)
case _ => Left("Cannot redo: move result not available")
case _ => Left("Cannot redo this command type")
// ─── Resign ──────────────────────────────────────────────────────
def resign(id: String): Either[String, GameSession] = synchronized:
withSession(id): session =>
if session.result.isDefined then Left("Game is already over")
else
val winner = session.context.turn.opposite
val updated = session.copy(result = Some(GameResult.Resign(winner)))
games(id) = updated
Right(updated)
// ─── Draw actions ────────────────────────────────────────────────
def drawAction(id: String, action: String): Either[String, GameSession] = synchronized:
withSession(id): session =>
if session.result.isDefined then Left("Game is already over")
else
action match
case "offer" =>
val updated = session.copy(drawOfferedBy = Some(session.context.turn))
games(id) = updated
Right(updated)
case "accept" =>
session.drawOfferedBy match
case None => Left("No draw offer to accept")
case Some(offerer) if offerer == session.context.turn =>
Left("Cannot accept your own draw offer")
case Some(_) =>
val updated = session.copy(result = Some(GameResult.AgreedDraw), drawOfferedBy = None)
games(id) = updated
Right(updated)
case "decline" =>
session.drawOfferedBy match
case None => Left("No draw offer to decline")
case Some(_) =>
val updated = session.copy(drawOfferedBy = None)
games(id) = updated
Right(updated)
case "claim" =>
if DefaultRules.isFiftyMoveRule(session.context) then
val updated = session.copy(result = Some(GameResult.FiftyMoveDraw))
games(id) = updated
Right(updated)
else Left("Fifty-move rule has not been triggered")
case other => Left(s"Unknown draw action: $other")
// ─── Import ──────────────────────────────────────────────────────
def importFen(req: ImportFenRequest): Either[String, GameSession] = synchronized:
FenParser.parseFen(req.fen) match
case Left(err) => Left(err)
case Right(ctx) =>
val id = generateId()
val session = newSession(id, req.white, req.black, ctx)
games(id) = session
Right(session)
def importPgn(pgn: String, white: Option[PlayerInfoDto], black: Option[PlayerInfoDto]): Either[String, GameSession] =
synchronized:
PgnParser.validatePgn(pgn) match
case Left(err) => Left(err)
case Right(game) =>
val id = generateId()
val session = newSession(id, white, black, GameContext.initial)
replayIntoSession(session, game.moves, GameContext.initial) match
case Left(err) => Left(err)
case Right(s) =>
games(id) = s
Right(s)
// ─── Private helpers ─────────────────────────────────────────────
private def withSession[A](id: String)(f: GameSession => Either[String, A]): Either[String, A] =
games.get(id) match
case None => Left(s"Game $id not found")
case Some(session) => f(session)
private def generateId(): String =
var id = GameId.generate()
while games.contains(id) do id = GameId.generate()
id
private def newSession(
id: String,
white: Option[PlayerInfoDto],
black: Option[PlayerInfoDto],
ctx: GameContext,
): GameSession =
GameSession(
gameId = id,
white = toPlayerInfo(white, "white", "White"),
black = toPlayerInfo(black, "black", "Black"),
context = ctx,
invoker = new CommandInvoker(),
)
private def toPlayerInfo(dto: Option[PlayerInfoDto], defaultId: String, defaultName: String): PlayerInfo =
dto.fold(PlayerInfo(PlayerId(defaultId), defaultName))(d => PlayerInfo(PlayerId(d.id), d.displayName))
private def parseUci(uci: String): Option[(Square, Square, Option[PromotionPiece])] =
if uci.length < 4 || uci.length > 5 then None
else
for
from <- Square.fromAlgebraic(uci.substring(0, 2))
to <- Square.fromAlgebraic(uci.substring(2, 4))
yield
val promotion = if uci.length == 5 then parsePromotionChar(uci.charAt(4)) else None
(from, to, promotion)
private def parsePromotionChar(c: Char): Option[PromotionPiece] =
c match
case 'q' => Some(PromotionPiece.Queen)
case 'r' => Some(PromotionPiece.Rook)
case 'b' => Some(PromotionPiece.Bishop)
case 'n' => Some(PromotionPiece.Knight)
case _ => None
private def findMatchingMove(
candidates: List[Move],
to: Square,
promotion: Option[PromotionPiece],
): Option[Move] =
candidates.filter(_.to == to) match
case Nil => None
case moves =>
promotion match
case Some(pp) => moves.find(_.moveType == MoveType.Promotion(pp))
case None =>
moves
.find(m => !m.moveType.isInstanceOf[MoveType.Promotion])
.orElse(moves.headOption)
private def detectGameOver(ctx: GameContext): Option[GameResult] =
if DefaultRules.isCheckmate(ctx) then Some(GameResult.Checkmate(ctx.turn.opposite))
else if DefaultRules.isStalemate(ctx) then Some(GameResult.Stalemate)
else if DefaultRules.isInsufficientMaterial(ctx) then Some(GameResult.InsufficientMaterial)
else None
private def replayIntoSession(
session: GameSession,
moves: List[Move],
startCtx: GameContext,
): Either[String, GameSession] =
moves.foldLeft[Either[String, GameSession]](Right(session)):
case (Left(err), _) => Left(err)
case (Right(s), move) =>
val legal = DefaultRules.legalMoves(s.context)(move.from)
legal
.find(m => m.from == move.from && m.to == move.to && m.moveType == move.moveType)
.orElse(legal.find(m => m.from == move.from && m.to == move.to)) match
case None => Left(s"Illegal move in PGN: $move")
case Some(legalMove) =>
val nextCtx = DefaultRules.applyMove(s.context)(legalMove)
val cmd = MoveCommand(
from = legalMove.from,
to = legalMove.to,
moveResult = Some(MoveResult.Successful(nextCtx, s.context.board.pieceAt(legalMove.to))),
previousContext = Some(s.context),
)
s.invoker.execute(cmd)
Right(s.copy(context = nextCtx))
@@ -1,99 +0,0 @@
package de.nowchess.backcore.resource
import de.nowchess.backcore.dto.*
import de.nowchess.backcore.game.{GameMapper, GameStore}
import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import jakarta.ws.rs.*
import jakarta.ws.rs.core.{MediaType, Response}
@Path("/api/board/game")
@Produces(Array(MediaType.APPLICATION_JSON))
@ApplicationScoped
class GameResource @Inject() (store: GameStore):
@POST
@Consumes(Array(MediaType.APPLICATION_JSON))
def createGame(req: CreateGameRequest): Response =
val session = store.create(Option(req).getOrElse(CreateGameRequest()))
Response.status(201).entity(GameMapper.toGameFull(session)).build()
@GET
@Path("/{gameId}")
def getGame(@PathParam("gameId") gameId: String): Response =
store.get(gameId) match
case Some(session) => Response.ok(GameMapper.toGameFull(session)).build()
case None =>
Response
.status(404)
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
.build()
@GET
@Path("/{gameId}/stream")
@Produces(Array("application/x-ndjson"))
def streamGame(@PathParam("gameId") gameId: String): Response =
store.get(gameId) match
case None =>
Response
.status(404)
.`type`(MediaType.APPLICATION_JSON)
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
.build()
case Some(session) =>
// Simplified: return a single-line NDJSON snapshot of the current game state
val event = s"""{"type":"gameFull","game":${GameMapper.toGameFullJson(session)}}"""
Response.ok(event + "\n").build()
@POST
@Path("/{gameId}/resign")
def resignGame(@PathParam("gameId") gameId: String): Response =
store.resign(gameId) match
case Right(_) => Response.ok(OkResponse()).build()
case Left(err) if err.contains("not found") =>
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", err)).build()
case Left(err) =>
Response.status(400).entity(ApiErrorResponse("RESIGN_ERROR", err)).build()
@POST
@Path("/{gameId}/draw/{action}")
def drawAction(
@PathParam("gameId") gameId: String,
@PathParam("action") action: String,
): Response =
store.drawAction(gameId, action) match
case Right(_) => Response.ok(OkResponse()).build()
case Left(err) if err.contains("not found") =>
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", err)).build()
case Left(err) =>
Response.status(400).entity(ApiErrorResponse("DRAW_ERROR", err)).build()
@GET
@Path("/{gameId}/export/fen")
@Produces(Array(MediaType.TEXT_PLAIN))
def exportFen(@PathParam("gameId") gameId: String): Response =
store.get(gameId) match
case None =>
Response
.status(404)
.`type`(MediaType.APPLICATION_JSON)
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
.build()
case Some(session) =>
import de.nowchess.io.fen.FenExporter
Response.ok(FenExporter.exportGameContext(session.context)).build()
@GET
@Path("/{gameId}/export/pgn")
@Produces(Array("application/x-chess-pgn"))
def exportPgn(@PathParam("gameId") gameId: String): Response =
store.get(gameId) match
case None =>
Response
.status(404)
.`type`(MediaType.APPLICATION_JSON)
.entity(ApiErrorResponse("GAME_NOT_FOUND", s"Game $gameId not found"))
.build()
case Some(session) =>
import de.nowchess.io.pgn.PgnExporter
Response.ok(PgnExporter.exportGameContext(session.context)).build()
@@ -1,29 +0,0 @@
package de.nowchess.backcore.resource
import de.nowchess.backcore.dto.{ApiErrorResponse, ImportFenRequest, ImportPgnRequest}
import de.nowchess.backcore.game.{GameMapper, GameStore}
import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import jakarta.ws.rs.*
import jakarta.ws.rs.core.{MediaType, Response}
@Path("/api/board/game/import")
@Produces(Array(MediaType.APPLICATION_JSON))
@Consumes(Array(MediaType.APPLICATION_JSON))
@ApplicationScoped
class ImportResource @Inject() (store: GameStore):
@POST
@Path("/fen")
def importFen(req: ImportFenRequest): Response =
store.importFen(Option(req).getOrElse(ImportFenRequest())) match
case Right(session) => Response.status(201).entity(GameMapper.toGameFull(session)).build()
case Left(err) => Response.status(400).entity(ApiErrorResponse("INVALID_FEN", err)).build()
@POST
@Path("/pgn")
def importPgn(req: ImportPgnRequest): Response =
val body = Option(req).getOrElse(ImportPgnRequest())
store.importPgn(body.pgn, None, None) match
case Right(session) => Response.status(201).entity(GameMapper.toGameFull(session)).build()
case Left(err) => Response.status(400).entity(ApiErrorResponse("INVALID_PGN", err)).build()
@@ -1,87 +0,0 @@
package de.nowchess.backcore.resource
import de.nowchess.api.board.Square
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
import de.nowchess.backcore.dto.*
import de.nowchess.backcore.game.{GameMapper, GameStore}
import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import jakarta.ws.rs.*
import jakarta.ws.rs.core.{MediaType, Response}
@Path("/api/board/game")
@Produces(Array(MediaType.APPLICATION_JSON))
@ApplicationScoped
class MoveResource @Inject() (store: GameStore):
@POST
@Path("/{gameId}/move/{uci}")
def makeMove(
@PathParam("gameId") gameId: String,
@PathParam("uci") uci: String,
): Response =
store.applyMove(gameId, uci) match
case Right(session) => Response.ok(GameMapper.toGameState(session)).build()
case Left(err) if err.contains("not found") =>
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", err)).build()
case Left(err) =>
Response.status(400).entity(ApiErrorResponse("INVALID_MOVE", err)).build()
@GET
@Path("/{gameId}/moves")
def getLegalMoves(
@PathParam("gameId") gameId: String,
@QueryParam("square") squareParam: String,
): Response =
val square = Option(squareParam).flatMap(Square.fromAlgebraic)
store.legalMoves(gameId, square) match
case Right(moves) =>
val dtos = moves.map(toLegalMoveDto)
Response.ok(LegalMovesResponse(dtos)).build()
case Left(err) if err.contains("not found") =>
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", err)).build()
case Left(err) =>
Response.status(400).entity(ApiErrorResponse("ERROR", err)).build()
@POST
@Path("/{gameId}/undo")
def undoMove(@PathParam("gameId") gameId: String): Response =
store.undo(gameId) match
case Right(session) => Response.ok(GameMapper.toGameState(session)).build()
case Left(err) if err.contains("not found") =>
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", err)).build()
case Left(err) =>
Response.status(400).entity(ApiErrorResponse("UNDO_NOT_AVAILABLE", err)).build()
@POST
@Path("/{gameId}/redo")
def redoMove(@PathParam("gameId") gameId: String): Response =
store.redo(gameId) match
case Right(session) => Response.ok(GameMapper.toGameState(session)).build()
case Left(err) if err.contains("not found") =>
Response.status(404).entity(ApiErrorResponse("GAME_NOT_FOUND", err)).build()
case Left(err) =>
Response.status(400).entity(ApiErrorResponse("REDO_NOT_AVAILABLE", err)).build()
private def toLegalMoveDto(move: Move): LegalMoveDto =
val uci = GameMapper.moveToUci(move)
val (moveType, promotion) = move.moveType match
case MoveType.Normal(true) => ("capture", None)
case MoveType.Normal(false) => ("normal", None)
case MoveType.CastleKingside => ("castleKingside", None)
case MoveType.CastleQueenside => ("castleQueenside", None)
case MoveType.EnPassant => ("enPassant", None)
case MoveType.Promotion(pp) =>
val pName = pp match
case PromotionPiece.Queen => "queen"
case PromotionPiece.Rook => "rook"
case PromotionPiece.Bishop => "bishop"
case PromotionPiece.Knight => "knight"
("promotion", Some(pName))
LegalMoveDto(
from = move.from.toString,
to = move.to.toString,
uci = uci,
moveType = moveType,
promotion = promotion,
)
@@ -1,11 +0,0 @@
package de.nowchess.backcore
import io.quarkus.test.junit.QuarkusTest
import org.junit.jupiter.api.Test
@QuarkusTest
class BackcoreStartupTest:
@Test
def applicationStarts(): Unit =
// If we get here the Quarkus container started successfully
()
@@ -1,67 +0,0 @@
package de.nowchess.backcore.resource
import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured
import org.hamcrest.Matchers.{equalTo, matchesPattern, notNullValue}
import org.junit.jupiter.api.Test
@QuarkusTest
class GameResourceTest:
@Test
def createGameReturns201WithGameId(): Unit =
RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.body("gameId", matchesPattern("[A-Za-z0-9]{8}"))
.body("state.fen", notNullValue())
.body("state.turn", equalTo("white"))
.body("state.status", equalTo("started"))
@Test
def createGameWithPlayersReturns201(): Unit =
RestAssured
.`given`()
.contentType("application/json")
.body("""{"white":{"id":"p1","displayName":"Alice"},"black":{"id":"p2","displayName":"Bob"}}""")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.body("white.id", equalTo("p1"))
.body("black.displayName", equalTo("Bob"))
@Test
def getGameReturns200ForExistingGame(): Unit =
val gameId = RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId")
.`then`()
.statusCode(200)
.body("gameId", equalTo(gameId))
@Test
def getGameReturns404ForUnknownId(): Unit =
RestAssured
.`given`()
.when()
.get("/api/board/game/XXXXXXXX")
.`then`()
.statusCode(404)
@@ -1,177 +0,0 @@
package de.nowchess.backcore.resource
import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured
import org.hamcrest.Matchers.{containsString, equalTo, matchesPattern, notNullValue}
import org.junit.jupiter.api.Test
@QuarkusTest
class ImportExportTest:
private val startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
// ─── Import FEN ────────────────────────────────────────────────
@Test
def importFenReturns201WithCorrectPosition(): Unit =
RestAssured
.`given`()
.contentType("application/json")
.body(s"""{"fen":"$startFen"}""")
.when()
.post("/api/board/game/import/fen")
.`then`()
.statusCode(201)
.body("gameId", matchesPattern("[A-Za-z0-9]{8}"))
.body("state.fen", equalTo(startFen))
.body("state.turn", equalTo("white"))
@Test
def importFenWithCustomPositionWorks(): Unit =
val fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
RestAssured
.`given`()
.contentType("application/json")
.body(s"""{"fen":"$fen"}""")
.when()
.post("/api/board/game/import/fen")
.`then`()
.statusCode(201)
.body("state.fen", equalTo(fen))
.body("state.turn", equalTo("black"))
@Test
def importFenWithInvalidFenReturns400(): Unit =
RestAssured
.`given`()
.contentType("application/json")
.body("""{"fen":"not-a-fen"}""")
.when()
.post("/api/board/game/import/fen")
.`then`()
.statusCode(400)
// ─── Import PGN ────────────────────────────────────────────────
@Test
def importPgnReturns201(): Unit =
RestAssured
.`given`()
.contentType("application/json")
.body("""{"pgn":"1. e4 e5 2. Nf3 Nc6 *"}""")
.when()
.post("/api/board/game/import/pgn")
.`then`()
.statusCode(201)
.body("gameId", matchesPattern("[A-Za-z0-9]{8}"))
.body("state.turn", equalTo("white"))
@Test
def importPgnWithInvalidPgnReturns400(): Unit =
RestAssured
.`given`()
.contentType("application/json")
.body("""{"pgn":"1. z9 *"}""")
.when()
.post("/api/board/game/import/pgn")
.`then`()
.statusCode(400)
// ─── Export FEN ────────────────────────────────────────────────
@Test
def exportFenReturnsStartingFen(): Unit =
val gameId = RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId/export/fen")
.`then`()
.statusCode(200)
.body(equalTo(startFen))
@Test
def exportFenOnUnknownGameReturns404(): Unit =
RestAssured
.`given`()
.when()
.get("/api/board/game/XXXXXXXX/export/fen")
.`then`()
.statusCode(404)
// ─── Export PGN ────────────────────────────────────────────────
@Test
def exportPgnReturnsText(): Unit =
val gameId = RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/move/e2e4")
.`then`()
.statusCode(200)
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId/export/pgn")
.`then`()
.statusCode(200)
.body(containsString("e4"))
// ─── Stream ────────────────────────────────────────────────────
@Test
def streamReturnsNdjsonSnapshot(): Unit =
val gameId = RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
val body = RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId/stream")
.`then`()
.statusCode(200)
.contentType("application/x-ndjson")
.extract()
.body()
.asString()
assert(body.trim.startsWith("""{"type":"gameFull""""), s"Expected gameFull event, got: $body")
@Test
def streamOnUnknownGameReturns404(): Unit =
RestAssured
.`given`()
.when()
.get("/api/board/game/XXXXXXXX/stream")
.`then`()
.statusCode(404)
@@ -1,84 +0,0 @@
package de.nowchess.backcore.resource
import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured
import org.hamcrest.Matchers.{containsString, empty, equalTo, hasItem, hasItems, not, notNullValue}
import org.junit.jupiter.api.Test
@QuarkusTest
class MoveResourceTest:
private def createGame(): String =
RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
@Test
def makeMoveReturns200WithUpdatedFen(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/move/e2e4")
.`then`()
.statusCode(200)
.body("fen", containsString("4P3")) // e4 pawn present in FEN
.body("turn", equalTo("black"))
.body("moves", hasItem("e2e4"))
@Test
def makeMoveOnUnknownGameReturns404(): Unit =
RestAssured
.`given`()
.when()
.post("/api/board/game/XXXXXXXX/move/e2e4")
.`then`()
.statusCode(404)
@Test
def illegalMoveReturns400(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/move/e2e5") // illegal — pawns can't jump 3 squares
.`then`()
.statusCode(400)
@Test
def getLegalMovesReturnsNonEmptyList(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId/moves")
.`then`()
.statusCode(200)
.body("moves", not(empty()))
@Test
def getLegalMovesFilteredBySquareReturnsCorrectMoves(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId/moves?square=e2")
.`then`()
.statusCode(200)
.body("moves.uci", hasItems("e2e3", "e2e4"))
@Test
def getLegalMovesOnUnknownGameReturns404(): Unit =
RestAssured
.`given`()
.when()
.get("/api/board/game/XXXXXXXX/moves")
.`then`()
.statusCode(404)
@@ -1,168 +0,0 @@
package de.nowchess.backcore.resource
import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured
import org.hamcrest.Matchers.{equalTo, notNullValue}
import org.junit.jupiter.api.Test
@QuarkusTest
class ResignDrawTest:
private def createGame(): String =
RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
// ─── Resign ────────────────────────────────────────────────────
@Test
def resignReturns200(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/resign")
.`then`()
.statusCode(200)
.body("ok", equalTo(true))
@Test
def afterResignGameShowsResignStatusAndWinner(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/resign")
.`then`()
.statusCode(200)
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId")
.`then`()
.statusCode(200)
.body("state.status", equalTo("resign"))
.body("state.winner", notNullValue())
@Test
def resignOnUnknownGameReturns404(): Unit =
RestAssured
.`given`()
.when()
.post("/api/board/game/XXXXXXXX/resign")
.`then`()
.statusCode(404)
// ─── Draw ──────────────────────────────────────────────────────
@Test
def offerDrawSetsDrawOfferedStatus(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/draw/offer")
.`then`()
.statusCode(200)
.body("ok", equalTo(true))
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId")
.`then`()
.statusCode(200)
.body("state.status", equalTo("drawOffered"))
@Test
def acceptDrawAfterOfferSetsDrawStatus(): Unit =
val gameId = createGame()
// White offers
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/draw/offer")
.`then`()
.statusCode(200)
// Black moves so it's black's turn... actually the API doesn't enforce turn-based draw accept.
// White offered, so black (opponent) accepts — but since there's no auth, we just call accept.
// The GameStore checks drawOfferedBy != turn to allow accept.
// White offered on white's turn, so black needs to accept — but current turn is still white.
// We need to make a move first to switch turns.
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/move/e2e4")
.`then`()
.statusCode(200)
// Now it's black's turn and white offered the draw — black accepts
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/draw/accept")
.`then`()
.statusCode(200)
.body("ok", equalTo(true))
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId")
.`then`()
.statusCode(200)
.body("state.status", equalTo("draw"))
@Test
def declineDrawClearsOffer(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/draw/offer")
.`then`()
.statusCode(200)
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/draw/decline")
.`then`()
.statusCode(200)
.body("ok", equalTo(true))
RestAssured
.`given`()
.when()
.get(s"/api/board/game/$gameId")
.`then`()
.statusCode(200)
.body("state.status", equalTo("started"))
@Test
def acceptWithoutOfferReturns400(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/draw/accept")
.`then`()
.statusCode(400)
@Test
def drawOnUnknownGameReturns404(): Unit =
RestAssured
.`given`()
.when()
.post("/api/board/game/XXXXXXXX/draw/offer")
.`then`()
.statusCode(404)
@@ -1,88 +0,0 @@
package de.nowchess.backcore.resource
import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured
import org.hamcrest.Matchers.{containsString, equalTo}
import org.junit.jupiter.api.Test
@QuarkusTest
class UndoRedoTest:
private val initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
private def createGame(): String =
RestAssured
.`given`()
.contentType("application/json")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(201)
.extract()
.path[String]("gameId")
@Test
def undoAfterMoveRestoresOriginalPosition(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/move/e2e4")
.`then`()
.statusCode(200)
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/undo")
.`then`()
.statusCode(200)
.body("fen", equalTo(initialFen))
.body("undoAvailable", equalTo(false))
@Test
def redoAfterUndoRestoresMovedPosition(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/move/e2e4")
.`then`()
.statusCode(200)
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/undo")
.`then`()
.statusCode(200)
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/redo")
.`then`()
.statusCode(200)
.body("fen", containsString("4P3"))
.body("turn", equalTo("black"))
@Test
def undoWithNoHistoryReturns400(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/undo")
.`then`()
.statusCode(400)
@Test
def redoWithNoRedoStackReturns400(): Unit =
val gameId = createGame()
RestAssured
.`given`()
.when()
.post(s"/api/board/game/$gameId/redo")
.`then`()
.statusCode(400)
@@ -1,10 +1,6 @@
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.jvm.tasks.Jar
plugins {
id("scala")
id("org.scoverage")
application
}
group = "de.nowchess"
@@ -12,6 +8,8 @@ version = "1.0-SNAPSHOT"
@Suppress("UNCHECKED_CAST")
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
@Suppress("UNCHECKED_CAST")
val scoverageExcluded = rootProject.extra["SCOVERAGE_EXCLUDED"] as List<String>
repositories {
mavenCentral()
@@ -23,33 +21,23 @@ scala {
scoverage {
scoverageVersion.set(versions["SCOVERAGE"]!!)
excludedPackages.set(listOf(
"de.nowchess.ui.gui",
"de.nowchess.ui.terminal",
"de.nowchess.ui.Main",
))
}
application {
mainClass.set("de.nowchess.ui.Main")
excludedPackages.set(
listOf(
"de\\.nowchess\\.bot\\.bots\\.NNUEBot",
"de\\.nowchess\\.bot\\.bots\\.nnue\\..*",
"de\\.nowchess\\.bot\\.util\\.PolyglotBook",
)
)
excludedFiles.set(scoverageExcluded)
}
tasks.withType<ScalaCompile> {
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
}
tasks.named<JavaExec>("run") {
jvmArgs("-Dfile.encoding=UTF-8", "-Dstdout.encoding=UTF-8", "-Dstderr.encoding=UTF-8")
standardInput = System.`in`
}
tasks.named<Jar>("jar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
dependencies {
implementation("org.scala-lang:scala3-compiler_3") {
compileOnly("org.scala-lang:scala3-compiler_3") {
version {
strictly(versions["SCALA3"]!!)
}
@@ -60,27 +48,10 @@ dependencies {
}
}
implementation(project(":modules:core"))
implementation(project(":modules:rule"))
implementation(project(":modules:api"))
implementation(project(":modules:io"))
// ScalaFX dependencies
implementation("org.scalafx:scalafx_3:${versions["SCALAFX"]!!}")
// JavaFX dependencies for the current platform
val javaFXVersion = versions["JAVAFX"]!!
val osName = System.getProperty("os.name").lowercase()
val platform = when {
osName.contains("win") -> "win"
osName.contains("mac") -> "mac"
osName.contains("linux") -> "linux"
else -> "linux"
}
listOf("base", "controls", "graphics", "media").forEach { module ->
implementation("org.openjfx:javafx-$module:$javaFXVersion:$platform")
}
implementation(project(":modules:rule"))
implementation("com.microsoft.onnxruntime:onnxruntime:${versions["ONNXRUNTIME"]!!}")
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
testImplementation("org.junit.jupiter:junit-jupiter")
@@ -102,3 +73,7 @@ tasks.test {
tasks.reportScoverage {
dependsOn(tasks.test)
}
tasks.jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
# Data and weights are local artifacts, not committed
data/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
.venv
# IDE
.idea/
.vscode/
*.swp
*.swo
tactical_data/
trainingdata/
/datasets/
+173
View File
@@ -0,0 +1,173 @@
# Training Dataset Management
The NNUE training pipeline now features versioned dataset management, similar to model versioning. This prevents data loss and allows you to maintain multiple training configurations.
## Directory Structure
```
datasets/
ds_v1/
labeled.jsonl # Training data: {"fen": "...", "eval": 0.5, "eval_raw": 150}
metadata.json # Version info and composition
ds_v2/
labeled.jsonl
metadata.json
```
## Metadata Schema
Each dataset has a `metadata.json` file tracking its composition:
```json
{
"version": 1,
"created": "2026-04-13T15:30:45.123456",
"total_positions": 1000000,
"stockfish_depth": 12,
"sources": [
{
"type": "generated",
"count": 500000,
"params": {
"num_positions": 500000,
"min_move": 1,
"max_move": 50
}
},
{
"type": "tactical",
"count": 300000,
"max_puzzles": 300000
},
{
"type": "file_import",
"count": 200000,
"path": "/path/to/original_file.txt"
}
]
}
```
## TUI Workflow
### Main Menu
```
1 - Manage Training Data
2 - Train Model
3 - Export Model
4 - Exit
```
### Training Data Management Submenu
```
1 - Create new dataset
2 - Extend existing dataset
3 - View all datasets
4 - Delete dataset
5 - Back
```
## Creating a Dataset
Use the "Create new dataset" option to add data from one or more sources:
1. **Generate random positions** — Play random games and sample positions
- Number of positions
- Move range (min/max move number to sample from)
- Number of worker threads
2. **Import from file** — Load positions from a FEN file
- File must contain one FEN string per line
- Duplicates are automatically removed
3. **Extract tactical puzzles** — Download and extract Lichess puzzle database
- Maximum number of puzzles to include
- Automatically filters for tactical themes (forks, pins, mates, etc.)
You can combine multiple sources in a single dataset creation session. All positions are:
- Deduplicated (only unique FENs are kept)
- Labeled with Stockfish evaluations
- Saved to `datasets/ds_vN/labeled.jsonl`
## Extending a Dataset
Use "Extend existing dataset" to add more positions to an existing dataset:
1. Select the dataset version to extend
2. Choose data sources (same options as creation)
3. Confirm labeling parameters
4. New positions are:
- Labeled with Stockfish
- Deduplicated against the target dataset (preventing duplicates)
- Merged into the existing `labeled.jsonl`
- Metadata is updated with the new source entry
## Training with a Dataset
When you start training (Standard or Burst mode), you'll be prompted to select a dataset version. The TUI will display all available datasets with:
- Version number
- Total number of positions
- Source types (generated, tactical, imported)
- Stockfish depth used
- Creation date
## Legacy Data Migration
If you have existing labeled data in `data/training_data.jsonl` from before this update:
1. Open the "Manage Training Data" menu
2. Choose "Create new dataset"
3. Select "Import from file"
4. Point to `data/training_data.jsonl`
5. Complete the dataset creation
Alternatively, you can manually copy the file to `datasets/ds_v1/labeled.jsonl` and create a `metadata.json` file.
## Viewing Dataset Details
Use "View all datasets" to see a table of all datasets with:
- Version number
- Position count
- Source composition
- Stockfish depth
- Creation date
## Deleting a Dataset
Use "Delete dataset" to remove a dataset and free up disk space. **This action cannot be undone.**
⚠️ The system does not prevent deleting datasets used by model checkpoints. Plan accordingly.
## Technical Details
### Deduplication Strategy
When extending a dataset, positions are deduplicated **within that dataset only**. This allows different datasets to contain overlapping positions if desired.
When creating a new dataset from multiple sources, all sources are combined and deduplicated before labeling.
### Labeled Position Format
Each line in `labeled.jsonl` is a JSON object:
```json
{
"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"eval": 0.0,
"eval_raw": 0
}
```
- `fen`: The position in Forsyth-Edwards Notation
- `eval`: Normalized evaluation ([-1, 1] range using tanh)
- `eval_raw`: Raw Stockfish evaluation in centipawns
### Storage Location
Datasets are stored in the `datasets/` directory relative to the script location. The old `data/` directory is preserved for backward compatibility but is not actively used by the new system.
## Performance Tips
- **Smaller datasets train faster** — Start with 100k-500k positions
- **Deduplication matters** — Use the extend functionality to build up your dataset without redundant data
- **Stockfish depth** — Depth 12-14 balances accuracy and labeling speed
- **Workers** — Use 4-8 workers for labeling if your machine supports it; more workers = faster but uses more CPU/memory
+129
View File
@@ -0,0 +1,129 @@
# NNUE Python Pipeline
Central CLI for training and exporting chess evaluation neural networks (NNUE).
## Directory Structure
```
python/
├── nnue.py # Main CLI entry point
├── src/ # Python modules
│ ├── generate.py # Generate random chess positions
│ ├── label.py # Label positions with Stockfish
│ ├── train.py # Train NNUE model
│ └── export.py # Export weights to Scala
├── data/ # Training data (gitignored)
│ ├── positions.txt
│ └── training_data.jsonl
└── weights/ # Model weights (gitignored)
├── nnue_weights_v1.pt
├── nnue_weights_v1_metadata.json
└── ...
```
## Quick Start
```bash
# Train a new model (500k positions, auto-detect checkpoint)
python nnue.py train
# Train from specific checkpoint
python nnue.py train --from-checkpoint 2
# Train with custom games count
python nnue.py train --games 200000
# Train with custom positions file
python nnue.py train --positions-file my_positions.txt
# Export specific version to Scala
python nnue.py export 2
# List all checkpoints
python nnue.py list
```
## CLI Commands
### `train` - Train NNUE model
```bash
python nnue.py train [OPTIONS]
```
**Options:**
- `--from-checkpoint N` - Resume from checkpoint version N (default: uses latest)
- `--games N` - Number of games to generate (default: 500000)
- `--positions-file FILE` - Use existing positions file instead of generating
- `--stockfish PATH` - Path to Stockfish binary (default: `$STOCKFISH_PATH` or `/usr/games/stockfish`)
**Examples:**
```bash
# Train with latest checkpoint
python nnue.py train
# Train from v2 with 100k games
python nnue.py train --from-checkpoint 2 --games 100000
# Train with custom positions
python nnue.py train --positions-file my_games.txt --stockfish /opt/stockfish/sf15
```
### `export` - Export weights to Scala
```bash
python nnue.py export WEIGHTS [output_path]
```
**Arguments:**
- `WEIGHTS` - Version number (e.g., `2`) or full filename (e.g., `nnue_weights_v2.pt`)
**Examples:**
```bash
# Export version 2
python nnue.py export 2
# Export with full filename
python nnue.py export nnue_weights_v3.pt
```
Output goes to `../src/main/scala/de/nowchess/bot/bots/nnue/NNUEWeights_vN.scala`
### `list` - List available checkpoints
```bash
python nnue.py list
```
Shows all available model versions with file sizes.
## Data Flow
1. **Generate**`data/positions.txt`
- Random chess positions from 8-20 move openings
- Filters out checks, game-over states, and captures
2. **Label**`data/training_data.jsonl`
- Evaluates each position with Stockfish at depth 12
- Stores FEN + evaluation in JSONL format
3. **Train**`weights/nnue_weights_vN.pt`
- Trains neural network on labeled positions
- Auto-versioning (v1, v2, v3, etc.)
- Saves metadata alongside weights
4. **Export**`NNUEWeights_vN.scala`
- Converts weights to Scala object
- Ready for integration into bot
## Versioning
- Models are automatically versioned (v1, v2, v3, etc.)
- Each version gets a `_metadata.json` file with training info
- Training from checkpoint uses latest version unless specified with `--from-checkpoint`
## Files
- `data/` and `weights/` are gitignored (local artifacts)
- Documentation in `docs/` explains training, debugging, and incremental improvements
- Source modules in `src/` are independent and can be imported for custom workflows

Some files were not shown because too many files have changed in this diff Show More