Method to interpret the third field of a FEN string in a program playing FRC

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
Roland Chastain
Posts: 640
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Method to interpret the third field of a FEN string in a program playing FRC

Post by Roland Chastain »

Hello!

I tried to solve the following problem. For a program playing Fischer random chess, how to read the third field of a FEN string, without knowing what the notation style will be ("KQkq" or "HAha").

Here is my solution. I would be glad to know what you think about the method I found. I feel that it is too complicated, but I didn't find something simpler.

The function returns for each color and each side the position of the king and of the rook, or nil value if the castling isn't available.

Code: Select all

-- Lua function for an engine playing Fischer random chess.
-- Extracts castling data from a FEN string, accepting both notation style.

function EncodeCastling(
  AFen3,
  ABoard -- The chessboard is needed as second parameter
)
  
  function GetRookFile(
    AColor,   -- Piece color
    APattern, -- Regular expression for character detection
    AFrom,    -- Where to start searching for the rook
    ATo,      -- Where to stop
    AStep,    -- In which direction to search
    AKey      -- K, Q, k, q
  )
    local LResult = 0
    for LMatch in string.gmatch(AFen3, APattern) do
      local LRook = AColor and 'r' or 'R'
      local LFile = AColor and 'a' or 'A'
      local LRank = AColor and  8  or  1
      for x = AFrom, ATo, AStep do
        if LResult == 0 then
          if (ABoard[x][LRank] == LRook) and (
            (LMatch == string.char(string.byte(LFile) + x - 1)) -- B-H, A-G, b-h, a-g
            or (LMatch == AKey)                                 -- K, Q, k, q
          ) then
            LResult = x
            break
          end
        end
      end
    end
    return LResult
  end

  local K, Q, k, q, X = nil, nil, nil, nil, nil
  
  local IsWhiteCastlingAvailable = string.match(AFen3, "[ABCDEFGHKQ]")
  if IsWhiteCastlingAvailable then
    for x = 1, 8 do
      if ABoard[x][1] == 'K' then
        X = x
        break
      end
    end
  end
  
  local IsBlackCastlingAvailable = string.match(AFen3, "[abcdefghkq]")
  if IsBlackCastlingAvailable and (X == nil) then
    for x = 1, 8 do
      if ABoard[x][8] == 'k' then
        X = x
        break
      end
    end
  end
  
  if IsWhiteCastlingAvailable then
    K = GetRookFile(false, "[BCDEFGHK]", 8, X, -1, "K")
    Q = GetRookFile(false, "[ABCDEFGQ]", 1, X,  1, "Q")
  end
  
  if IsBlackCastlingAvailable then
    k = GetRookFile(true,  "[bcdefghk]", 8, X, -1, "k")
    q = GetRookFile(true,  "[abcdefgq]", 1, X,  1, "q")
  end

  return {K = K, Q = Q, k = k, q = q, X = X}
end 

-- demo

require('chess')

function Test(AFen)
  local LPos = EncodePosition(AFen)
  local LCastling = EncodeCastling(LPos.castlingAvailability, LPos.piecePlacement)
  print("white k. side", LCastling.K) -- Rook file or nil when no castling is available
  print("white q. side", LCastling.Q) -- Idem
  print("black k. side", LCastling.k) -- Idem
  print("black q. side", LCastling.q) -- Idem
  print("king", LCastling.X)          -- King file or nil when no castling is available
end

local LSample = {
    "rknbbqnr/pppppppp/8/8/8/8/PPPPPPPP/RKNBBQNR w HAha - 0 1",
    "nrbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/NRBKQBNR w KQkq - 0 1",
    "qrbknbrn/pppppppp/8/8/8/8/PPPPPPPP/QRBKNBRN w GBgb - 0 1",
    "nrbkqrnb/pppppppp/8/8/8/8/PPPPPPPP/NRBKQRNB w FBfb - 0 1",
    "qnrbbknr/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR w HChc - 0 1",
    "rnb1k1nr/p1pp1ppp/4p3/1p6/1P6/P1N1PN2/2P2P1P/R1BQKB1q b Qkq - 1 10",
    "rnb1k2r/pppp1pp1/4p2p/8/8/2bPPN2/P2B1PqP/R2QKR2 b Qkq - 1 13"
  }

for i = 1, #LSample do
  Test(LSample[i])
end

Code: Select all

white k. side   8
white q. side   1
black k. side   8
black q. side   1
king    2
white k. side   8
white q. side   2
black k. side   8
black q. side   2
king    4
white k. side   7
white q. side   2
black k. side   7
black q. side   2
king    4
white k. side   6
white q. side   2
black k. side   6
black q. side   2
king    4
white k. side   8
white q. side   3
black k. side   8
black q. side   3
king    6
white k. side   0
white q. side   1
black k. side   8
black q. side   1
king    5
white k. side   0
white q. side   1
black k. side   8
black q. side   1
king    5
If you want to try the Lua script, you need the module chess.lua from the Luciole projet.

Thank you.
Qui trop embrasse mal étreint.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: Method to interpret the third field of a FEN string in a program playing FRC

Post by mar »

Well, in X-FEN you don't specify the rook file (AHah - that's Shredder notation if I'm not mistaken), so you simply have to scan for the rooks to determine rook file.

I think this is probably what you do.
It's a bit trickier if you have both rooks on either side, for more information: https://en.wikipedia.org/wiki/X-FEN
Martin Sedlak
User avatar
Roland Chastain
Posts: 640
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: Method to interpret the third field of a FEN string in a program playing FRC

Post by Roland Chastain »

mar wrote: Thu May 16, 2019 11:01 pm Well, in X-FEN you don't specify the rook file (AHah - that's Shredder notation if I'm not mistaken), so you simply have to scan for the rooks to determine rook file.

I think this is probably what you do.
It's a bit trickier if you have both rooks on either side, for more information: https://en.wikipedia.org/wiki/X-FEN
Thank you for your answer and for the useful link. It seems that I followed the good method (if not wrote the most efficient and short code). :)

I completed and cleaned up my script. Now it can also convert internal castling data to FEN string (X-FEN or Shredder-FEN style).

Code: Select all

function FileLetter(AFile, AColor)
  return string.char(string.byte(AColor and 'a' or 'A') + AFile - 1)
end

function EncodeCastling(AFen3, ABoard)
  
  function RookFile(
    AColor,
    APattern,
    AFrom,
    ATo,
    AStep,
    AKey
  )
    local LResult = nil
    for LMatch in string.gmatch(AFen3, APattern) do
      local LRook = AColor and 'r' or 'R'
      local LRank = AColor and  8  or  1
      for x = AFrom, ATo, AStep do
        if LResult == nil then
          if (ABoard[x][LRank] == LRook) and ((LMatch == FileLetter(x, AColor)) or (LMatch == AKey)) then
            LResult = x
            break
          end
        end
      end
    end
    return LResult
  end

  local K, Q, k, q, X = nil, nil, nil, nil, nil
  local WhiteCastling = string.match(AFen3, "[ABCDEFGHKQ]")
  local BlackCastling = string.match(AFen3, "[abcdefghkq]")
  
  if WhiteCastling then
    for x = 1, 8 do
      if ABoard[x][1] == 'K' then
        X = x
        break
      end
    end
    K = RookFile(false, "[BCDEFGHK]", 8, X, -1, "K")
    Q = RookFile(false, "[ABCDEFGQ]", 1, X,  1, "Q")
  end
  
  if BlackCastling then
    if X == nil then
      for x = 1, 8 do
        if ABoard[x][8] == 'k' then
          X = x
          break
        end
      end
    end
    k = RookFile(true,  "[bcdefghk]", 8, X, -1, "k")
    q = RookFile(true,  "[abcdefgq]", 1, X,  1, "q")
  end

  return {K = K, Q = Q, k = k, q = q, X = X}
end 

function RookInFront(ABoard, AColor, AFrom, ATo, AStep)
  local LRook = AColor and 'r' or 'R'
  local LRank = AColor and  8  or  1
  for x = AFrom, ATo - AStep, AStep do
    if ABoard[x][LRank] == LRook then
      return true
    end
  end
  return false
end

function DecodeCastling(ACastling, ABoard, AAlwaysFileLetter)
  local LResult = ""
  if AAlwaysFileLetter then
    if ACastling.K then LResult = LResult .. FileLetter(ACastling.K, false) end
    if ACastling.Q then LResult = LResult .. FileLetter(ACastling.Q, false) end
    if ACastling.k then LResult = LResult .. FileLetter(ACastling.k,  true) end
    if ACastling.q then LResult = LResult .. FileLetter(ACastling.q,  true) end
  else
    if ACastling.K then LResult = LResult .. (RookInFront(ABoard, false, 8, ACastling.K,-1) and FileLetter(ACastling.K, false) or "K") end
    if ACastling.Q then LResult = LResult .. (RookInFront(ABoard, false, 1, ACastling.Q, 1) and FileLetter(ACastling.Q, false) or "Q") end
    if ACastling.k then LResult = LResult .. (RookInFront(ABoard, true,  8, ACastling.k,-1) and FileLetter(ACastling.k, true)  or "k") end
    if ACastling.q then LResult = LResult .. (RookInFront(ABoard, true,  1, ACastling.q, 1) and FileLetter(ACastling.q, true)  or "q") end
  end
  return LResult
end

require('chess')

function Test(AFen)
  local LPos = EncodePosition(AFen)
  local LCastling = EncodeCastling(LPos.castlingAvailability, LPos.piecePlacement)
  print("-- " .. AFen)
  print("rook file white k. side:", LCastling.K)
  print("          white q. side:", LCastling.Q)
  print("          black k. side:", LCastling.k)
  print("          black q. side:", LCastling.q)
  print("king file              :", LCastling.X)
  print("X-FEN                  :", DecodeCastling(LCastling, LPos.piecePlacement, false))
  print("S-FEN                  :", DecodeCastling(LCastling, LPos.piecePlacement, true)) -- Shredder-FEN
end

local LSample = {
    "rknbbqnr/pppppppp/8/8/8/8/PPPPPPPP/RKNBBQNR w HAha - 0 1",
    "nrbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/NRBKQBNR w KQkq - 0 1",
    "qrbknbrn/pppppppp/8/8/8/8/PPPPPPPP/QRBKNBRN w GBgb - 0 1",
    "nrbkqrnb/pppppppp/8/8/8/8/PPPPPPPP/NRBKQRNB w FBfb - 0 1",
    "qnrbbknr/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR w HChc - 0 1",
    "rnb1k1nr/p1pp1ppp/4p3/1p6/1P6/P1N1PN2/2P2P1P/R1BQKB1q b Qkq - 1 10",
    "rnb1k2r/pppp1pp1/4p2p/8/8/2bPPN2/P2B1PqP/R2QKR2 b Qkq - 1 13",
    "rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR w Gkq - 4 11"
  }

for i = 1, #LSample do
  Test(LSample[i])
end
Qui trop embrasse mal étreint.