packages feed

turncoat-1.0: src/Models/Position.hs

module Models.Position where


import           AppPrelude
import           Models.Move        (foldlBoard)
import           Models.Piece
import           Models.Score
import           Utils.Board

import           Data.Bitraversable (bisequence)
import           Data.List.Split    (chunksOf)
import           Data.Maybe         (fromJust)
import           Test.QuickCheck    (Arbitrary (..))


data Position = Position {
    previousPositions :: ~[ZKey]
  , halfMoveClock     :: Ply
  , phase             :: Phase
  , color             :: Color
  , player            :: Board
  , enemy             :: Board
  , pawns             :: Board
  , knights           :: Board
  , bishops           :: Board
  , rooks             :: Board
  , queens            :: Board
  , kings             :: Board
  , enPassant         :: Board
  , castling          :: Board
  , attacked          :: Board
  , leapingCheckers   :: Board
  , sliderCheckers    :: Board
  , pinnedPieces      :: Board
}

newtype ZKey = ZKey Word64
  deriving (Eq, Show, Generic, Ord, Num, Hashable, Storable, Arbitrary)


startPosition :: Position
startPosition = emptyPosition {
    color    = White
  , phase    = totalPhase
  , player   = rank_1  .| rank_2
  , enemy    = rank_7  .| rank_8
  , pawns    = rank_2  .| rank_7
  , rooks    = (rank_1 .| rank_8) & (file_A .| file_H)
  , knights  = (rank_1 .| rank_8) & (file_B .| file_G)
  , bishops  = (rank_1 .| rank_8) & (file_C .| file_F)
  , queens   = (rank_1 .| rank_8) & file_D
  , kings    = (rank_1 .| rank_8) & file_E
  , castling = (rank_1 .| rank_8) & (file_A .| file_E .| file_H)
}


emptyPosition :: Position
emptyPosition = Position {
    previousPositions = []
  , color             = White
  , halfMoveClock     = 0
  , phase             = 0
  , player            = 0
  , enemy             = 0
  , pawns             = 0
  , rooks             = 0
  , knights           = 0
  , bishops           = 0
  , queens            = 0
  , kings             = 0
  , castling          = 0
  , attacked          = 0
  , enPassant         = 0
  , leapingCheckers   = 0
  , sliderCheckers    = 0
  , pinnedPieces      = 0
}


{-# INLINE  getZobristKey #-}
getZobristKey :: Position -> ZKey
getZobristKey pos@Position {..} = ZKey
  (piecesHash ^ castlingHash ^ enPassantHash ^ sideToMoveHash)
  where

    !piecesHash =
      foldlBoard 0 (^) getPieceHash (player .| enemy)

    !castlingHash = castlingRngVec !! idx
      where
      idx =   inBoard file_A + 2 * inBoard file_H
        + 4 * inBoard rank_1 + 8 * inBoard rank_8
      inBoard x = fromIntegral $ toCondition (castling & x)

    !enPassantHash = toCondition enPassant * enPassantRngVec !! idx
      where
        idx = toFile (lsb enPassant)

    !sideToMoveHash = fromIntegral colorN * sideToMoveRng
      where
        Color colorN = color

    getPieceHash n = pieceRngVec !! idx
      where
      idx = n + 64 * (fromIntegral piece + 6 * fromIntegral pieceColor)
      (Piece piece, Color pieceColor) = fromJust $ pieceAt n pos


{-# INLINE  pieceAt #-}
pieceAt :: Square -> Position -> Maybe (Piece, Color)
pieceAt n (Position {..}) = bisequence (piece, color')
  where
    piece  | testSquare pawns n   = Just Pawn
           | testSquare knights n = Just Knight
           | testSquare bishops n = Just Bishop
           | testSquare rooks n   = Just Rook
           | testSquare queens n  = Just Queen
           | testSquare kings n   = Just King
           | otherwise            = Nothing

    color' | testSquare player n = Just color
           | testSquare enemy n  = Just $ reverseColor color
           | otherwise           = Nothing


{-# INLINE  maybeCapturedPieceAt #-}
maybeCapturedPieceAt :: Square -> Position -> Maybe Piece
maybeCapturedPieceAt n (Position {..})
  | testSquare pawns n   = Just Pawn
  | testSquare knights n = Just Knight
  | testSquare bishops n = Just Bishop
  | testSquare rooks n   = Just Rook
  | testSquare queens n  = Just Queen
  | otherwise            = Nothing


{-# INLINE  isPieceAt #-}
isPieceAt :: Piece -> Square -> Position -> Bool
isPieceAt piece n Position {..} =
  case piece of
    Pawn   -> testSquare pawns n
    Knight -> testSquare knights n
    Bishop -> testSquare bishops n
    Rook   -> testSquare rooks n
    Queen  -> testSquare queens n
    King   -> testSquare kings n


{-# INLINE  isRepeatedPosition #-}
isRepeatedPosition :: ZKey -> Position -> Bool
isRepeatedPosition zKey Position {..} =
  zKey `elem` previousPositions


{-# INLINE  getPhase #-}
getPhase :: Position -> Phase
getPhase Position {..} = min totalPhase
   (minorPiecePhase * fromIntegral (popCount (knights .| bishops))
  + rookPhase       * fromIntegral (popCount rooks)
  + queenPhase      * fromIntegral (popCount queens))


instance Show Position where
  show pos =
    show pos.color <> " to play: \n\n" <> boardStr
    where
      boardStr = intercalate "\n" (intersperse ' ' <$> pieces)
      pieces = reverse $ chunksOf 8 do
        n <- toList squares
        pure $ maybe ' ' pieceToChar (pieceAt n pos)