From a1b7cc7f4a9f88f63ee359b72011b7ade6ff3514 Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 5 Apr 2026 17:01:34 +0200 Subject: [PATCH] refactor(core): streamline FEN and PGN export/import methods in ChessBoardView --- .idea/copilotDiffState.xml | 18 ----- .../de/nowchess/chess/engine/GameEngine.scala | 4 + .../chess/engine/GameEngineNotationTest.scala | 2 +- .../de/nowchess/ui/gui/ChessBoardView.scala | 74 ++++++------------- 4 files changed, 28 insertions(+), 70 deletions(-) delete mode 100644 .idea/copilotDiffState.xml diff --git a/.idea/copilotDiffState.xml b/.idea/copilotDiffState.xml deleted file mode 100644 index afa82a2..0000000 --- a/.idea/copilotDiffState.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala index 58533da..3db2286 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala @@ -217,8 +217,12 @@ class GameEngine( if ruleSet.isCheckmate(currentContext) then val winner = currentContext.turn.opposite notifyObservers(CheckmateEvent(currentContext, winner)) + invoker.clear() + currentContext = GameContext.initial else if ruleSet.isStalemate(currentContext) then notifyObservers(StalemateEvent(currentContext)) + invoker.clear() + currentContext = GameContext.initial else if ruleSet.isCheck(currentContext) then notifyObservers(CheckDetectedEvent(currentContext)) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala index 0ee7678..a431535 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala @@ -73,7 +73,7 @@ class GameEngineNotationTest extends AnyFunSuite with Matchers: // Verify the captured pawn was found (computeCaptured EnPassant branch) val moveEvt = events.collect { case e: MoveExecutedEvent => e }.head moveEvt.capturedPiece shouldBe defined - moveEvt.capturedPiece.get should include ("black") + moveEvt.capturedPiece.get should include ("Black") events.clear() engine.undo() diff --git a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala index ed3e757..de4d186 100644 --- a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala +++ b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala @@ -5,16 +5,19 @@ import scalafx.Includes.* import scalafx.application.Platform import scalafx.geometry.{Insets, Pos} import scalafx.scene.control.{Button, ButtonType, ChoiceDialog, Label} -import scalafx.scene.layout.{BorderPane, GridPane, HBox, VBox, StackPane} +import scalafx.scene.layout.{BorderPane, GridPane, HBox, StackPane, VBox} import scalafx.scene.paint.Color as FXColor import scalafx.scene.shape.Rectangle import scalafx.scene.text.{Font, Text} import scalafx.stage.Stage -import de.nowchess.api.board.{Board, Color, Piece, PieceType, Square, File, Rank} +import de.nowchess.api.board.{Board, Color, File, Piece, PieceType, Rank, Square} import de.nowchess.api.game.{GameHistory, HistoryMove} import de.nowchess.api.move.PromotionPiece import de.nowchess.chess.command.{MoveCommand, MoveResult} import de.nowchess.chess.engine.GameEngine +import de.nowchess.io.fen.{FenExporter, FenParser} +import de.nowchess.io.pgn.{PgnExporter, PgnParser} +import de.nowchess.io.{GameContextExport, GameContextImport} /** ScalaFX chess board view that displays the game state. * Uses chess sprites and color palette. @@ -276,33 +279,32 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B case _ => engine.completePromotion(PromotionPiece.Queen) // Default private def doFenExport(): Unit = - val fen = de.nowchess.io.fen.FenExporter.gameContextToFen(engine.context) - showCopyDialog("FEN Export", fen) + doExport(FenExporter, "FEN") private def doFenImport(): Unit = - showInputDialog("FEN Import", rows = 1).foreach { fen => - de.nowchess.io.fen.FenParser.parseFen(fen) match - case Some(gameContext) => - engine.loadPosition(gameContext) - case None => - showMessage("⚠️ Invalid FEN string") - } + doImport(FenParser, "FEN") private def doPgnExport(): Unit = - val pgn = de.nowchess.io.pgn.PgnExporter.exportGame( - Map("Event" -> "NowChess Game", "Date" -> "2026.04.04"), - exportableGameHistory() - ) - showCopyDialog("PGN Export", pgn) + doExport(PgnExporter, "PGN") private def doPgnImport(): Unit = - showInputDialog("PGN Import", rows = 5).foreach { pgn => - engine.loadPgn(pgn) match - case Right(_) => - showMessage("✓ PGN loaded successfully!") + doImport(PgnParser, "PGN") + + private def doExport(exporter: GameContextExport, formatName: String): Unit = { + val exported = exporter.exportGameContext(engine.context) + showCopyDialog(s"$formatName Export", exported) + } + + private def doImport(importer: GameContextImport, formatName: String): Unit = { + showInputDialog(s"$formatName Import", rows = 5).foreach { input => + importer.importGameContext(input) match + case Right(gameContext) => + engine.loadPosition(gameContext) + showMessage(s"✓ $formatName loaded successfully!") case Left(err) => - showMessage(s"⚠️ PGN Error: $err") + showMessage(s"⚠️ $formatName Error: $err") } + } private def showCopyDialog(title: String, content: String): Unit = val area = new javafx.scene.control.TextArea(content) @@ -335,33 +337,3 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B val result = dialog.showAndWait() if result.isPresent && result.get != null && result.get.nonEmpty then Some(result.get) else None - private def exportableGameHistory(): GameHistory = - val moveCommands = engine.commandHistory.collect { case moveCmd: MoveCommand => moveCmd } - val activeMoveCount = engine.context.moves.length - val historyMoves = moveCommands.take(activeMoveCount).flatMap: moveCmd => - moveCmd.previousContext.flatMap: previousContext => - moveCmd.moveResult.collect: - case MoveResult.Successful(_, captured) => - val movingPiece = previousContext.board.pieceAt(moveCmd.from) - val pieceType = movingPiece.map(_.pieceType).getOrElse(PieceType.Pawn) - val castleSide = moveCmd.notation match - case "O-O" => Some("Kingside") - case "O-O-O" => Some("Queenside") - case _ => None - val promotionPiece = moveCmd.notation.split("=").lastOption.flatMap: - case "Q" => Some(PromotionPiece.Queen) - case "R" => Some(PromotionPiece.Rook) - case "B" => Some(PromotionPiece.Bishop) - case "N" => Some(PromotionPiece.Knight) - case _ => None - HistoryMove( - from = moveCmd.from, - to = moveCmd.to, - castleSide = castleSide, - promotionPiece = promotionPiece, - pieceType = pieceType, - isCapture = captured.isDefined - ) - - GameHistory(historyMoves.toList) -