module Combinatorics.Mastermind ( Eval(..), evaluate, evaluateAll, formatEvalHistogram, numberDistinct, ) where import qualified Combinatorics.Permutation.WithoutSomeFixpoints as PermWOFP import Combinatorics (binomial) import Text.Printf (printf) import qualified Data.Map as Map; import Data.Map (Map) import qualified Data.Foldable as Fold import qualified Data.List.HT as ListHT import Data.Tuple.HT (mapPair) {- | Cf. @board-games@ package. -} data Eval = Eval {black, white :: Int} deriving (Eq, Ord, Show) {- | Given the code and a guess, compute the evaluation. -} evaluate :: (Ord a) => [a] -> [a] -> Eval evaluate code attempt = uncurry Eval $ mapPair (length, Fold.sum . uncurry (Map.intersectionWith min) . mapPair (histogram,histogram) . unzip) $ ListHT.partition (uncurry (==)) $ zip code attempt {- *Combinatorics.Mastermind> filter ((Eval 2 0 ==) . evaluate "aabbb") $ replicateM 5 ['a'..'c'] ["aaaaa","aaaac","aaaca","aaacc","aacaa","aacac","aacca","aaccc","acbcc","accbc","acccb","cabcc","cacbc","caccb","ccbbc","ccbcb","cccbb"] -} evaluateAll :: (Ord a) => [[a]] -> [a] -> Map Eval Int evaluateAll codes attempt = histogram $ map (evaluate attempt) codes formatEvalHistogram :: Map Eval Int -> String formatEvalHistogram m = let n = maximum $ map (\(Eval b w) -> b+w) $ Map.keys m in unlines $ zipWith (\b -> unwords . map (\w -> printf "%6d" $ Map.findWithDefault 0 (Eval b w) m)) [0..] (reverse $ tail $ ListHT.inits [0..n]) histogram :: (Ord a) => [a] -> Map a Int histogram = Map.fromListWith (+) . map (\a -> (a,1)) {- | @numberDistinct n k b w@ computes the number of matching codes, given that all codes have distinct symbols. @n@ is the alphabet size, @k@ the width of the code, @b@ the number of black evaluation sticks and @w@ the number of white evaluation sticks. -} numberDistinct :: Int -> Int -> Int -> Int -> Integer numberDistinct n k b w = binomial (toInteger k) (toInteger b) * numberDistinctWhite (n-b) (k-b) w {- | @numberDistinctWhite n k w == numberDistinct n k 0 w@ -} numberDistinctWhite :: Int -> Int -> Int -> Integer numberDistinctWhite n k w = let ni = toInteger n ki = toInteger k wi = toInteger w in binomial ki wi * PermWOFP.numbers !! k !! w * binomial (ni-ki) (ki-wi)