packages feed

turncoat-1.0: src/Utils/Board.hs

{- HLINT ignore "Use camelCase" -}

module Utils.Board (Board, Square, (>>), (<<), (&), (.\), (.|), (^), (~),  popCount, popCountToBoard, lsb, msb, toBoard, toCondition, getSquareDistance, toReverseCondition, testSquare, toFile, toRank, knightMovesVec, kingMovesVec, fileMovesVec, rankMovesVec, diagMovesVec, antiDiagMovesVec, northEastMovesVec, northWestMovesVec, southEastMovesVec, southWestMovesVec, northMovesVec, westMovesVec, southMovesVec, eastMovesVec, shortCastleSlidingFiles, longCastleSlidingFiles, shortCastleFiles, longCastleFiles, whiteKnightOutpostRanks, blackKnightOutpostRanks, knightOupostFiles, castlingRngVec, enPassantRngVec, sideToMoveRng, pieceRngVec, whiteKnightOutpostAttackersVec, blackKnightOutpostAttackersVec, whitePassedPawnBlockersVec, blackPassedPawnBlockersVec, squares, rank_1, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, file_A, file_B, file_C, file_D, file_E, file_F, file_G, file_H)  where

import           AppPrelude           hiding (foldl', map)

import qualified Data.Bits            as Bits
import           Data.Vector.Storable (foldl', foldl1, foldl1', map, slice)
import           System.IO.Unsafe     (unsafePerformIO)
import           System.Random

type Board = Word64

type Square = Int
type SideSquare = Int
type Diagonal = Int
type Rank = Int
type File = Int
type Diag = Int


{-# SPECIALIZE (<<) :: Board -> Square -> Board #-}
infixl 9 <<
(<<) :: Bits.Bits a => a -> Square -> a
(<<) !x !y = Bits.unsafeShiftL x y

{-# SPECIALIZE (>>) :: Board -> Square -> Board #-}
infixl 9 >>
(>>) :: Bits.Bits a => a -> Square -> a
(>>) !x !y = Bits.unsafeShiftR x y

infixl 8 &
(&) :: Board -> Board -> Board
(&) !x !y = (Bits..&.) x y

infixl 7 .\
(.\) :: Board -> Board -> Board
(.\) !x !y = x & (~) y

infixl 7 .|
(.|) :: Board -> Board -> Board
(.|) !x !y = (Bits..|.) x y

infixl 7 ^
(^) :: Board -> Board -> Board
(^) !x !y = Bits.xor x y

infixl 9 ~
(~) :: Board -> Board
(~) !x = Bits.complement x

popCount :: Board -> Int
popCount !x = Bits.popCount x

popCountToBoard :: Board -> Board
popCountToBoard !x = fromIntegral $! popCount x

lsb :: Board -> Square
lsb !x = Bits.countTrailingZeros x

msb :: Board -> Square
msb !x = 65 * (zeros / 64) + 63 - zeros
  where
    !zeros = Bits.countLeadingZeros x

{-# SPECIALIZE toReverseCondition :: Board -> Board #-}
{-# SPECIALIZE toReverseCondition :: Square -> Square #-}
toReverseCondition :: (Num a, Ord a) => a -> a
toReverseCondition !x = 1 - toCondition x

{-# SPECIALIZE toCondition :: Board -> Board #-}
{-# SPECIALIZE toCondition :: Square -> Square #-}
toCondition :: (Ord a, Num a) => a -> a
toCondition !x = min 1 x

toBoard :: Square -> Board
toBoard !n = fromIntegral (1 - n / 64) * (1 << n)

testSquare :: Board -> Square -> Bool
testSquare !x !n = x & toBoard n /= 0

toFile :: Square -> File
toFile !n = n % 8

toRank :: Square -> Rank
toRank !n = n / 8

knightMove :: Square -> Board
knightMove n =
    move1 .| move2 .| move3 .| move4 .| move5
          .| move6 .| move7 .| move8
  where
    move1 = (board .\ (file_H .| file_G .| rank_8)) << 10
    move3 = (board .\ (file_H .| rank_7 .| rank_8)) << 17
    move4 = (board .\ (file_A .| rank_7 .| rank_8)) << 15
    move5 = (board .\ (file_A .| file_B .| rank_8)) << 6
    move6 = (board .\ (file_A .| file_B .| rank_1)) >> 10
    move7 = (board .\ (file_A .| rank_2 .| rank_1)) >> 17
    move8 = (board .\ (file_H .| rank_2 .| rank_1)) >> 15
    move2 = (board .\ (file_H .| file_G .| rank_1)) >> 6
    board = toBoard n

kingMove :: Square -> Board
kingMove n =
    move1 .| move2 .| move3 .| move4 .| move5
          .| move6 .| move7 .| move8
  where
    move1 = (board .\ file_H) << 1
    move2 = (board .\ (file_H .| rank_8)) << 9
    move3 = (board .\ file_A) >> 1
    move4 = (board .\ (file_A .| rank_1)) >> 9
    move5 = (board .\ rank_8) << 8
    move6 = (board .\ (file_A .| rank_8)) << 7
    move7 = (board .\ rank_1) >> 8
    move8 = (board .\ (file_H .| rank_1)) >> 7
    board = toBoard n


knightOutpostAttackers :: Bool -> Int -> Board
knightOutpostAttackers above n = filesBoard & ranksBoard
  where
    filesBoard = getPreviousFile file .| getNextFile file
    ranksBoard = foldl' (.|) 0
      $ map getRank
      $ filter (compareFn rank) sideSquares
    rank = toRank n
    file = toFile n
    compareFn | above     = (<)
              | otherwise = (>)

passedPawnBlockers :: Bool -> Int -> Board
passedPawnBlockers above n = filesBoard & ranksBoard
  where
    filesBoard = getPreviousFile file .| getFile file .| getNextFile file
    ranksBoard = foldl' (.|) 0
      $ map getRank
      $ filter (compareFn rank) sideSquares
    rank = toRank n
    file = toFile n
    compareFn | above     = (<)
              | otherwise = (>)


squareDistance :: Square -> Square -> Int
squareDistance n1 n2 = max fileDistance rankDistance
  where
    fileDistance = abs (file1 - file2)
    rankDistance = abs (rank1 - rank2)
    file1        = toFile n1
    file2        = toFile n2
    rank1        = toRank n1
    rank2        = toRank n2


fileMove :: Square -> Board
fileMove n = ranks !! toRank n

rankMove :: Square -> Board
rankMove n = files !! toFile n

diagMove :: Square -> Board
diagMove n = antiDiags !! (toFile n + toRank n)

antiDiagMove :: Square -> Board
antiDiagMove n = diags !! (7 - toFile n + toRank n)

getRank :: Rank -> Board
getRank n = foldl1' (.|) (map f sideSquares)
  where
    f = (1 << (8 * n) <<)

getFile :: File -> Board
getFile n = foldl1' (.|) (map f sideSquares)
  where
    f = (1 << n <<) . (* 8)

getPreviousFile :: File -> Board
getPreviousFile n
  | n == 0 = 0
  | otherwise = getFile (n - 1)

getNextFile :: File -> Board
getNextFile n
  | n == 7 = 0
  | otherwise = getFile (n + 1)

getSquareDistance :: Square -> Square -> Int
getSquareDistance n1 n2 =
  squareDistanceVec !! (n1 * 64 + n2)


getDiag :: Diag -> Board
getDiag = diagHelper (7 -)

getAntiDiag :: Int -> Board
getAntiDiag = diagHelper id

diagHelper :: (Int -> Int) -> Int -> Board
diagHelper f n = foldl1 (.|) xs
  where
  xs = fromList do
    x <- toList sideSquares
    let y = f (n - x) `rem` 8
    guard $ inRange 0 7 (n - x)
    pure $ toBoard (y + 8 * x)


-- Move Functions
northMove :: Square -> Board
northMove n = aboveMask n & rankMovesVec !! n

eastMove :: Square -> Board
eastMove n = aboveMask n & fileMovesVec !! n

westMove :: Square -> Board
westMove n = belowMask n & fileMovesVec !! n

southMove :: Square -> Board
southMove n = belowMask n & rankMovesVec !! n

northWestMove :: Square -> Board
northWestMove n = aboveMask n & diagMovesVec !! n

northEastMove :: Square -> Board
northEastMove n = aboveMask n & antiDiagMovesVec !! n

southEastMove :: Square -> Board
southEastMove n = belowMask n & diagMovesVec !! n

southWestMove :: Square -> Board
southWestMove n = belowMask n & antiDiagMovesVec !! n


-- Masks
aboveMask :: Square -> Board
aboveMask n = (toBoard 63 - toBoard n) << 1

belowMask :: Square -> Board
belowMask n = toBoard n - 1


-- Directions
ranks :: Vector Board
ranks = map getRank sideSquares

files :: Vector Board
files = map getFile sideSquares

diags :: Vector Board
diags = map getDiag diagonals

antiDiags :: Vector Board
antiDiags = map getAntiDiag diagonals


-- Cached Piece moves
knightMovesVec :: Vector Board
knightMovesVec = map knightMove squares

kingMovesVec :: Vector Board
kingMovesVec = map kingMove squares


-- Sliding moves
fileMovesVec :: Vector Board
fileMovesVec = snoc (map fileMove squares) 0

rankMovesVec :: Vector Board
rankMovesVec = snoc (map rankMove squares) 0

diagMovesVec :: Vector Board
diagMovesVec = snoc (map diagMove squares) 0

antiDiagMovesVec :: Vector Board
antiDiagMovesVec = snoc (map antiDiagMove squares) 0

westMovesVec :: Vector Board
westMovesVec = snoc (map westMove squares) 0

northMovesVec :: Vector Board
northMovesVec = snoc (map northMove squares) 0

eastMovesVec :: Vector Board
eastMovesVec = snoc (map eastMove squares) 0

southMovesVec :: Vector Board
southMovesVec = snoc (map southMove squares) 0

northWestMovesVec :: Vector Board
northWestMovesVec = snoc (map northWestMove squares) 0

northEastMovesVec :: Vector Board
northEastMovesVec = snoc (map northEastMove squares) 0

southEastMovesVec :: Vector Board
southEastMovesVec = snoc (map southEastMove squares) 0

southWestMovesVec :: Vector Board
southWestMovesVec = snoc (map southWestMove squares) 0


-- Evaluation
whiteKnightOutpostAttackersVec :: Vector Board
whiteKnightOutpostAttackersVec =
  map (knightOutpostAttackers True) squares

blackKnightOutpostAttackersVec :: Vector Board
blackKnightOutpostAttackersVec =
  map (knightOutpostAttackers False) squares

whitePassedPawnBlockersVec :: Vector Board
whitePassedPawnBlockersVec =
  map (passedPawnBlockers True) squares

blackPassedPawnBlockersVec :: Vector Board
blackPassedPawnBlockersVec =
  map (passedPawnBlockers False) squares

squareDistanceVec :: Vector Int
squareDistanceVec = fromList do
  n1 <- toList squares
  n2 <- toList squares
  pure $ squareDistance n1 n2


{-# NOINLINE pieceRngVec #-}
pieceRngVec :: Vector Board
pieceRngVec = unsafePerformIO
  (slice 0 768 <$> genRandoms)


{-# NOINLINE castlingRngVec #-}
castlingRngVec :: Vector Board
castlingRngVec = unsafePerformIO
  (slice 768 16 <$> genRandoms)


{-# NOINLINE enPassantRngVec #-}
enPassantRngVec :: Vector Board
enPassantRngVec = unsafePerformIO
  (slice 784 8 <$> genRandoms)


{-# NOINLINE sideToMoveRng #-}
sideToMoveRng :: Board
sideToMoveRng = unsafePerformIO
  ((!! 792) <$> genRandoms)


genRandoms :: IO (Vector Board)
genRandoms = fromList
  . take 1000 . randoms
  <$> getStdGen


-- Ranges
squares :: Vector Square
squares = fromList [0 .. 63]

sideSquares :: Vector SideSquare
sideSquares = fromList [0 .. 7]

diagonals :: Vector Diagonal
diagonals = fromList [0 .. 14]


-- Useful boards
shortCastleSlidingFiles :: Board
shortCastleSlidingFiles = file_F .| file_G

longCastleSlidingFiles :: Board
longCastleSlidingFiles = file_C .| file_D


shortCastleFiles :: Board
shortCastleFiles = file_F .| file_G .| file_H

longCastleFiles :: Board
longCastleFiles = file_A .| file_B .| file_C

whiteKnightOutpostRanks :: Board
whiteKnightOutpostRanks = rank_5 .| rank_6 .| rank_7

blackKnightOutpostRanks :: Board
blackKnightOutpostRanks = rank_4 .| rank_3 .| rank_2

knightOupostFiles :: Board
knightOupostFiles = file_C .| file_D .| file_E .| file_F


-- Ranks
rank_1 :: Board
rank_1 = getRank 0

rank_2 :: Board
rank_2 = getRank 1

rank_3 :: Board
rank_3 = getRank 2

rank_4 :: Board
rank_4 = getRank 3

rank_5 :: Board
rank_5 = getRank 4

rank_6 :: Board
rank_6 = getRank 5

rank_7 :: Board
rank_7 = getRank 6

rank_8 :: Board
rank_8 = getRank 7


-- Files
file_A :: Board
file_A = getFile 0

file_B :: Board
file_B = getFile 1

file_C :: Board
file_C = getFile 2

file_D :: Board
file_D = getFile 3

file_E :: Board
file_E = getFile 4

file_F :: Board
file_F = getFile 5

file_G :: Board
file_G = getFile 6

file_H :: Board
file_H = getFile 7