feat(notation): add san move formatting and parsing
This commit is contained in:
1 parent
cc8a4221d9
commit
1a4deee98a
1 file changed
+113
@@ -0,0 +1,113 @@
|
||||
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
|
||||
Reference in new issue
Block a user