{-# LANGUAGE PatternSynonyms #-} module Game.Chess.Internal.Square where import Control.Lens (Iso', from, iso, view) import Data.Bifunctor (Bifunctor (..)) import Data.Binary (Binary (get, put), getWord8, putWord8) import Data.Bits (Bits (testBit)) import Data.Char (chr, ord) import Data.Coerce (coerce) import Data.Ix (Ix (..)) import Data.String (IsString (fromString)) import Data.Word (Word64) import GHC.Stack (HasCallStack) newtype Rank = Rank Int deriving (Eq, Ord) unRank :: Rank -> Int unRank = coerce mkRank :: HasCallStack => Int -> Rank mkRank n | n >= 0 && n <= 7 = Rank n | otherwise = error $ "mkRank " ++ show n instance Show Rank where showsPrec _ (Rank 0) = showString "Rank1" showsPrec _ (Rank 1) = showString "Rank2" showsPrec _ (Rank 2) = showString "Rank3" showsPrec _ (Rank 3) = showString "Rank4" showsPrec _ (Rank 4) = showString "Rank5" showsPrec _ (Rank 5) = showString "Rank6" showsPrec _ (Rank 6) = showString "Rank7" showsPrec _ (Rank 7) = showString "Rank8" showsPrec d (Rank n) = showParen (d > 10) $ showString "Rank " . showsPrec 11 n instance Enum Rank where toEnum n | n >= 0 && n <= 7 = Rank n | otherwise = error $ "Rank out-of-bound " ++ show n fromEnum = coerce instance Bounded Rank where minBound = Rank1 maxBound = Rank8 pattern Rank1, Rank2, Rank3, Rank4, Rank5, Rank6, Rank7, Rank8 :: Rank pattern Rank1 = Rank 0 pattern Rank2 = Rank 1 pattern Rank3 = Rank 2 pattern Rank4 = Rank 3 pattern Rank5 = Rank 4 pattern Rank6 = Rank 5 pattern Rank7 = Rank 6 pattern Rank8 = Rank 7 {-# COMPLETE Rank1, Rank2, Rank3, Rank4, Rank5, Rank6, Rank7, Rank8 :: Rank #-} newtype File = File Int deriving (Eq, Ord) unFile :: File -> Int unFile = coerce mkFile :: HasCallStack => Int -> File mkFile n | n >= 0 && n <= 7 = File n | otherwise = error $ "mkFile " ++ show n instance Show File where showsPrec _ (File 0) = showString "FileA" showsPrec _ (File 1) = showString "FileB" showsPrec _ (File 2) = showString "FileC" showsPrec _ (File 3) = showString "FileD" showsPrec _ (File 4) = showString "FileE" showsPrec _ (File 5) = showString "FileF" showsPrec _ (File 6) = showString "FileG" showsPrec _ (File 7) = showString "FileH" showsPrec d (File n) = showParen (d > 10) $ showString "File " . showsPrec 11 n instance Enum File where toEnum n | n >= 0 && n <= 7 = File n | otherwise = error $ "File out-of-bound " ++ show n fromEnum = coerce instance Bounded File where minBound = FileA maxBound = FileH pattern FileA, FileB, FileC, FileD, FileE, FileF, FileG, FileH :: File pattern FileA = File 0 pattern FileB = File 1 pattern FileC = File 2 pattern FileD = File 3 pattern FileE = File 4 pattern FileF = File 5 pattern FileG = File 6 pattern FileH = File 7 {-# COMPLETE FileA, FileB, FileC, FileD, FileE, FileF, FileG, FileH :: File #-} newtype Square = Sq Int deriving (Eq, Ord) instance Binary Square where put = putWord8 . fromIntegral . unSquare get = mkSq . fromIntegral <$> getWord8 instance Ix Square where range (Sq i, Sq j) = [Sq k | k <- [i..j]] index (Sq i, Sq j) (Sq k) = index (i, j) k inRange (Sq i, Sq j) (Sq k) = inRange (i, j) k rangeSize (Sq i, Sq j) = j - i unSquare :: Square -> Int unSquare = coerce {-# INLINE unSquare #-} -- TODO: this check is expensive, maybe only worth in "debug" builds. mkSq :: HasCallStack => Int -> Square mkSq n | n >= 0 && n <= 63 = Sq n | otherwise = error $ "mkSq " ++ show n instance Show Square where showsPrec d (Sq i) | i >= 0 && i <= 63 = showString [f', r'] | otherwise = showParen (d > 10) $ showString "Sq " . showsPrec 11 i where (r, f) = i `divMod` 8 r' = chr (r + ord '1') f' = chr (f + ord 'A') instance Enum Square where toEnum n | n >= 0 && n <= 63 = Sq n | otherwise = error $ "Sq out-of-bound " ++ show n fromEnum = coerce instance Bounded Square where minBound = A1 maxBound = H8 pattern A1, A2, A3, A4, A5, A6, A7, A8 :: Square pattern B1, B2, B3, B4, B5, B6, B7, B8 :: Square pattern C1, C2, C3, C4, C5, C6, C7, C8 :: Square pattern D1, D2, D3, D4, D5, D6, D7, D8 :: Square pattern E1, E2, E3, E4, E5, E6, E7, E8 :: Square pattern F1, F2, F3, F4, F5, F6, F7, F8 :: Square pattern G1, G2, G3, G4, G5, G6, G7, G8 :: Square pattern H1, H2, H3, H4, H5, H6, H7, H8 :: Square pattern A1 = Sq 0 pattern B1 = Sq 1 pattern C1 = Sq 2 pattern D1 = Sq 3 pattern E1 = Sq 4 pattern F1 = Sq 5 pattern G1 = Sq 6 pattern H1 = Sq 7 pattern A2 = Sq 8 pattern B2 = Sq 9 pattern C2 = Sq 10 pattern D2 = Sq 11 pattern E2 = Sq 12 pattern F2 = Sq 13 pattern G2 = Sq 14 pattern H2 = Sq 15 pattern A3 = Sq 16 pattern B3 = Sq 17 pattern C3 = Sq 18 pattern D3 = Sq 19 pattern E3 = Sq 20 pattern F3 = Sq 21 pattern G3 = Sq 22 pattern H3 = Sq 23 pattern A4 = Sq 24 pattern B4 = Sq 25 pattern C4 = Sq 26 pattern D4 = Sq 27 pattern E4 = Sq 28 pattern F4 = Sq 29 pattern G4 = Sq 30 pattern H4 = Sq 31 pattern A5 = Sq 32 pattern B5 = Sq 33 pattern C5 = Sq 34 pattern D5 = Sq 35 pattern E5 = Sq 36 pattern F5 = Sq 37 pattern G5 = Sq 38 pattern H5 = Sq 39 pattern A6 = Sq 40 pattern B6 = Sq 41 pattern C6 = Sq 42 pattern D6 = Sq 43 pattern E6 = Sq 44 pattern F6 = Sq 45 pattern G6 = Sq 46 pattern H6 = Sq 47 pattern A7 = Sq 48 pattern B7 = Sq 49 pattern C7 = Sq 50 pattern D7 = Sq 51 pattern E7 = Sq 52 pattern F7 = Sq 53 pattern G7 = Sq 54 pattern H7 = Sq 55 pattern A8 = Sq 56 pattern B8 = Sq 57 pattern C8 = Sq 58 pattern D8 = Sq 59 pattern E8 = Sq 60 pattern F8 = Sq 61 pattern G8 = Sq 62 pattern H8 = Sq 63 rank :: Square -> Rank rank = Rank . (`div` 8) . coerce file :: Square -> File file = File . (`mod` 8) . coerce rankFile :: Iso' Square (Rank, File) rankFile = iso rf sq where rf (Sq i) = bimap Rank File $ i `divMod` 8 sq (Rank r, File f) = Sq $ r*8 + f mapRank :: (Rank -> Rank) -> Square -> Square mapRank f = view (from rankFile) . first f . view rankFile mapFile :: (File -> File) -> Square -> Square mapFile f = view (from rankFile) . second f . view rankFile fileChar, rankChar :: Square -> Char fileChar = chr . (ord 'a' +) . unFile . file rankChar = chr . (ord '1' +) . unRank . rank toCoord :: IsString s => Square -> s toCoord sq = fromString $ ($ sq) <$> [fileChar, rankChar] isDark :: Square -> Bool isDark (Sq i) = (0xaa55aa55aa55aa55 :: Word64) `testBit` i isLight :: Square -> Bool isLight = not . isDark