Files

114 lines
4.1 KiB
Scala

package gambit
object San:
private def pieceLetter(pieceType: Int): String = pieceType match
case Piece.Knight => "N"
case Piece.Bishop => "B"
case Piece.Rook => "R"
case Piece.Queen => "Q"
case Piece.King => "K"
case _ => ""
private def letterToType(ch: Char): Int = ch match
case 'N' => Piece.Knight
case 'B' => Piece.Bishop
case 'R' => Piece.Rook
case 'Q' => Piece.Queen
case 'K' => Piece.King
case _ => Piece.Empty
def toSan(pos: Position, move: Int): String =
val from = Move.from(move)
val to = Move.to(move)
val pieceType = Piece.typeOf(pos.board(from))
val body =
if Move.isCastle(move) then
if Square.fileOf(to) == 6 then "O-O" else "O-O-O"
else if pieceType == Piece.Pawn then
val core =
if Move.isCapture(move) then s"${('a' + Square.fileOf(from)).toChar}x${Square.name(to)}"
else Square.name(to)
if Move.isPromotion(move) then s"$core=${pieceLetter(Move.promo(move))}" else core
else
val capture = if Move.isCapture(move) then "x" else ""
s"${pieceLetter(pieceType)}${disambiguation(pos, move, pieceType, from, to)}$capture${Square.name(to)}"
body + suffix(pos, move)
private def disambiguation(pos: Position, move: Int, pieceType: Int, from: Int, to: Int): String =
val legal = new MoveList()
MoveGen.generateLegal(pos, legal)
var sameFile = false
var sameRank = false
var rival = false
var i = 0
while i < legal.size do
val other = legal(i)
if other != move && Move.to(other) == to && Piece.typeOf(pos.board(Move.from(other))) == pieceType then
rival = true
if Square.fileOf(Move.from(other)) == Square.fileOf(from) then sameFile = true
if Square.rankOf(Move.from(other)) == Square.rankOf(from) then sameRank = true
i += 1
if !rival then ""
else if !sameFile then s"${('a' + Square.fileOf(from)).toChar}"
else if !sameRank then s"${('1' + Square.rankOf(from)).toChar}"
else Square.name(from)
private def suffix(pos: Position, move: Int): String =
val undo = pos.make(move)
val opponent = pos.sideToMove
val check = pos.inCheck(opponent)
val replies = new MoveList()
MoveGen.generateLegal(pos, replies)
val out = if check && replies.size == 0 then "#" else if check then "+" else ""
pos.unmake(move, undo)
out
def parse(pos: Position, text: String): Int =
var s = text.trim
while s.nonEmpty && "+#!?".indexOf(s.last) >= 0 do s = s.dropRight(1)
if s == "O-O" || s == "0-0" then castleMove(pos, 6)
else if s == "O-O-O" || s == "0-0-0" then castleMove(pos, 2)
else
var promo = 0
val eq = s.indexOf('=')
if eq >= 0 then
promo = letterToType(s(eq + 1))
s = s.take(eq)
val isPawn = letterToType(s(0)) == Piece.Empty
val pieceType = if isPawn then Piece.Pawn else letterToType(s(0))
var body = if isPawn then s else s.drop(1)
body = body.filter(_ != 'x')
val dest = Square.fromName(body.takeRight(2))
val hint = body.dropRight(2)
var hintFile = -1
var hintRank = -1
for ch <- hint do
if ch >= 'a' && ch <= 'h' then hintFile = ch - 'a'
if ch >= '1' && ch <= '8' then hintRank = ch - '1'
matchMove(pos, pieceType, dest, promo, hintFile, hintRank)
private def castleMove(pos: Position, toFile: Int): Int =
val legal = new MoveList()
MoveGen.generateLegal(pos, legal)
var i = 0
while i < legal.size do
val m = legal(i)
if Move.isCastle(m) && Square.fileOf(Move.to(m)) == toFile then return m
i += 1
0
private def matchMove(pos: Position, pieceType: Int, dest: Int, promo: Int, hintFile: Int, hintRank: Int): Int =
val legal = new MoveList()
MoveGen.generateLegal(pos, legal)
var i = 0
while i < legal.size do
val m = legal(i)
val from = Move.from(m)
if Piece.typeOf(pos.board(from)) == pieceType && Move.to(m) == dest
&& (promo == 0 || Move.promo(m) == promo)
&& (hintFile < 0 || Square.fileOf(from) == hintFile)
&& (hintRank < 0 || Square.rankOf(from) == hintRank)
then return m
i += 1
0