packages feed

turncoat-1.0: src/Parsers/Position.hs

module Parsers.Position (positionFenParser, squareParser) where

import           AppPrelude

import           Bookhound.Parser
import           Bookhound.ParserCombinators
import           Bookhound.Parsers.Char      (space)
import           Bookhound.Parsers.Number    (unsignedInt)
import           Data.Char                   (digitToInt)

import           Models.Piece
import           Models.Position
import           Models.Score
import           MoveGen.MakeMove
import           Utils.Board


positionFenParser :: Parser Position
positionFenParser = do
  (pieces, color, castling, enPassant, halfMoveClock, _) <- position
  pure
    $ newPosition
    $ foldrFlipped includePiece pieces
    $ foldrFlipped includeCastling castling
    $ foldrFlipped includeEnPassant enPassant
    $ includeHalfMoveClock halfMoveClock
    $ includeColor color
    emptyPosition
  where
  position = (,,,,,)
    <$> (piecesP    <* space)
    <*> (colorP     <* space)
    <*> (castlingP  <* space)
    <*> (enPassantP <* space)
    <*> (ply        <* space)
    <*> unsignedInt
  piecesP =
    map (mapMaybe (\(x, y) -> (x,) <$> y) . zip [0 ..] . fold . reverse)
    $ satisfy lengthCheck
    $ satisfy (all lengthCheck)
    $ manySepBy (is '/') (fold <$> ((emptySquaresN <|> piece) |+))
  colorP = mandatory (map charToColor anyChar)
  castlingP = (mandatory (map charToCastlingRights anyChar) |+)
           <|> [] <$ is '-'
  enPassantP = Just <$> squareParser
           <|> Nothing <$ is '-'
  emptySquaresN = (`replicate` Nothing) . digitToInt <$> oneOf ['1' .. '8']
  piece = pure . pure <$> mandatory (map charToPiece anyChar)
  lengthCheck xs = length xs == 8
  ply = fromIntegral <$> unsignedInt
  mandatory = (=<<) (fromMaybe empty . map pure)
  foldrFlipped f = flip $ foldr f


squareParser :: Parser Square
squareParser = (+) <$> column <*> map (* 8) row
  where
    column = (\x -> x - fromEnum 'a') . fromEnum <$> oneOf ['a' .. 'h']
    row = (\x -> x - 1) . digitToInt <$> oneOf ['1' .. '8']


newPosition :: Position -> Position
newPosition = setInitialValues . makeNullMove . makeNullMove
  where
  setInitialValues pos = pos {
    phase = getPhase pos
  }


includePiece :: (Square, (Piece, Color)) -> Position -> Position
includePiece (square, (piece, pieceColor)) pos@Position {..} =
  if pieceColor == color then
    pos' { player = player .| board }
  else
    pos' { enemy  = enemy  .| board }
  where
  pos' = case piece of
    Pawn   -> pos { pawns   = pawns   .| board }
    Knight -> pos { knights = knights .| board }
    Bishop -> pos { bishops = bishops .| board }
    Rook   -> pos { rooks   = rooks   .| board }
    Queen  -> pos { queens  = queens  .| board }
    King   -> pos { kings   = kings   .| board }
  board = toBoard square


includeHalfMoveClock :: Ply -> Position -> Position
includeHalfMoveClock halfMoveClock pos =
  pos { halfMoveClock = halfMoveClock }


includeColor :: Color -> Position -> Position
includeColor color pos =
  pos { color = color }


includeCastling :: (CastlingRights, Color) -> Position -> Position
includeCastling (castlingRights, castlingColor) pos@Position {..} =
  pos { castling = castling .| row & (column .| file_E) }
  where
  row = case castlingColor of
    White -> rank_1
    Black -> rank_8
  column = case castlingRights of
    QueenSide -> file_A
    KingSide  -> file_H


includeEnPassant :: Square -> Position -> Position
includeEnPassant square pos =
  pos { enPassant = toBoard square }