board-games-0.4: test/Test/Mastermind.hs
module Test.Mastermind where
import qualified Game.Mastermind.CodeSet.Tree as CodeSetTree
-- import qualified Game.Mastermind.CodeSet.Union as CodeSetUnion
import qualified Game.Mastermind.CodeSet as CodeSet
import qualified Game.Mastermind.NonEmptyEnumSet as NonEmptySet
import qualified Game.Mastermind as MM
import Control.Applicative (liftA2, (<$>))
import qualified Data.NonEmpty.Class as NonEmptyC
import qualified Data.NonEmpty as NonEmpty
import qualified Data.Traversable as Trav
import Data.EnumSet (EnumSet)
import Data.NonEmpty ((!:))
import qualified Test.QuickCheck as QC
import Test.QuickCheck (Property, Arbitrary(arbitrary), (==>), )
alphabet :: EnumSet Int
alphabet = NonEmptySet.flatten neAlphabet
neAlphabet :: NonEmptySet.T Int
neAlphabet = NonEmptySet.fromList $ 0!:[1..9]
newtype Code = Code [Int]
deriving (Show)
genElement :: QC.Gen Int
genElement = QC.choose (0,9)
-- can we get it working with empty lists, too?
genCode :: Int -> QC.Gen Code
genCode width =
fmap (Code . take width) $ QC.listOf1 genElement
-- fmap (Code . take width) (QC.listOf genElement)
instance Arbitrary Code where
arbitrary = genCode 5
data CodePair = CodePair [Int] [Int]
deriving (Show)
genCodePair :: Int -> QC.Gen CodePair
genCodePair width =
liftA2
(\(Code xs) (Code ys) -> uncurry CodePair $ unzip $ zip xs ys)
(genCode width) (genCode width)
instance Arbitrary CodePair where
arbitrary = genCodePair 5
genEval :: Int -> QC.Gen MM.Eval
genEval size = do
total <- QC.frequency $ map (\k -> (k+1, return k)) [1 .. size]
rightPlaces <- QC.choose (0,total)
return $ MM.Eval rightPlaces (total - rightPlaces)
forAllEval :: QC.Testable prop => [a] -> (MM.Eval -> prop) -> Property
forAllEval code = QC.forAll (genEval (length code))
type CodeSetInt = CodeSetTree.T Int
genFixedLengthCodes :: (NonEmptyC.Gen f) => Int -> QC.Gen (f [Int])
genFixedLengthCodes width = NonEmptyC.genOf $ QC.vectorOf width genElement
bestSeparatingCode :: Property
bestSeparatingCode =
QC.forAll (genCodePair 4) $ \(CodePair base0 base1) ->
forAllEval base0 $ \eval0 ->
forAllEval base1 $ \eval1 -> do
let width = length base0
set =
CodeSet.intersection
(MM.matching alphabet base0 eval0)
(MM.matching alphabet base1 eval1)
not (CodeSet.null set) ==>
QC.forAll (fmap (NonEmpty.mapTail $ take 9) $ genFixedLengthCodes width) $
MM.propBestSeparatingCode width (set :: CodeSetInt)
intersections :: Property
intersections =
QC.forAll (genCode 4) $ \(Code code) ->
QC.forAll (fmap (take 10) $ genFixedLengthCodes (length code)) $ \codes ->
QC.forAll (Trav.mapM (\x -> (,) x <$> genEval (length code)) (code!:codes)) $
CodeSetTree.propIntersections . fmap (uncurry $ MM.matching alphabet)
-- should also work, when selecting any code from the set of matching codes
solve :: Code -> Bool
solve (Code secret) =
let recourse remain =
case CodeSet.flatten remain of
[] -> False
[attempt] -> secret == attempt
attempt:_ ->
recourse $ CodeSet.intersection remain $
MM.matching alphabet attempt $ MM.evaluate secret attempt
in recourse (CodeSet.cube neAlphabet (length secret) :: CodeSetInt)
{-
Other possible tests:
the products in a set produced by 'MM.matching' must be disjoint.
set laws for the two set implementations,
such as distributivity of union and intersection
check member against intersection with singleton
-}