module Combinatorics.Battleship.Fleet (
T,
ShipSize,
NumberOfShips,
cumulate,
dec, inc,
empty,
fromList, toList,
fromSizes, toSizes,
lookup,
maxSize,
singleton,
subset,
german,
english,
propList,
propSizes,
propCumulate,
propSubset,
propInc,
propDec,
propIncDec,
) where
import qualified Foreign.Storable.Newtype as Store
import Foreign.Storable (Storable, sizeOf, alignment, poke, peek, )
import Data.Foldable (foldMap, )
import Data.Bool.HT (if', )
import Data.Monoid (Monoid, mempty, mappend, )
import Data.Semigroup (Semigroup, (<>), )
import Data.Bits ((.&.), (.|.), xor, shiftL, shiftR, )
import Data.Word (Word32, )
import Prelude hiding (lookup)
import qualified Test.QuickCheck as QC
type ShipSize = Int
type NumberOfShips = Int
newtype T = Cons {decons :: Word32}
deriving (Eq, Ord)
instance Show T where
showsPrec prec x =
showParen (prec>10) $
showString "Fleet.fromList " .
shows (toList x)
instance Semigroup T where
Cons x <> Cons y = Cons (x+y)
instance Monoid T where
mempty = Cons 0
mappend = (<>)
instance Storable T where
sizeOf = Store.sizeOf decons
alignment = Store.alignment decons
poke = Store.poke decons
peek = Store.peek Cons
debug :: Bool
debug = False
{-# INLINE checkSize #-}
checkSize :: String -> ShipSize -> a -> a
checkSize name size =
if' (debug && (size<=0 || maxSize<size)) $
error $ name ++ ": ship size " ++ show size ++ " out of range"
bitsPerNumber :: Int
bitsPerNumber = 4
digitMask :: Word32
digitMask = shiftL 1 bitsPerNumber - 1
maxSize :: Int
maxSize = 8
bitPosFromSize :: Int -> Int
bitPosFromSize size =
(size-1)*bitsPerNumber
empty :: T
empty = mempty
singleton :: ShipSize -> NumberOfShips -> T
singleton size n =
checkSize "Fleet.singleton" size
Cons $ shiftL (fromIntegral n) (bitPosFromSize size)
fromList :: [(ShipSize, NumberOfShips)] -> T
fromList = foldMap (uncurry singleton)
fromSizes :: [ShipSize] -> T
fromSizes = fromList . map (flip (,) 1)
lookup :: T -> ShipSize -> NumberOfShips
lookup (Cons bits) size =
checkSize "Fleet.lookup" size $
fromIntegral $
shiftR bits (bitPosFromSize size)
.&.
digitMask
toList :: T -> [(ShipSize, NumberOfShips)]
toList fleet =
filter ((0/=) . snd) $
map (\size -> (size, lookup fleet size)) [1..maxSize]
toSizes :: T -> [ShipSize]
toSizes = concatMap (\(size,n) -> replicate n size) . toList
propList :: T -> Bool
propList fleet = fleet == fromList (toList fleet)
propSizes :: T -> Bool
propSizes fleet = fleet == fromSizes (toSizes fleet)
cumulate :: T -> T
cumulate = cumulateDiv
cumulateCascade :: T -> T
cumulateCascade (Cons x) =
Cons $ foldl (\y n -> y + shiftR y n) x $
takeWhile (< maxSize * bitsPerNumber) $ iterate (2*) bitsPerNumber
cumulateDiv :: T -> T
cumulateDiv (Cons x) =
Cons $
case divMod x digitMask of
(q,r) -> shiftL q bitsPerNumber .|. r
genBounded :: QC.Gen T
genBounded = do
n <- QC.choose (0, fromIntegral digitMask - 1)
fmap fromSizes $ QC.vectorOf n $ QC.choose (1, maxSize)
propCumulate :: QC.Property
propCumulate =
QC.forAll genBounded $
\x -> cumulateCascade x == cumulateDiv x
{-# INLINE subset #-}
subset :: T -> T -> Bool
subset = subsetParity
subsetLookup :: T -> T -> Bool
subsetLookup x y =
all (\size -> lookup x size <= lookup y size) [1..maxSize]
subsetParity :: T -> T -> Bool
subsetParity =
let sizesPos =
div (shiftL 1 (maxSize*bitsPerNumber) - 1) digitMask
in \(Cons x) (Cons y) ->
x<=y && xor (xor x y) (y-x) .&. sizesPos == 0
propSubset :: T -> T -> Bool
propSubset x y = subsetLookup x y == subsetParity x y
inc :: ShipSize -> T -> T
inc size (Cons fleet) =
checkSize "Fleet.inc" size $
Cons $ fleet + shiftL 1 (bitPosFromSize size)
dec :: ShipSize -> T -> T
dec size (Cons fleet) =
checkSize "Fleet.inc" size $
Cons $ fleet - shiftL 1 (bitPosFromSize size)
german :: T
german = fromList [(5,1), (4,2), (3,3), (2,4)]
english :: T
english = fromList [(2,1), (3,2), (4,1), (5,1)]
genShipSize :: QC.Gen ShipSize
genShipSize = QC.choose (1, maxSize)
propInc :: T -> QC.Property
propInc fleet =
QC.forAll genShipSize $ \size ->
QC.forAll genShipSize $ \pos ->
lookup fleet size < fromIntegral digitMask
QC.==>
lookup (inc size fleet) pos == lookup fleet pos + fromEnum (pos==size)
propDec :: T -> QC.Property
propDec fleet =
QC.forAll genShipSize $ \size ->
QC.forAll genShipSize $ \pos ->
lookup fleet size > 0
QC.==>
lookup (dec size fleet) pos == lookup fleet pos - fromEnum (pos==size)
propIncDec :: T -> QC.Property
propIncDec fleet =
QC.forAll genShipSize $ \size ->
lookup fleet size < fromIntegral digitMask
QC.==>
dec size (inc size fleet) == fleet
instance QC.Arbitrary T where
arbitrary = fmap Cons $ QC.choose (minBound, maxBound)
shrink = map (fromSizes . filter (>0)) . QC.shrink . toSizes