refactor(core): replace GameHistory with HistoryMove in PGN export logic

This commit is contained in:
2026-04-05 17:08:54 +02:00
parent a1b7cc7f4a
commit 432385c7b0
13 changed files with 110 additions and 218 deletions
@@ -11,7 +11,6 @@ import scalafx.scene.shape.Rectangle
import scalafx.scene.text.{Font, Text}
import scalafx.stage.Stage
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
@@ -178,7 +177,7 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B
if piece.color == currentTurn then
selectedSquare = Some(clickedSquare)
highlightSquare(rank, file, PieceSprites.SquareColors.Selected)
val legalDests = engine.ruleSet.legalMoves(engine.context, clickedSquare)
.collect { case move if move.from == clickedSquare => move.to }
legalDests.foreach { sq =>
@@ -289,7 +288,7 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B
private def doPgnImport(): Unit =
doImport(PgnParser, "PGN")
private def doExport(exporter: GameContextExport, formatName: String): Unit = {
val exported = exporter.exportGameContext(engine.context)
showCopyDialog(s"$formatName Export", exported)
@@ -3,8 +3,8 @@ package de.nowchess.ui.terminal
import scala.io.StdIn
import de.nowchess.api.move.PromotionPiece
import de.nowchess.chess.engine.GameEngine
import de.nowchess.chess.observer.{Observer, GameEvent, *}
import de.nowchess.chess.view.Renderer
import de.nowchess.chess.observer.*
import de.nowchess.ui.utils.Renderer
/** Terminal UI that implements Observer pattern.
* Subscribes to GameEngine and receives state change events.
@@ -0,0 +1,18 @@
package de.nowchess.ui.utils
import de.nowchess.api.board.{Color, Piece, PieceType}
extension (p: Piece)
def unicode: String = (p.color, p.pieceType) match
case (Color.White, PieceType.King) => "\u2654"
case (Color.White, PieceType.Queen) => "\u2655"
case (Color.White, PieceType.Rook) => "\u2656"
case (Color.White, PieceType.Bishop) => "\u2657"
case (Color.White, PieceType.Knight) => "\u2658"
case (Color.White, PieceType.Pawn) => "\u2659"
case (Color.Black, PieceType.King) => "\u265A"
case (Color.Black, PieceType.Queen) => "\u265B"
case (Color.Black, PieceType.Rook) => "\u265C"
case (Color.Black, PieceType.Bishop) => "\u265D"
case (Color.Black, PieceType.Knight) => "\u265E"
case (Color.Black, PieceType.Pawn) => "\u265F"
@@ -0,0 +1,28 @@
package de.nowchess.ui.utils
import de.nowchess.api.board.*
object Renderer:
private val AnsiReset = "\u001b[0m"
private val AnsiLightSquare = "\u001b[48;5;223m" // warm beige
private val AnsiDarkSquare = "\u001b[48;5;130m" // brown
private val AnsiWhitePiece = "\u001b[97m" // bright white text
private val AnsiBlackPiece = "\u001b[30m" // black text
def render(board: Board): String =
val rows = (0 until 8).reverse.map { rank =>
val cells = (0 until 8).map { file =>
val sq = Square(File.values(file), Rank.values(rank))
val isLightSq = (file + rank) % 2 != 0
val bgColor = if isLightSq then AnsiLightSquare else AnsiDarkSquare
board.pieceAt(sq) match
case Some(piece) =>
val fgColor = if piece.color == Color.White then AnsiWhitePiece else AnsiBlackPiece
s"$bgColor$fgColor ${piece.unicode} $AnsiReset"
case None =>
s"$bgColor $AnsiReset"
}.mkString
s"${rank + 1} $cells ${rank + 1}"
}.mkString("\n")
s" a b c d e f g h\n$rows\n a b c d e f g h\n"
@@ -0,0 +1,43 @@
package de.nowchess.ui.utils
import de.nowchess.api.board.Piece
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class PieceUnicodeTest extends AnyFunSuite with Matchers:
test("White King maps to "):
Piece.WhiteKing.unicode shouldBe "\u2654"
test("White Queen maps to ♕"):
Piece.WhiteQueen.unicode shouldBe "\u2655"
test("White Rook maps to "):
Piece.WhiteRook.unicode shouldBe "\u2656"
test("White Bishop maps to ♗"):
Piece.WhiteBishop.unicode shouldBe "\u2657"
test("White Knight maps to "):
Piece.WhiteKnight.unicode shouldBe "\u2658"
test("White Pawn maps to ♙"):
Piece.WhitePawn.unicode shouldBe "\u2659"
test("Black King maps to "):
Piece.BlackKing.unicode shouldBe "\u265A"
test("Black Queen maps to ♛"):
Piece.BlackQueen.unicode shouldBe "\u265B"
test("Black Rook maps to "):
Piece.BlackRook.unicode shouldBe "\u265C"
test("Black Bishop maps to ♝"):
Piece.BlackBishop.unicode shouldBe "\u265D"
test("Black Knight maps to "):
Piece.BlackKnight.unicode shouldBe "\u265E"
test("Black Pawn maps to ♟"):
Piece.BlackPawn.unicode shouldBe "\u265F"
@@ -0,0 +1,41 @@
package de.nowchess.ui.utils
import de.nowchess.api.board.*
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class RendererTest extends AnyFunSuite with Matchers:
test("render contains column header with all file labels"):
Renderer.render(Board.initial) should include("a b c d e f g h")
test("render output begins with the column header"):
Renderer.render(Board.initial) should startWith(" a b c d e f g h")
test("render contains rank labels 1 through 8"):
val output = Renderer.render(Board.initial)
for rank <- 1 to 8 do output should include(s"$rank ")
test("render shows white king unicode symbol for initial board"):
Renderer.render(Board.initial) should include("\u2654")
test("render shows black king unicode symbol for initial board"):
Renderer.render(Board.initial) should include("\u265A")
test("render contains ANSI light-square background code"):
Renderer.render(Board.initial) should include("\u001b[48;5;223m")
test("render contains ANSI dark-square background code"):
Renderer.render(Board.initial) should include("\u001b[48;5;130m")
test("render uses white-piece foreground color for white pieces"):
Renderer.render(Board.initial) should include("\u001b[97m")
test("render uses black-piece foreground color for black pieces"):
Renderer.render(Board.initial) should include("\u001b[30m")
test("render of empty board contains no piece unicode"):
val output = Renderer.render(Board(Map.empty))
output should include("a b c d e f g h")
output should not include "\u2654"
output should not include "\u265A"