feat(io): implement PgnParser.importGameContext with move replay

Implement the importGameContext method to validate PGN input via validatePgn()
and replay each move using DefaultRules.applyMove to populate GameContext.moves.
Returns final GameContext with all moves applied or error message on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 13:14:25 +02:00
parent 9f48f7afeb
commit 7c42260d14
@@ -23,6 +23,35 @@ object PgnParser:
val moveText = rest.mkString(" ")
validateMovesText(moveText).map(moves => PgnGame(headers, moves))
/** Import a PGN text into a GameContext by validating and replaying all moves.
* Returns Right(GameContext) with all moves applied and .moves populated.
* Returns Left(error message) if validation fails or move replay encounters an issue. */
def importGameContext(input: String): Either[String, GameContext] =
validatePgn(input).flatMap { game =>
// Replay moves to populate GameContext.moves via DefaultRules.applyMove
val (finalCtx, errors) = game.moves.foldLeft((GameContext.initial, Option.empty[String])) {
case ((ctx, Some(err)), _) => (ctx, Some(err)) // Already failed, stop
case ((ctx, None), histMove) =>
val moveOpt = parseAlgebraicMove(
s"${histMove.from}${histMove.to}",
ctx,
ctx.turn
)
moveOpt match
case None => (ctx, Some(s"Failed to parse move ${histMove.from}${histMove.to}"))
case Some(move) =>
val nextCtx = DefaultRules.applyMove(ctx, move)
(nextCtx, None)
}
errors match
case Some(err) => Left(err)
case None =>
if finalCtx.moves.isEmpty && game.moves.nonEmpty then
Left("No moves were parsed from the PGN")
else
Right(finalCtx)
}
/** Parse a complete PGN text into a PgnGame with headers and moves.
* Always succeeds (returns Some); malformed tokens are silently skipped. */
def parsePgn(pgn: String): Option[PgnGame] =