Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions core/src/main/scala/Replay.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,8 @@ object Replay:
sans,
Tags(
List(
initialFen.map { fen =>
Tag(_.FEN, fen.value)
},
variant.some.filterNot(_.standard).map { v =>
Tag(_.Variant, v.name)
}
initialFen.map(fen => Tag(_.FEN, fen.value)),
variant.some.filterNot(_.standard).map(v => Tag(_.Variant, v.name))
).flatten
)
)
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/bitboard/Board.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ case class Board(
def byPiece(piece: Piece): Bitboard =
byColor(piece.color) & byRole(piece.role)

def byPiece(color: Color, role: Role): Bitboard =
byColor(color) & byRole(role)

def roleAt(s: Square): Option[Role] =
byRole.findRole(_.contains(s))

Expand Down
38 changes: 18 additions & 20 deletions core/src/main/scala/format/pgn/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,40 @@ object Parser:
// pgnComment with % or whitespaces
private val escape = pgnComment.? *> whitespace.rep0.?

final val pgnContext = "Error parsing PGN"

def full(pgn: PgnStr): Either[ErrorStr, ParsedPgn] =
pgnParser.parse(pgn.value, "Cannot parse pgn")
pgnParser.parse(pgn.value, pgnContext)

/**
* Parse the mainline with san moves and ignoring variations
*/
def mainline(pgn: PgnStr): Either[ErrorStr, ParsedMainline[San]] =
pgnSansParser.parse(pgn.value, "Cannot parse pgn")
pgnSansParser.parse(pgn.value, pgnContext)

/**
* Parse the mainline of a PGN file ignoring variations
* similar to the [[mainline]] but with metas
*/
def mainlineWithMetas(pgn: PgnStr): Either[ErrorStr, ParsedMainline[SanWithMetas]] =
pgnMainlineParser.parse(pgn.value, "Cannot parse pgn")
pgnMainlineParser.parse(pgn.value, pgnContext)

def tags(pgn: PgnStr): Either[ErrorStr, Tags] =
tagsParser.parse(pgn.value, "Cannot parse tags").map(Tags(_))
tagsParser.parse(pgn.value, "Error parsing tags").map(Tags(_))

def moves(strMoves: Iterable[SanStr]): Either[ErrorStr, Sans] =
strMoves.toList.traverse(san).map(Sans(_))

def moves(str: PgnMovesStr): Either[ErrorStr, Option[ParsedPgnTree]] =
moveParser.rep0
.parse(str.value, "Cannot parse moves")
.parse(str.value, "Error parsing moves")
.map(Tree.build)

def move(str: SanStr): Either[ErrorStr, ParsedPgnTree] =
moveParser.parse(str.value, "Cannot parse move")
moveParser.parse(str.value, "Error parsing move")

def san(str: SanStr): Either[ErrorStr, San] =
simpleSan.parse(str.value, "Cannot parse move")
simpleSan.parse(str.value, "Error parsing move")

private val blockComment = P.until0(P.char('}')).with1.between(P.char('{'), P.char('}')).map(Comment(_))
private val inlineComment = P.char(';') *> P.until(R.lf).map(Comment(_))
Expand Down Expand Up @@ -249,19 +251,15 @@ object Parser:
escapePgnTag(tagsAndMainlineParser(fullBody(sanOnly)))

extension [A](p: P0[A])
inline def parse(str: String, context: String): Either[ErrorStr, A] =
p.parse(str).bimap(err => showExpectations(context, str, err), _._2)

private def showExpectations(context: String, str: String, error: P.Error): ErrorStr =
val lm = LocationMap(str)
val idx = error.failedAtOffset
val caret = lm
.toCaret(idx)
.getOrElse(throw RuntimeException("This is impossible"))
val line = lm.getLine(caret.line).getOrElse("")
val errorLine = line ++ "\n" ++ " ".*(caret.col) ++ "^"
val errorMessage = s"$context: [${caret.line + 1}.${caret.col + 1}]: ${expToString(error.expected.head)}"
ErrorStr(s"$errorMessage\n\n$errorLine\n$str")
private inline def parse(str: String, context: String): Either[ErrorStr, A] =
p.parse(str).bimap(showExpectations(context, str), _._2)

private def showExpectations(context: String, str: String)(error: P.Error): ErrorStr =
val lm = LocationMap(str)
val idx = error.failedAtOffset
val caret = lm.toCaret(idx).getOrElse(throw RuntimeException("This is impossible"))
ErrorStr:
s"$context: ${expToString(error.expected.head)} at line ${caret.line + 1}, column ${caret.col + 1}"

private def expToString(expectation: Expectation): String =
expectation match
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/scala/format/pgn/Reader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ object Reader:
private def makeReplay(game: Game, sans: Sans): Result =
sans.value
.foldM(Replay(game)): (replay, san) =>
san(replay.state.situation)
.bimap((replay, _), replay.addMove(_))
san(replay.state.situation).bimap((replay, _), replay.addMove(_))
.match
case Left(replay, err) => Result.Incomplete(replay, err)
case Right(replay) => Result.Complete(replay)
Expand Down
20 changes: 7 additions & 13 deletions core/src/main/scala/format/pgn/parsingModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,14 @@ case class Std(
promotion: Option[PromotableRole] = None
) extends San:

def apply(situation: Situation) = move(situation)

def move(situation: Situation): Either[ErrorStr, chess.Move] =
def apply(situation: Situation): Either[ErrorStr, chess.Move] =
situation.board
.byPiece(situation.color - role)
.byPiece(situation.color, role)
.first: square =>
if compare(file, square.file) && compare(rank, square.rank)
then situation.generateMovesAt(square).find(_.dest == dest)
else None
.toRight(ErrorStr(s"No move found: $this\n$situation"))
.toRight(ErrorStr(s"Cannot play $this"))
.flatMap(_.withPromotion(promotion).toRight(ErrorStr("Wrong promotion")))

override def toString = s"$role ${dest.key}"
Expand All @@ -92,27 +90,23 @@ case class Std(

case class Drop(role: Role, square: Square) extends San:

def apply(situation: Situation) = drop(situation)

def drop(situation: Situation): Either[ErrorStr, chess.Drop] =
def apply(situation: Situation): Either[ErrorStr, chess.Drop] =
situation.drop(role, square)

case class Castle(side: Side) extends San:

def apply(situation: Situation) = move(situation)
def apply(situation: Situation): Either[ErrorStr, chess.Move] =

def move(situation: Situation): Either[ErrorStr, chess.Move] =
import situation.{ genCastling, ourKing, variant }
def error: ErrorStr = ErrorStr(s"Cannot castle / variant is $variant")
if !variant.allowsCastling then error.asLeft
if !variant.allowsCastling then ErrorStr(s"Cannot castle in $variant").asLeft
else
ourKing
.flatMap: k =>
variant
.applyVariantEffect(genCastling(k))
.filter(variant.kingSafety)
.find(_.castle.exists(_.side == side))
.toRight(error)
.toRight(ErrorStr(s"Cannot castle ${side.fold("kingside", "queenside")}"))

opaque type Sans = List[San]
object Sans extends TotalWrapper[Sans, List[San]]
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/variant/Variant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ abstract class Variant private[variant] (
yield m3

def drop(situation: Situation, role: Role, square: Square): Either[ErrorStr, Drop] =
ErrorStr(s"$this variant cannot drop $situation $role $square").asLeft
ErrorStr(s"$this variant cannot drop $role $square").asLeft

def staleMate(situation: Situation): Boolean = situation.check.no && situation.legalMoves.isEmpty

Expand Down