packages feed

board-games 0.2.1 → 0.3

raw patch · 15 files changed

+731/−360 lines, 15 filesdep +enummapsetdep +paralleldep ~QuickCheckdep ~basedep ~containersPVP ok

version bump matches the API change (PVP)

Dependencies added: enummapset, parallel

Dependency ranges changed: QuickCheck, base, containers, non-empty, random, transformers, utility-ht

API changes (from Hackage documentation)

- Game.Mastermind.CodeSet.Tree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Game.Mastermind.CodeSet.Tree.Indexable a)
- Game.Mastermind.CodeSet.Tree: instance GHC.Classes.Ord a => GHC.Classes.Ord (Game.Mastermind.CodeSet.Tree.Indexable a)
- Game.Mastermind.CodeSet.Tree: instance GHC.Show.Show a => GHC.Show.Show (Game.Mastermind.CodeSet.Tree.T a)
- Game.Mastermind.CodeSet.Union: instance (GHC.Classes.Ord a, GHC.Show.Show a) => GHC.Show.Show (Game.Mastermind.CodeSet.Union.T a)
+ Game.Mastermind: randomizedAttempt :: (C set, RandomGen g, Enum a) => Int -> set a -> State g (Maybe [a])
+ Game.Mastermind: scanningRandomizedAttempt :: (C set, RandomGen g, Enum a) => Int -> EnumSet a -> [([a], Eval)] -> set a -> State g (Maybe [a])
+ Game.Mastermind: separatingRandomizedAttempt :: (C set, RandomGen g, Enum a) => Int -> EnumSet a -> set a -> State g (Maybe [a])
+ Game.Mastermind.CodeSet.Tree: instance (GHC.Enum.Enum a, GHC.Show.Show a) => GHC.Show.Show (Game.Mastermind.CodeSet.Tree.T a)
+ Game.Mastermind.CodeSet.Tree: instance GHC.Enum.Enum a => GHC.Classes.Eq (Game.Mastermind.CodeSet.Tree.Indexable a)
+ Game.Mastermind.CodeSet.Tree: instance GHC.Enum.Enum a => GHC.Classes.Ord (Game.Mastermind.CodeSet.Tree.Indexable a)
+ Game.Mastermind.CodeSet.Union: instance (GHC.Enum.Enum a, GHC.Show.Show a) => GHC.Show.Show (Game.Mastermind.CodeSet.Union.T a)
+ Game.Mastermind.NonEmptyEnumSet: Cons :: EnumSet a -> T a
+ Game.Mastermind.NonEmptyEnumSet: [flatten] :: T a -> EnumSet a
+ Game.Mastermind.NonEmptyEnumSet: fetch :: EnumSet a -> Maybe (T a)
+ Game.Mastermind.NonEmptyEnumSet: fromList :: Enum a => T [] a -> T a
+ Game.Mastermind.NonEmptyEnumSet: instance (GHC.Enum.Enum a, GHC.Show.Show a) => GHC.Show.Show (Game.Mastermind.NonEmptyEnumSet.T a)
+ Game.Mastermind.NonEmptyEnumSet: instance GHC.Classes.Eq (Game.Mastermind.NonEmptyEnumSet.T a)
+ Game.Mastermind.NonEmptyEnumSet: instance GHC.Classes.Ord (Game.Mastermind.NonEmptyEnumSet.T a)
+ Game.Mastermind.NonEmptyEnumSet: member :: Enum a => a -> T a -> Bool
+ Game.Mastermind.NonEmptyEnumSet: newtype T a
+ Game.Mastermind.NonEmptyEnumSet: singleton :: Enum a => a -> T a
+ Game.Mastermind.NonEmptyEnumSet: size :: T a -> Int
+ Game.Mastermind.NonEmptyEnumSet: toFlatList :: Enum a => T a -> [a]
+ Game.Mastermind.NonEmptyEnumSet: union :: T a -> T a -> T a
- Game.Mastermind: evaluate :: Ord a => [a] -> [a] -> Eval
+ Game.Mastermind: evaluate :: Enum a => [a] -> [a] -> Eval
- Game.Mastermind: matching :: (C set, Ord a) => Set a -> [a] -> Eval -> set a
+ Game.Mastermind: matching :: (C set, Enum a) => EnumSet a -> [a] -> Eval -> set a
- Game.Mastermind: matchingSimple :: Ord a => Set a -> [a] -> Int -> [[Set a]]
+ Game.Mastermind: matchingSimple :: Enum a => EnumSet a -> [a] -> Int -> [[EnumSet a]]
- Game.Mastermind: mixedRandomizedAttempt :: (C set, RandomGen g, Ord a) => Int -> set a -> StateT g Maybe [a]
+ Game.Mastermind: mixedRandomizedAttempt :: (C set, RandomGen g, Enum a) => Int -> set a -> State g (Maybe [a])
- Game.Mastermind: partitionSizes :: Ord a => Set a -> [a] -> [(Eval, Integer)]
+ Game.Mastermind: partitionSizes :: Enum a => EnumSet a -> [a] -> [(Eval, Integer)]
- Game.Mastermind: propBestSeparatingCode :: (C set, Ord a) => Int -> set a -> [[a]] -> Bool
+ Game.Mastermind: propBestSeparatingCode :: (C set, Enum a) => Int -> set a -> T [] [a] -> Bool
- Game.Mastermind.CodeSet: (#*&) :: C set => a -> set a -> set a
+ Game.Mastermind.CodeSet: (#*&) :: (C set, Enum a) => a -> set a -> set a
- Game.Mastermind.CodeSet: (*&) :: (C set, Ord a) => Set a -> set a -> set a
+ Game.Mastermind.CodeSet: (*&) :: (C set, Enum a) => EnumSet a -> set a -> set a
- Game.Mastermind.CodeSet: compress :: (C set, Ord a) => set a -> set a
+ Game.Mastermind.CodeSet: compress :: (C set, Enum a) => set a -> set a
- Game.Mastermind.CodeSet: flatten :: (C set, Ord a) => set a -> [[a]]
+ Game.Mastermind.CodeSet: flatten :: (C set, Enum a) => set a -> [[a]]
- Game.Mastermind.CodeSet: intersection :: (C set, Ord a) => set a -> set a -> set a
+ Game.Mastermind.CodeSet: intersection :: (C set, Enum a) => set a -> set a -> set a
- Game.Mastermind.CodeSet: intersections :: (C set, Ord a) => T [] (set a) -> set a
+ Game.Mastermind.CodeSet: intersections :: (C set, Enum a) => T [] (set a) -> set a
- Game.Mastermind.CodeSet: intersectionsPQ :: (C set, Ord a) => T [] (set a) -> set a
+ Game.Mastermind.CodeSet: intersectionsPQ :: (C set, Enum a) => T [] (set a) -> set a
- Game.Mastermind.CodeSet: select :: C set => set a -> Integer -> [a]
+ Game.Mastermind.CodeSet: select :: (C set, Enum a) => set a -> Integer -> [a]
- Game.Mastermind.CodeSet: symbols :: (C set, Ord a) => set a -> Set a
+ Game.Mastermind.CodeSet: symbols :: (C set, Enum a) => set a -> EnumSet a
- Game.Mastermind.CodeSet: union :: (C set, Ord a) => set a -> set a -> set a
+ Game.Mastermind.CodeSet: union :: (C set, Enum a) => set a -> set a -> set a
- Game.Mastermind.CodeSet: unions :: (C set, Ord a) => [set a] -> set a
+ Game.Mastermind.CodeSet: unions :: (C set, Enum a) => [set a] -> set a
- Game.Mastermind.CodeSet.Tree: intersection :: Ord a => T a -> T a -> T a
+ Game.Mastermind.CodeSet.Tree: intersection :: Enum a => T a -> T a -> T a
- Game.Mastermind.CodeSet.Tree: member :: Ord a => [a] -> T a -> Bool
+ Game.Mastermind.CodeSet.Tree: member :: Enum a => [a] -> T a -> Bool
- Game.Mastermind.CodeSet.Tree: propIntersections :: Ord a => T [] (T a) -> Bool
+ Game.Mastermind.CodeSet.Tree: propIntersections :: Enum a => T [] (T a) -> Bool
- Game.Mastermind.CodeSet.Union: fromLists :: Ord a => [[T [] a]] -> T a
+ Game.Mastermind.CodeSet.Union: fromLists :: Enum a => [[T [] a]] -> T a
- Game.Mastermind.CodeSet.Union: member :: Ord a => [a] -> T a -> Bool
+ Game.Mastermind.CodeSet.Union: member :: Enum a => [a] -> T a -> Bool
- Game.Mastermind.CodeSet.Union: overlapping :: Ord a => T a -> [([Set a], [[Set a]])]
+ Game.Mastermind.CodeSet.Union: overlapping :: Enum a => T a -> [([EnumSet a], [[EnumSet a]])]
- Game.Mastermind.CodeSet.Union: overlappingPairs :: Ord a => T a -> [([Set a], [Set a])]
+ Game.Mastermind.CodeSet.Union: overlappingPairs :: Enum a => T a -> [([EnumSet a], [EnumSet a])]

Files

Changes.md view
@@ -1,5 +1,18 @@ # Change log for the `board-games` package +## 0.3++ * `Mastermind.CodeSet`:+   Move from `Set` to `EnumSet` since this is three to ten times faster.+   It works best for alphabets with contiguous `fromEnum`-associated `Int`s,+   but even if not it should not be much worse than `Set`.+   If this is still too slow for you, you might consider mapping your alphabet+   to a contiguous set of `Int`s first.+   I tried to maintain both `Set` and `EnumSet` in one interface.+   It is possible even in Haskell 98 using explicit method dictionaries.+   However, it gets complicated and I am afraid+   that the speed advantage is diminished by the generalization overhead.+ ## 0.2.1   * add criterion benchmarks for `Mastermind`
Makefile view
@@ -9,4 +9,4 @@ 	./dist/build/board-games-test/board-games-test  criterion.html:	./dist/build/board-games-benchmark/board-games-benchmark-	$< --output=$@ $(patsubst %.html, %.csv, --summary=$@)+	$< --output=$@ $(patsubst %.html, %.csv, --csv=$@)
− benchmark/Main.hs
@@ -1,144 +0,0 @@-module Main where--import Game.Mastermind (Eval(Eval), matching)--import qualified Game.Mastermind.CodeSet.Tree as CodeSetTree-import qualified Game.Mastermind.CodeSet.Union as CodeSetUnion-import qualified Game.Mastermind.CodeSet as CodeSet--import Criterion.Main (Benchmark, defaultMain, bgroup, bench, whnf)--import qualified Data.NonEmpty as NonEmpty-import qualified Data.Set as Set-import Data.NonEmpty ((!:))-import Data.Set (Set)-import Data.Tuple.HT (mapSnd)---alphabet, digits :: Set Char-alphabet = Set.fromList ['a'..'z']-digits = Set.fromList ['0'..'9']---type List1 = NonEmpty.T []-type List2 = NonEmpty.T List1--gamesWords, gamesNumbers :: [List2 (String, Eval)]-gamesWords =-   [-      ("flq", Eval 0 0) !:-      ("chx", Eval 0 0) !:-      ("sez", Eval 2 0) :-      ("aes", Eval 1 1) :-      ("bde", Eval 0 1) :-      ("gij", Eval 0 0) :-      ("kmn", Eval 0 0) :-      ("opr", Eval 0 0) :-      ("tuv", Eval 0 1) :-      ("set", Eval 3 0) :-      [],--      ("ncqy", Eval 0 1) !:-      ("yxcl", Eval 0 0) !:-      ("ikjn", Eval 0 2) :-      ("dnoj", Eval 0 2) :-      ("jbnz", Eval 1 0) :-      ("ofnk", Eval 1 0) :-      ("adni", Eval 1 2) :-      ("eghm", Eval 0 1) :-      ("gaah", Eval 0 0) :-      ("mind", Eval 4 0) :-      [],--      ("ozdtp", Eval 0 1) !:-      ("cxgkz", Eval 1 1) !:-      ("gbqvz", Eval 0 1) :-      ("ctdng", Eval 0 2) :-      ("dwghk", Eval 1 0) :-      ("sygpc", Eval 2 0) :-      ("gogfm", Eval 2 0) :-      ("ploir", Eval 1 2) :-      ("logic", Eval 5 0) :-      [],--      ("tynrsu", Eval 0 3) !:-      ("msycnl", Eval 1 1) !:-      ("uslher", Eval 2 1) :-      ("pceeyr", Eval 1 1) :-      ("psugen", Eval 1 1) :-      ("kscdur", Eval 1 1) :-      ("zwghob", Eval 0 0) :-      ("masafa", Eval 3 0) :-      ("master", Eval 6 0) :-      []-   ]--gamesNumbers =-   [-      ("092", Eval 1 0) !:-      ("009", Eval 2 0) !:-      ("130", Eval 0 1) :-      ("456", Eval 0 0) :-      ("007", Eval 3 0) :-      [],--      ("7483", Eval 0 0) !:-      ("2066", Eval 2 0) !:-      ("0106", Eval 0 2) :-      ("2501", Eval 1 2) :-      ("2012", Eval 3 0) :-      ("2019", Eval 4 0) :-      [],--      ("04575", Eval 1 1) !:-      ("43465", Eval 0 3) !:-      ("32556", Eval 2 2) :-      ("16553", Eval 1 3) :-      ("58536", Eval 3 1) :-      ("65536", Eval 5 0) :-      [],--      ("899820", Eval 1 1) !:-      ("872456", Eval 2 3) !:-      ("256296", Eval 0 2) :-      ("142857", Eval 5 0) :-      []-   ]--addWidth :: NonEmpty.T f ([a], b) -> (Int, NonEmpty.T f ([a], b))-addWidth xs = (length $ fst $ NonEmpty.head xs, xs)---singleBench ::-   (CodeSet.C set) =>-   (set Char -> Integer) -> Set Char -> (Int, List1 (String, Eval)) -> Benchmark-singleBench setSize symbols (width, xs) =-   bench (show width) $-      whnf (setSize . CodeSet.intersections .-            fmap (uncurry (matching symbols))) xs--benchWordsAndNumbers ::-   (CodeSet.C set) =>-   (set Char -> Integer) ->-   (List2 (String, Eval) -> List1 (String, Eval)) -> [Benchmark]-benchWordsAndNumbers setSize cut =-   bgroup "words"-      (map (singleBench setSize alphabet . mapSnd cut . addWidth) gamesWords) :-   bgroup "numbers"-      (map (singleBench setSize digits . mapSnd cut . addWidth) gamesNumbers) :-   []--benchCodeSets :: (CodeSet.C set) => (set Char -> Integer) -> [Benchmark]-benchCodeSets setSize =-   bgroup "3 evaluations"-      (benchWordsAndNumbers setSize-         (NonEmpty.mapTail (take 2) . NonEmpty.flatten)) :-   bgroup "all but one evaluation"-      (benchWordsAndNumbers setSize NonEmpty.init) :-   []--main :: IO ()-main = defaultMain $-   bgroup "tree" (benchCodeSets CodeSetTree.size) :-   bgroup "union" (benchCodeSets CodeSetUnion.size) :-   []
+ benchmark/MastermindSpeed.hs view
@@ -0,0 +1,145 @@+module Main where++import Game.Mastermind (Eval(Eval), matching)++import qualified Game.Mastermind.CodeSet.Tree as CodeSetTree+import qualified Game.Mastermind.CodeSet.Union as CodeSetUnion+import qualified Game.Mastermind.CodeSet as CodeSet++import Criterion.Main (Benchmark, defaultMain, bgroup, bench, whnf)++import qualified Data.NonEmpty as NonEmpty+import qualified Data.EnumSet as EnumSet+import Data.NonEmpty ((!:))+import Data.EnumSet (EnumSet)+import Data.Tuple.HT (mapSnd)+++alphabet, digits :: EnumSet Char+alphabet = EnumSet.fromList ['a'..'z']+digits = EnumSet.fromList ['0'..'9']+++type List1 = NonEmpty.T []+type List2 = NonEmpty.T List1++gamesWords, gamesNumbers :: [List2 (String, Eval)]+gamesWords =+   [+      ("flq", Eval 0 0) !:+      ("chx", Eval 0 0) !:+      ("sez", Eval 2 0) :+      ("aes", Eval 1 1) :+      ("bde", Eval 0 1) :+      ("gij", Eval 0 0) :+      ("kmn", Eval 0 0) :+      ("opr", Eval 0 0) :+      ("tuv", Eval 0 1) :+      ("set", Eval 3 0) :+      [],++      ("ncqy", Eval 0 1) !:+      ("yxcl", Eval 0 0) !:+      ("ikjn", Eval 0 2) :+      ("dnoj", Eval 0 2) :+      ("jbnz", Eval 1 0) :+      ("ofnk", Eval 1 0) :+      ("adni", Eval 1 2) :+      ("eghm", Eval 0 1) :+      ("gaah", Eval 0 0) :+      ("mind", Eval 4 0) :+      [],++      ("ozdtp", Eval 0 1) !:+      ("cxgkz", Eval 1 1) !:+      ("gbqvz", Eval 0 1) :+      ("ctdng", Eval 0 2) :+      ("dwghk", Eval 1 0) :+      ("sygpc", Eval 2 0) :+      ("gogfm", Eval 2 0) :+      ("ploir", Eval 1 2) :+      ("logic", Eval 5 0) :+      [],++      ("tynrsu", Eval 0 3) !:+      ("msycnl", Eval 1 1) !:+      ("uslher", Eval 2 1) :+      ("pceeyr", Eval 1 1) :+      ("psugen", Eval 1 1) :+      ("kscdur", Eval 1 1) :+      ("zwghob", Eval 0 0) :+      ("masafa", Eval 3 0) :+      ("master", Eval 6 0) :+      []+   ]++gamesNumbers =+   [+      ("092", Eval 1 0) !:+      ("009", Eval 2 0) !:+      ("130", Eval 0 1) :+      ("456", Eval 0 0) :+      ("007", Eval 3 0) :+      [],++      ("7483", Eval 0 0) !:+      ("2066", Eval 2 0) !:+      ("0106", Eval 0 2) :+      ("2501", Eval 1 2) :+      ("2012", Eval 3 0) :+      ("2019", Eval 4 0) :+      [],++      ("04575", Eval 1 1) !:+      ("43465", Eval 0 3) !:+      ("32556", Eval 2 2) :+      ("16553", Eval 1 3) :+      ("58536", Eval 3 1) :+      ("65536", Eval 5 0) :+      [],++      ("899820", Eval 1 1) !:+      ("872456", Eval 2 3) !:+      ("256296", Eval 0 2) :+      ("142857", Eval 5 0) :+      []+   ]++addWidth :: NonEmpty.T f ([a], b) -> (Int, NonEmpty.T f ([a], b))+addWidth xs = (length $ fst $ NonEmpty.head xs, xs)+++singleBench ::+   (CodeSet.C set) =>+   (set Char -> Integer) -> EnumSet Char ->+   (Int, List1 (String, Eval)) -> Benchmark+singleBench setSize symbols (width, xs) =+   bench (show width) $+      whnf (setSize . CodeSet.intersections .+            fmap (uncurry (matching symbols))) xs++benchWordsAndNumbers ::+   (CodeSet.C set) =>+   (set Char -> Integer) ->+   (List2 (String, Eval) -> List1 (String, Eval)) -> [Benchmark]+benchWordsAndNumbers setSize cut =+   bgroup "words"+      (map (singleBench setSize alphabet . mapSnd cut . addWidth) gamesWords) :+   bgroup "numbers"+      (map (singleBench setSize digits . mapSnd cut . addWidth) gamesNumbers) :+   []++benchCodeSets :: (CodeSet.C set) => (set Char -> Integer) -> [Benchmark]+benchCodeSets setSize =+   bgroup "3 evaluations"+      (benchWordsAndNumbers setSize+         (NonEmpty.mapTail (take 2) . NonEmpty.flatten)) :+   bgroup "all but one evaluation"+      (benchWordsAndNumbers setSize NonEmpty.init) :+   []++main :: IO ()+main = defaultMain $+   bgroup "tree" (benchCodeSets CodeSetTree.size) :+   bgroup "union" (benchCodeSets CodeSetUnion.size) :+   []
+ benchmark/MastermindStrategy.hs view
@@ -0,0 +1,125 @@+module Main where++import qualified Game.Mastermind as MM+import qualified Game.Mastermind.CodeSet.Tree as CodeSetTree+import qualified Game.Mastermind.CodeSet as CodeSet+import qualified Game.Mastermind.NonEmptyEnumSet as NonEmptySet+import Game.Utility (randomSelect, histogram)++import qualified System.Random as Rnd+import qualified System.IO as IO++import qualified Control.Parallel.Strategies as Strategy++import qualified Control.Monad.Trans.State as MS+import qualified Control.Functor.HT as FuncHT+import Control.Monad (replicateM, liftM2)++import Text.Printf (printf)++import qualified Data.Traversable as Trav+import qualified Data.Foldable as Fold+import qualified Data.NonEmpty as NonEmpty+import qualified Data.Empty as Empty+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Zip as Zip+import Data.NonEmpty ((!:))+import Data.Maybe (fromMaybe)+++justState :: MS.State g (Maybe a) -> MS.State g a+justState = fmap $ fromMaybe (error "contradicting evaluation")++play ::+   (Rnd.RandomGen g) =>+   (Int -> [([Char], MM.Eval)] ->+    CodeSetTree.T Char -> MS.State g (Maybe String)) ->+   NonEmptySet.T Char -> String -> MS.State g [String]+play doGuess alphabet code = do+   let width = length code+   let go oldGuesses codeSet = do+         guess <- justState $ doGuess width oldGuesses codeSet+         let eval = MM.evaluate guess code+         let currentGuesses = oldGuesses++[(guess,eval)]+         if eval == MM.Eval width 0+            then return $ fmap fst currentGuesses+            else go currentGuesses $+                  CodeSetTree.intersection codeSet $+                  MM.matching (NonEmptySet.flatten alphabet) guess eval+   go [] $ CodeSet.cube alphabet width+++type Tuple = NonEmpty.T (NonEmpty.T (NonEmpty.T (NonEmpty.T Empty.T)))++playVariants ::+   (Rnd.RandomGen g) =>+   NonEmptySet.T Char -> String -> MS.State g (String, Tuple Int)+playVariants alphabet code = do+   let alphabetFlat = NonEmptySet.flatten alphabet+   guessesColumns <-+      Trav.mapM (\strategy -> play strategy alphabet code) $+         (\width _oldGuesses -> MM.mixedRandomizedAttempt width) !:+         (\width _oldGuesses -> MM.randomizedAttempt width) !:+         (\width oldGuesses ->+            MM.scanningRandomizedAttempt width alphabetFlat oldGuesses) !:+         (\width _oldGuesses ->+            MM.separatingRandomizedAttempt width alphabetFlat) !:+         Empty.Cons+   return (code, fmap length guessesColumns)++playMany ::+   (Rnd.RandomGen g) =>+   NonEmpty.T [] Char -> Int -> g -> [(String, Tuple Int)]+playMany symbols width =+   let alphabet = NonEmptySet.fromList symbols+   in map+         (\code ->+            MS.evalState (playVariants alphabet code) (Rnd.mkStdGen 3141)) .+      MS.evalState+         (replicateM 100 $ replicateM width $+          randomSelect $ NonEmpty.flatten symbols)+++withWriteFile :: FilePath -> (IO.Handle -> IO a) -> IO a+withWriteFile path act =+   IO.withFile path IO.WriteMode $ \h ->+      IO.hSetBuffering h IO.LineBuffering >> act h++run :: IO.Handle -> (String, NonEmpty.T [] Char) -> Int -> IO ()+run averagesHandle (alphabetName,alphabet) width = do+   let pathEnding = printf "-%s-%d.csv" alphabetName width+   let games =+         Strategy.withStrategy+            (Strategy.parList $+             Strategy.parTuple2+                Strategy.rdeepseq (Strategy.parTraversable Strategy.rdeepseq)) $+         playMany alphabet width $ Rnd.mkStdGen 42+   let writeLines path txt = withWriteFile path $ \h -> IO.hPutStr h txt+   let csvLine cells = List.intercalate "," $ Fold.toList cells+   let writeCSV path = writeLines path . unlines . map csvLine+   writeCSV ("game-lengths" ++ pathEnding) $+      map (\(code,lengths) -> ('"':code++'"':"") !: fmap show lengths) games+   let gamesPerStrategy = Zip.transposeClip $ map snd games+   writeCSV ("histogram" ++ pathEnding) $ map (fmap show) $+      FuncHT.outerProduct (Map.findWithDefault 0)+         [1 .. NonEmpty.foldl1Map max maximum gamesPerStrategy]+         (fmap histogram gamesPerStrategy)+   let average :: [Int] -> Double+       average xs = fromIntegral (sum xs) / fromIntegral (length xs)+       sqr x = x^(2::Int)+       averageDeviation xs =+         let mean = average xs+         in [show mean,+             printf "%.4f" $ sqrt (average (map sqr xs) - sqr mean)]+   IO.hPutStrLn averagesHandle $ csvLine $+      alphabetName !: show width !:+         Fold.foldMap averageDeviation gamesPerStrategy++main :: IO ()+main =+   withWriteFile "averages.csv" $ \averageHandle ->+   sequence_ $+   liftM2 (run averageHandle)+      [("numbers", '0'!:['1'..'9']), ("words", 'a'!:['b'..'z'])]+      [3..6]
board-games.cabal view
@@ -1,5 +1,5 @@ Name:             board-games-Version:          0.2.1+Version:          0.3 License:          GPL License-File:     LICENSE Author:           Henning Thielemann <haskell@henning-thielemann.de>@@ -40,7 +40,7 @@   location: http://code.haskell.org/~thielema/games/  Source-Repository this-  tag:      0.2.1+  tag:      0.3   type:     darcs   location: http://code.haskell.org/~thielema/games/ @@ -56,7 +56,9 @@     cgi >=3001.1 && <3002,     non-empty >=0.2 && <0.4,     utility-ht >=0.0.3 && <0.1,-    transformers >=0.2.2 && <0.6+    transformers >=0.2.2 && <0.6,+    enummapset >=0.1 && <0.7,+    QuickCheck >2.0 && <3.0   If flag(splitBase)     Build-Depends:       containers >=0.2 && <0.7,@@ -68,7 +70,7 @@    Default-Language: Haskell2010   GHC-Options:      -Wall-  Hs-Source-Dirs:   src+  Hs-Source-Dirs:   src, private   Exposed-Modules:     Game.Tree     Game.VierGewinnt.HTML@@ -81,6 +83,7 @@     Game.Mastermind.CodeSet     Game.Mastermind.CodeSet.Union     Game.Mastermind.CodeSet.Tree+    Game.Mastermind.NonEmptyEnumSet   Other-Modules:     Game.Utility @@ -111,29 +114,53 @@   Type:             exitcode-stdio-1.0   Default-Language: Haskell2010   GHC-Options:      -Wall-  Hs-Source-Dirs:   test+  Hs-Source-Dirs:   test, private   Main-Is:          Test.hs-  Other-Modules:    Test.Mastermind+  Other-Modules:+    Test.Mastermind+    Game.Utility   Build-Depends:     board-games,-    QuickCheck >1.2 && <3.0,-    non-empty,+    QuickCheck,+    non-empty >=0.3.1,     utility-ht,     transformers,+    enummapset,     containers,     random,     array,     base -Benchmark board-games-benchmark+Benchmark mastermind-strategy   Type:             exitcode-stdio-1.0   Default-Language: Haskell2010+  GHC-Options:      -Wall -fwarn-missing-import-lists -threaded -rtsopts+  Hs-Source-Dirs:   benchmark, private+  Main-Is:          MastermindStrategy.hs+  Other-Modules:+    Game.Utility+  Build-Depends:+    board-games,+    QuickCheck,+    parallel >=3.2.1 && <3.3,+    transformers,+    enummapset,+    containers,+    random,+    non-empty,+    utility-ht,+    base++Benchmark mastermind-benchmark+  Type:             exitcode-stdio-1.0+  Default-Language: Haskell2010   GHC-Options:      -Wall -fwarn-missing-import-lists -threaded   Hs-Source-Dirs:   benchmark-  Main-Is:          Main.hs+  Main-Is:          MastermindSpeed.hs   Build-Depends:     board-games,     criterion >=0.6 && <1.6,+    enummapset,     containers,     non-empty,     utility-ht,
+ private/Game/Utility.hs view
@@ -0,0 +1,65 @@+module Game.Utility where++import qualified System.Random as Rnd++import qualified Control.Monad.Trans.State as MS+import Control.Monad (liftM, liftM2)++import qualified Data.Foldable as Fold+import qualified Data.EnumMap as EnumMap+import qualified Data.Map as Map+import Data.EnumMap (EnumMap)+import Data.Map (Map)++import qualified Test.QuickCheck as QC+++readMaybe :: (Read a) => String -> Maybe a+readMaybe str =+   case reads str of+      [(a,"")] -> Just a+      _ -> Nothing++nullToMaybe :: [a] -> Maybe [a]+nullToMaybe [] = Nothing+nullToMaybe s  = Just s++-- candidate for random-utility, cf. module htam:Election, markov-chain+-- for Sets it would be more efficient to use Set.elemAt+randomSelect :: (Rnd.RandomGen g, Monad m) => [a] -> MS.StateT g m a+randomSelect items =+   liftM (items!!) $ MS.state $ Rnd.randomR (0, length items-1)+++histogram :: (Ord a) => [a] -> Map a Int+histogram = Map.fromListWith (+) . map (\a -> (a,1))+++-- unfortunately it is not a Monoid because mergeChoice is not associative+data Choice a = Choice (EnumMap a Int) Int+   deriving (Eq, Show)++instance (QC.Arbitrary a, Enum a) => QC.Arbitrary (Choice a) where+   arbitrary = do+      bag <-+         fmap EnumMap.fromList $ QC.listOf $+         liftM2 (,) QC.arbitrary (fmap QC.getNonNegative QC.arbitrary)+      count <- QC.choose (0, Fold.sum bag)+      return $ Choice bag count+   shrink (Choice bag count) =+      map (\(xs,c) ->+            let b = fmap abs $ EnumMap.fromList xs+            in Choice b (min c $ Fold.sum b)) $+      QC.shrink (EnumMap.toList bag, count)++noChoice :: (Enum a) => Choice a+noChoice = Choice EnumMap.empty 0++-- it is hard to test whether fullEval absorbs+mergeChoice :: (Enum a) => Choice a -> Choice a -> Choice a+mergeChoice (Choice symbolsA countA) (Choice symbolsB countB) =+   Choice+      (EnumMap.unionWith max symbolsA symbolsB)+      (countA + countB+         - min (min countA countB)+               (Fold.sum (EnumMap.intersectionWith min symbolsA symbolsB)))
src/Game/Mastermind.hs view
@@ -4,7 +4,10 @@    matching,    matchingSimple, +   randomizedAttempt,    mixedRandomizedAttempt,+   scanningRandomizedAttempt,+   separatingRandomizedAttempt,    partitionSizes,     mainSimple,@@ -17,23 +20,32 @@ 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 Game.Mastermind.CodeSet    (intersection, (*&), (#*&), unit, empty, union, unions, cube, )-import Game.Utility (randomSelect, )+import Game.Utility+   (Choice(Choice), mergeChoice, noChoice, randomSelect, histogram) -import qualified Data.NonEmpty.Set as NonEmptySet+import qualified Data.EnumMap as EnumMap+import qualified Data.EnumSet as EnumSet import qualified Data.Map as Map import qualified Data.Set as Set+import Data.EnumMap (EnumMap)+import Data.EnumSet (EnumSet) +import qualified Data.NonEmpty as NonEmpty+import qualified Data.List as List import Data.NonEmpty ((!:)) import Data.List.HT (partition, ) import Data.Tuple.HT (mapPair, ) import Data.Maybe.HT (toMaybe, )-import Data.Maybe (listToMaybe, )-import Control.Monad (guard, when, replicateM, )+import Data.Maybe (listToMaybe, fromMaybe)+import Data.Ord.HT (comparing)+import Data.Eq.HT (equating)  import qualified Control.Monad.Trans.State as MS-import qualified Control.Monad.Trans.Class as MT+import Control.Monad.IO.Class (liftIO)+import Control.Monad (guard, when, replicateM, liftM2, )  import qualified System.Random as Rnd import qualified System.IO as IO@@ -45,52 +57,54 @@ {- | Given the code and a guess, compute the evaluation. -}-evaluate :: (Ord a) => [a] -> [a] -> Eval+evaluate :: (Enum a) => [a] -> [a] -> Eval evaluate code attempt =    uncurry Eval $    mapPair       (length,-       sum . Map.elems .-       uncurry (Map.intersectionWith min) .-       mapPair (histogram,histogram) . unzip) $-   partition (uncurry (==)) $+       sum . EnumMap.elems .+       uncurry (EnumMap.intersectionWith min) .+       mapPair (bagFromList,bagFromList) . unzip) $+   partition (uncurry $ equating fromEnum) $    zip code attempt  {- *Game.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"]-*Game.Mastermind> CodeSet.flatten $ matching (Set.fromList ['a'..'c']) "aabbb" (Eval 2 0)+*Game.Mastermind> CodeSet.flatten $ matching (EnumSet.fromList ['a'..'c']) "aabbb" (Eval 2 0) ["aaaaa","aaaac","aaaca","aaacc","aacaa","aacac","aacca","aaccc","acbcc","accbc","acccb","cabcc","cacbc","caccb","ccbbc","ccbcb","cccbb"] -}  -histogram :: (Ord a) => [a] -> Map.Map a Int-histogram = Map.fromListWith (+) . map (\a -> (a,1))+bagFromList :: (Enum a) => [a] -> EnumMap a Int+bagFromList = EnumMap.fromListWith (+) . map (\a -> (a,1)) -selectFromHistogram :: (Ord a) => Map.Map a Int -> [(a, Map.Map a Int)]-selectFromHistogram hist =-   map (\a -> (a, Map.update (\n -> toMaybe (n>1) (pred n)) a hist)) $-   Map.keys hist-{--   Map.toList $-   Map.mapWithKey-      (\a _ -> Map.update (\n -> toMaybe (n>1) (pred n)) a hist) hist--}+selectFromBag, _selectFromBag ::+   (Enum a) => EnumMap a Int -> [(a, EnumMap a Int)]+selectFromBag hist =+   map (\a -> (a, EnumMap.update (\n -> toMaybe (n>1) (pred n)) a hist)) $+   EnumMap.keys hist +_selectFromBag hist =+   EnumMap.toList $+   EnumMap.mapWithKey+      (\a _ -> EnumMap.update (\n -> toMaybe (n>1) (pred n)) a hist) hist++ {- | A variant of the game: It is only possible to specify number of symbols at right places.  The results of 'matching' and 'matchingSimple' cannot be compared. -}-matchingSimple :: Ord a => Set.Set a -> [a] -> Int -> [[Set.Set a]]+matchingSimple :: Enum a => EnumSet a -> [a] -> Int -> [[EnumSet a]] matchingSimple alphabet code rightPlaces =    map       (zipWith          (\symbol right ->             if right-              then Set.singleton symbol-              else Set.delete symbol alphabet)+              then EnumSet.singleton symbol+              else EnumSet.delete symbol alphabet)          code) $    possibleRightPlaces (length code) rightPlaces @@ -121,7 +135,7 @@ of codes and their evaluations. The searched code is in the intersection of all corresponding code sets. -}-matching :: (CodeSet.C set, Ord a) => Set.Set a -> [a] -> Eval -> set a+matching :: (CodeSet.C set, Enum a) => EnumSet a -> [a] -> Eval -> set a matching alphabet =    let findCodes =           foldr@@ -131,13 +145,13 @@                   else                     (unions $ do                         guard (rightSymbols > 0)-                        (src, floating1) <- selectFromHistogram floating0-                        guard (c /= src)+                        (src, floating1) <- selectFromBag floating0+                        guard (not $ equating fromEnum c src)                         return $ src #*& go (rightSymbols-1) floating1)                     `union`-                    (Set.difference-                        (Set.delete c alphabet)-                        (Map.keysSet floating0) *&+                    (EnumSet.difference+                        (EnumSet.delete c alphabet)+                        (EnumMap.keysSet floating0) *&                      go rightSymbols floating0))              (\rightSymbols _floating ->                 if rightSymbols>0@@ -149,11 +163,11 @@           (\pattern ->              let patternCode = zip pattern code              in  findCodes patternCode rightSymbols $-                 histogram $ map snd $ filter (not . fst) patternCode) $+                 bagFromList $ map snd $ filter (not . fst) patternCode) $        possibleRightPlaces (length code) rightPlaces  -partitionSizes :: (Ord a) => Set.Set a -> [a] -> [(Eval, Integer)]+partitionSizes :: (Enum a) => EnumSet a -> [a] -> [(Eval, Integer)] partitionSizes alphabet code =    map (\eval -> (eval, CodeSetTree.size $ matching alphabet code eval)) $    possibleEvaluations (length code)@@ -166,41 +180,44 @@   interaction ::-   (CodeSetTree.T Char -> MS.StateT state Maybe [Char]) ->+   (CodeSetTree.T Char -> MS.State state (Maybe [Char])) ->    state -> NonEmptySet.T Char -> Int -> IO () interaction select initial alphabet n =-   let go state set =-          case MS.runStateT (select set) state of-             Nothing -> putStrLn "contradicting evaluations"-             Just (attempt, newState) -> do-                putStr $ show attempt ++ " " ++-                   show (CodeSet.size set, CodeSet.representationSize set,-                         Set.size (CodeSet.symbols set)) ++ " "-                IO.hFlush IO.stdout-                eval <- getLine-                let evalHist = histogram eval-                    evalHistRem =-                       Map.keys $ Map.delete 'o' $ Map.delete 'x' evalHist-                when (not $ null evalHistRem)-                   (putStrLn $ "ignoring: " ++ evalHistRem)-                let rightPlaces  = length (filter ('x' ==) eval)-                    rightSymbols = length (filter ('o' ==) eval)+   let go set = do+          newGuess <- MS.state $ MS.runState $ select set+          case newGuess of+             Nothing -> liftIO $ putStrLn "contradicting evaluations"+             Just attempt -> do+                liftIO $ do+                   putStr $+                      show attempt ++ " " +++                      show (CodeSet.size set, CodeSet.representationSize set,+                            EnumSet.size (CodeSet.symbols set)) ++ " "+                   IO.hFlush IO.stdout+                eval <- liftIO getLine+                let getEval =+                      fmap (fromMaybe 0) . MS.state .+                      EnumMap.updateLookupWithKey (\_ _ -> Nothing)+                let ((rightPlaces,rightSymbols), ignored) =+                      MS.runState (liftM2 (,) (getEval 'x') (getEval 'o')) $+                      bagFromList eval+                when (not $ EnumMap.null ignored) $+                   liftIO $ putStrLn $ "ignoring: " ++ EnumMap.keys ignored                 if rightPlaces >= n-                  then putStrLn "I won!"-                  else go newState $ intersection set $+                  then liftIO $ putStrLn "I won!"+                  else go $ intersection set $                        matching (NonEmptySet.flatten alphabet) attempt $                        Eval rightPlaces rightSymbols-   in  go initial (cube alphabet n)+   in MS.evalStateT (go (cube alphabet n)) initial  mainSimple :: NonEmptySet.T Char -> Int -> IO ()-mainSimple = interaction (MT.lift . listToMaybe . CodeSet.flatten) ()+mainSimple = interaction (return . listToMaybe . CodeSet.flatten) ()  {- | minimum of maximums using alpha-beta-pruning -}-minimax :: (Ord b) => (a -> [b]) -> [a] -> a-minimax _ [] = error "minimax of empty list"-minimax f (a0:rest) =+minimax :: (Ord b) => (a -> [b]) -> NonEmpty.T [] a -> a+minimax f (NonEmpty.Cons a0 rest) =    fst $    foldl       (\old@(_minA, minB) a ->@@ -211,14 +228,14 @@ {- | Remove all but one unused symbols from the alphabet. -}-reduceAlphabet :: (CodeSet.C set, Ord a) => set a -> Set.Set a -> Set.Set a+reduceAlphabet :: (CodeSet.C set, Enum a) => set a -> EnumSet a -> EnumSet a reduceAlphabet set alphabet =    let symbols = CodeSet.symbols set-   in  Set.union symbols $ Set.fromList $ take 1 $ Set.toList $-       Set.difference alphabet symbols+   in  EnumSet.union symbols $ EnumSet.fromList $ take 1 $ EnumSet.toList $+       EnumSet.difference alphabet symbols  bestSeparatingCode ::-   (CodeSet.C set, Ord a) => Int -> set a -> [[a]] -> [a]+   (CodeSet.C set, Enum a) => Int -> set a -> NonEmpty.T [] [a] -> [a] bestSeparatingCode n set =    let alphabet = CodeSet.symbols set    in minimax $ \attempt ->@@ -230,17 +247,17 @@ all matching codes and build a histogram. -} bestSeparatingCodeHistogram ::-   (CodeSet.C set, Ord a) => set a -> [[a]] -> [a]+   (CodeSet.C set, Enum a) => set a -> NonEmpty.T [] [a] -> [a] bestSeparatingCodeHistogram set =    minimax $ \attempt ->       Map.elems $ histogram $ map (evaluate attempt) $ CodeSet.flatten set  propBestSeparatingCode ::-   (CodeSet.C set, Ord a) => Int -> set a -> [[a]] -> Bool+   (CodeSet.C set, Enum a) => Int -> set a -> NonEmpty.T [] [a] -> Bool propBestSeparatingCode n set attempts =-   bestSeparatingCode n set attempts-   ==-   bestSeparatingCodeHistogram set attempts+   equating (map fromEnum)+      (bestSeparatingCode n set attempts)+      (bestSeparatingCodeHistogram set attempts)   {-@@ -250,16 +267,13 @@ from mainSimple and needs more attempts. -} randomizedAttempt ::-   (CodeSet.C set, Rnd.RandomGen g, Ord a) =>-   Int -> set a -> MS.StateT g Maybe [a]+   (CodeSet.C set, Rnd.RandomGen g, Enum a) =>+   Int -> set a -> MS.State g (Maybe [a]) randomizedAttempt n set = do-   randomAttempts <--      replicateM 10 $-      replicateM n $-      randomSelect . Set.toList $-      CodeSet.symbols set-   let possible = CodeSet.flatten set-       somePossible =+   let symbolSet = CodeSet.symbols set+   let randomCode = replicateM n $ randomSelect $ EnumSet.toList symbolSet+   randomAttempts <- liftM2 (!:) randomCode $ replicateM 9 randomCode+   let somePossible =           -- take 10 possible codes           let size = CodeSet.size set               num = 10@@ -268,9 +282,24 @@               take num $               map (flip div (fromIntegral num)) $               iterate (size+) 0-   _ <- MT.lift $ listToMaybe possible-   return $ bestSeparatingCode n set $ somePossible ++ randomAttempts+   return $+      toMaybe (not $ CodeSet.null set) $+      bestSeparatingCode n set $+      NonEmpty.appendLeft somePossible randomAttempts ++withNonEmptyCodeSet ::+   (Monad m, CodeSet.C set, Enum a) =>+   set a ->+   (NonEmpty.T [] [a] -> m (Maybe [a])) ->+   m (Maybe [a])+withNonEmptyCodeSet set f =+   case CodeSet.flatten set of+      [] -> return Nothing+      x:[] -> return $ Just x+      x:_:[] -> return $ Just x+      x:xs -> f $ x!:xs+ {- | In the beginning we choose codes that separate reasonably well, based on heuristics.@@ -283,21 +312,18 @@ without the extra symbol? -} separatingRandomizedAttempt ::-   (CodeSet.C set, Rnd.RandomGen g, Ord a) =>-   Int -> Set.Set a -> set a -> MS.StateT g Maybe [a]-separatingRandomizedAttempt n alphabet0 set = do-   case CodeSet.size set of-      0 -> MT.lift Nothing-      1 -> return $ head $ CodeSet.flatten set-      2 -> return $ head $ CodeSet.flatten set-      size ->-         let alphabet = reduceAlphabet set alphabet0-             alphabetSize = Set.size alphabet-             bigSize = toInteger size-         in  if bigSize * (bigSize + toInteger alphabetSize ^ n) <= 1000000-               then return $ bestSeparatingCodeHistogram set $-                    CodeSet.flatten set ++ replicateM n (Set.toList alphabet)-               else randomizedAttempt n set+   (CodeSet.C set, Rnd.RandomGen g, Enum a) =>+   Int -> EnumSet a -> set a -> MS.State g (Maybe [a])+separatingRandomizedAttempt n alphabet0 set =+   withNonEmptyCodeSet set $ \flattenedSet ->+      let size = CodeSet.size set+          alphabet = reduceAlphabet set alphabet0+          alphabetSize = EnumSet.size alphabet+      in if size * (size + toInteger alphabetSize ^ n) <= 1000000+            then return $ Just $ bestSeparatingCodeHistogram set $+                 NonEmpty.appendRight flattenedSet $+                 replicateM n (EnumSet.toList alphabet)+            else randomizedAttempt n set  {- | In the beginning we simply choose a random code@@ -306,20 +332,80 @@ then we compare different alternatives. -} mixedRandomizedAttempt ::-   (CodeSet.C set, Rnd.RandomGen g, Ord a) =>-   Int -> set a -> MS.StateT g Maybe [a]-mixedRandomizedAttempt n set = do-   case CodeSet.size set of-      0 -> MT.lift Nothing-      1 -> return $ head $ CodeSet.flatten set-      2 -> return $ head $ CodeSet.flatten set-      size ->-         if size <= 100+   (CodeSet.C set, Rnd.RandomGen g, Enum a) =>+   Int -> set a -> MS.State g (Maybe [a])+mixedRandomizedAttempt n set =+   withNonEmptyCodeSet set $ \ _flattenedSet ->+      let size = CodeSet.size set+      in if size <= 100            then randomizedAttempt n set-           else-              fmap (CodeSet.select set) $-              MS.state $ Rnd.randomR (0, size-1)+           else fmap (Just . CodeSet.select set) $+                MS.state $ Rnd.randomR (0, size-1) +{- |+This strategy starts with scanning the alphabet.+That is, we test sets of different symbols we did not try so far.+The idea is to sort out unused symbols early.+This is especially useful when the alphabet is large,+i.e. its size is some multiples of the code width.++We stop scanning when we are sure to have seen+all characters of the secret code.+E.g.:++> vicx+> alsn   o+> mfgt   o+> hjqw+> edpz   oo+> bkru   - we already know, that these cannot be in the secret code++We use the 'Choice' data type+for tracking the number of symbols that we can minimally use+from the ones we have already tried.+The order of applying 'mergeChoice' matters,+but I see no easy way to find a good order+or to make it robust against re-ordering.++If the user tells us that all symbols in a code are used,+then the scanning phase ends immediately.+This happens automatically according to our way of processing 'Choice's.+-}+scanningRandomizedAttempt ::+   (CodeSet.C set, Rnd.RandomGen g, Enum a) =>+   Int -> EnumSet a -> [([a], Eval)] -> set a -> MS.State g (Maybe [a])+scanningRandomizedAttempt n alphabet oldGuesses set = do+   let sumEval (Eval correctPlaces correctSymbols) =+         correctPlaces + correctSymbols+   let (Choice totalBag count) =+         foldl mergeChoice noChoice $+         map (uncurry Choice . mapPair (bagFromList, sumEval)) oldGuesses+   let unusedSymbols = EnumSet.difference alphabet $ EnumMap.keysSet totalBag+   if count>=n+      then randomizedAttempt n set+      else+         if EnumSet.size unusedSymbols <= n+            then mixedRandomizedAttempt n set+            else do+               let nextSymbols = EnumSet.toList unusedSymbols+               keys <-+                  mapM+                     (const $ MS.state $ Rnd.randomR (0,1::Double))+                     nextSymbols+               return $ Just $ map snd $ take n $+                  List.sortBy (comparing fst) $ zip keys nextSymbols+{-+   if count>=n || EnumSet.size unusedSymbols <= n+      then randomizedAttempt n set+      else do+         let nextSymbols = EnumSet.toList unusedSymbols+         keys <-+            mapM (const $ MS.state $ Rnd.randomR (0,1::Double)) nextSymbols+         return $ map snd $ take n $+            List.sortBy (comparing fst) $ zip keys nextSymbols+-}++ mainRandom :: NonEmptySet.T Char -> Int -> IO () mainRandom alphabet n = do    g <- Rnd.getStdGen@@ -347,6 +433,6 @@ contradicting evaluations *Game.Mastermind> map (evaluate "amiga") ["uvqcm","wukjv","lmoci","caoab","mbadi","ombed","lqbia"] [Eval 0 1,Eval 0 0,Eval 1 1,Eval 0 2,Eval 0 3,Eval 1 0,Eval 1 1]-*Game.Mastermind> map (\attempt -> member "amiga" $ matching (Set.fromList $ ['a'..'z']) attempt (evaluate "amiga" attempt)) ["uvqcm","wukjv","lmoci","caoab","mbadi","ombed","lqbia"]+*Game.Mastermind> map (\attempt -> member "amiga" $ matching (EnumSet.fromList $ ['a'..'z']) attempt (evaluate "amiga" attempt)) ["uvqcm","wukjv","lmoci","caoab","mbadi","ombed","lqbia"] [True,True,True,True,False,True,False] -}
src/Game/Mastermind/CodeSet.hs view
@@ -7,10 +7,11 @@    (*&), (#*&),    ) where +import qualified Game.Mastermind.NonEmptyEnumSet as NonEmptySet+ import qualified Data.NonEmpty.Class as NonEmptyC-import qualified Data.NonEmpty.Set as NonEmptySet import qualified Data.NonEmpty as NonEmpty-import qualified Data.Set as Set+import Data.EnumSet (EnumSet) import Data.Function.HT (nest, ) import Data.Ord.HT (comparing, ) @@ -19,23 +20,23 @@  class C set where    empty :: set a-   union, intersection :: (Ord a) => set a -> set a -> set a+   union, intersection :: (Enum a) => set a -> set a -> set a    unit :: set a    leftNonEmptyProduct :: NonEmptySet.T a -> set a -> set a-   flatten :: (Ord a) => set a -> [[a]]-   symbols :: (Ord a) => set a -> Set.Set a+   flatten :: (Enum a) => set a -> [[a]]+   symbols :: (Enum a) => set a -> EnumSet a    null :: set a -> Bool    size :: set a -> Integer-   select :: set a -> Integer -> [a]+   select :: (Enum a) => set a -> Integer -> [a]    representationSize :: set a -> Int    -- | simplify set representation by combining set products where possible-   compress :: (Ord a) => set a -> set a+   compress :: (Enum a) => set a -> set a  cube :: (C set) => NonEmptySet.T a -> Int -> set a cube alphabet n =    nest n (leftNonEmptyProduct alphabet) unit -unions :: (C set, Ord a) => [set a] -> set a+unions :: (C set, Enum a) => [set a] -> set a unions = foldr union empty  @@ -47,7 +48,7 @@ thus the procedure would always insert at the front. This is what 'intersections' does anyway. -}-intersectionsPQ :: (C set, Ord a) => NonEmpty.T [] (set a) -> set a+intersectionsPQ :: (C set, Enum a) => NonEmpty.T [] (set a) -> set a intersectionsPQ =    let go (NonEmpty.Cons (_, set) []) = set        go (NonEmpty.Cons (_,x) ((_,y):rest)) =@@ -59,10 +60,10 @@        NonEmptyC.sortBy (comparing fst) .        fmap (\set -> (representationSize set, set)) -intersections :: (C set, Ord a) => NonEmpty.T [] (set a) -> set a+intersections :: (C set, Enum a) => NonEmpty.T [] (set a) -> set a intersections = NonEmpty.foldl1 intersection . nonEmptySortKey size --- cannot be easily generalized for inclusion in non-empty package+-- ToDo: import from NonEmptyC nonEmptySortKey :: (Ord b) => (a -> b) -> NonEmpty.T [] a -> NonEmpty.T [] a nonEmptySortKey f =    fmap snd . NonEmptyC.sortBy (comparing fst) . fmap (\x -> (f x, x))@@ -73,12 +74,11 @@ {- | Like 'leftNonEmptyProduct' but the left operand can be empty. -}-(*&) :: (C set, Ord a) => Set.Set a -> set a -> set a+(*&) :: (C set, Enum a) => EnumSet a -> set a -> set a c *& set =    case NonEmptySet.fetch c of       Nothing -> empty       Just nec -> leftNonEmptyProduct nec set -(#*&) :: (C set) => a -> set a -> set a-c #*& set =-   leftNonEmptyProduct (NonEmptySet.singleton c) set+(#*&) :: (C set, Enum a) => a -> set a -> set a+c #*& set = leftNonEmptyProduct (NonEmptySet.singleton c) set
src/Game/Mastermind/CodeSet/Tree.hs view
@@ -4,14 +4,14 @@    ) where  import qualified Game.Mastermind.CodeSet as CodeSet-import Game.Utility (nonEmptySetToList, )+import qualified Game.Mastermind.NonEmptyEnumSet as NonEmptySet  import Control.Monad (liftM2, mfilter, ) -import qualified Data.NonEmpty.Set as NonEmptySet import qualified Data.NonEmpty as NonEmpty+import qualified Data.EnumSet as EnumSet import qualified Data.Map as Map-import qualified Data.Set as Set+import Data.EnumSet (EnumSet)  import Data.Tuple.HT (mapFst, swap, ) import Data.Ord.HT (comparing, )@@ -48,18 +48,18 @@    compress = compress  -flatten :: (Ord a) => T a -> [[a]]+flatten :: (Enum a) => T a -> [[a]] flatten End = [[]] flatten (Products xs) =    concatMap-      (\(a,b) -> liftM2 (:) (nonEmptySetToList a) (flatten b))+      (\(a,b) -> liftM2 (:) (NonEmptySet.toFlatList a) (flatten b))       (Map.toList xs) -symbols :: (Ord a) => T a -> Set.Set a-symbols End = Set.empty+symbols :: (Enum a) => T a -> EnumSet a+symbols End = EnumSet.empty symbols (Products xps) =-   Set.unions $-   map (\(x,xs) -> Set.union (NonEmptySet.flatten x) (symbols xs)) $+   EnumSet.unions $+   map (\(x,xs) -> EnumSet.union (NonEmptySet.flatten x) (symbols xs)) $    Map.toList xps  @@ -71,7 +71,7 @@    Map.toList xs  -- FixMe: somehow inefficient, because the sizes of subsets are recomputed several times-select :: T a -> Integer -> [a]+select :: (Enum a) => T a -> Integer -> [a] select End n =    case compare n 0 of      LT -> error "CodeSet.select.end: index negative"@@ -93,7 +93,7 @@          [] -> error "CodeSet.select: index too large"          ((x,xs), ((n1,_), xsSize)) : _ ->             let (j,k) = divMod n1 xsSize-            in  (nonEmptySetToList x !! fromInteger j)+            in  (NonEmptySet.toFlatList x !! fromInteger j)                 : select xs k  representationSize :: T a -> Int@@ -107,18 +107,18 @@ We could try to merge set products. I'll first want to see, whether this is needed in a relevant number of cases. -}-union :: (Ord a) => T a -> T a -> T a+union :: (Enum a) => T a -> T a -> T a union End End = End union (Products xs) (Products ys) = Products (Map.unionWith union xs ys) union _ _ = error "CodeSet.union: sets with different tuple size" -intersection :: (Ord a) => T a -> T a -> T a+intersection :: (Enum a) => T a -> T a -> T a intersection End End = End intersection (Products xps) (Products yps) =    Products $ Map.fromListWith union $ normalizeProducts $    liftM2       (\(x,xs) (y,ys) ->-         (Set.intersection (NonEmptySet.flatten x) (NonEmptySet.flatten y),+         (EnumSet.intersection (NonEmptySet.flatten x) (NonEmptySet.flatten y),           intersection xs ys))       (Map.toList xps)       (Map.toList yps)@@ -128,7 +128,7 @@ {- | Remove empty set products. -}-normalizeProducts :: (Ord a) => [(Set.Set a, T a)] -> [(NonEmptySet.T a, T a)]+normalizeProducts :: (Enum a) => [(EnumSet a, T a)] -> [(NonEmptySet.T a, T a)] normalizeProducts =    mapMaybe       (\(x,xs) ->@@ -139,7 +139,7 @@ Comparing for structural equivalence is overly strict, but a lot simpler than comparing for set equivalence. -}-propIntersections :: (Ord a) => NonEmpty.T [] (T a) -> Bool+propIntersections :: (Enum a) => NonEmpty.T [] (T a) -> Bool propIntersections xs =    equating Indexable       (CodeSet.intersections xs)@@ -155,14 +155,14 @@ -} newtype Indexable a = Indexable (T a) -instance (Eq a) => Eq (Indexable a) where+instance (Enum a) => Eq (Indexable a) where    (Indexable x) == (Indexable y) =       case (x,y) of          (End,End) -> True          (Products xs, Products ys) -> equating (fmap Indexable) xs ys          _ -> False -instance (Ord a) => Ord (Indexable a) where+instance (Enum a) => Ord (Indexable a) where    compare (Indexable x) (Indexable y) =       case (x,y) of          (End,End) -> EQ@@ -172,7 +172,7 @@          (Products xs, Products ys) -> comparing (fmap Indexable) xs ys  -compress :: (Ord a) => T a -> T a+compress :: (Enum a) => T a -> T a compress End = End compress (Products xs) =    Products $@@ -182,7 +182,7 @@    map (mapFst Indexable) $ map swap $ Map.toList $    fmap compress xs -member :: (Ord a) => [a] -> T a -> Bool+member :: (Enum a) => [a] -> T a -> Bool member [] End = True member (c:cs) (Products xps) =    any (\(x,xs) -> NonEmptySet.member c x && member cs xs) $
src/Game/Mastermind/CodeSet/Union.hs view
@@ -5,13 +5,14 @@    ) where  import qualified Game.Mastermind.CodeSet as CodeSet-import Game.Utility (nonEmptySetToList, )+import qualified Game.Mastermind.NonEmptyEnumSet as NonEmptySet -import qualified Data.NonEmpty.Set as NonEmptySet import qualified Data.NonEmpty as NonEmpty+import qualified Data.EnumSet as EnumSet import qualified Data.Set as Set import qualified Data.List.HT as ListHT import qualified Data.List as List+import Data.EnumSet (EnumSet) import Data.Maybe (mapMaybe, )  import Control.Monad (liftM2, guard, )@@ -24,7 +25,7 @@ -} newtype T a = Cons [[NonEmptySet.T a]] -instance (Ord a, Show a) => Show (T a) where+instance (Enum a, Show a) => Show (T a) where    showsPrec n cs =       showParen (n>=10) $       showString "CodeSet.fromLists " . shows (toLists cs)@@ -44,17 +45,17 @@    compress = id  -toLists :: (Ord a) => T a -> [[[a]]]-toLists (Cons xs) = map (map nonEmptySetToList) xs+toLists :: (Enum a) => T a -> [[[a]]]+toLists (Cons xs) = map (map NonEmptySet.toFlatList) xs -fromLists :: (Ord a) => [[NonEmpty.T [] a]] -> T a+fromLists :: (Enum a) => [[NonEmpty.T [] a]] -> T a fromLists = Cons . map (map NonEmptySet.fromList) -flatten :: (Ord a) => T a -> [[a]]+flatten :: (Enum a) => T a -> [[a]] flatten = concatMap sequence . toLists -symbols :: (Ord a) => T a -> Set.Set a-symbols = Set.unions . map Set.unions . flattenFactors+symbols :: (Enum a) => T a -> EnumSet a+symbols = EnumSet.unions . map EnumSet.unions . flattenFactors  cube :: Int -> NonEmptySet.T a -> T a cube n alphabet = Cons [replicate n alphabet]@@ -67,7 +68,7 @@ productSizes (Cons x) =    map (product . map (fromIntegral . NonEmptySet.size)) x -select :: T a -> Integer -> [a]+select :: (Enum a) => T a -> Integer -> [a] select set@(Cons xs) n0 =    let sizes = productSizes set    in  if n0<0@@ -87,7 +88,7 @@                               divMod n2                                  (fromIntegral $ NonEmptySet.size componentSet)                       in  (n3,-                           nonEmptySetToList componentSet !! fromInteger i))+                           NonEmptySet.toFlatList componentSet !! fromInteger i))                    n1 prod  representationSize :: T a -> Int@@ -102,33 +103,33 @@ union :: T a -> T a -> T a union (Cons x) (Cons y) = Cons (x++y) -intersection :: (Ord a) => T a -> T a -> T a+intersection :: (Enum a) => T a -> T a -> T a intersection x y =    normalize $-   liftM2 (zipWith Set.intersection) (flattenFactors x) (flattenFactors y)+   liftM2 (zipWith EnumSet.intersection) (flattenFactors x) (flattenFactors y) -member :: (Ord a) => [a] -> T a -> Bool+member :: (Enum a) => [a] -> T a -> Bool member code (Cons xs) =    any (and . zipWith NonEmptySet.member code) xs  {- | Remove empty set products. -}-normalize :: (Ord a) => [[Set.Set a]] -> T a+normalize :: (Enum a) => [[EnumSet a]] -> T a normalize = Cons . mapMaybe (mapM NonEmptySet.fetch) -flattenFactors :: (Ord a) => T a -> [[Set.Set a]]+flattenFactors :: (Enum a) => T a -> [[EnumSet a]] flattenFactors (Cons xs) = map (map NonEmptySet.flatten) xs  -disjointProduct :: (Ord a) => [Set.Set a] -> [Set.Set a] -> Bool+disjointProduct :: (Enum a) => [EnumSet a] -> [EnumSet a] -> Bool disjointProduct prod0 prod1 =-   any Set.null $ zipWith Set.intersection prod0 prod1+   any EnumSet.null $ zipWith EnumSet.intersection prod0 prod1  {- | for debugging: list all pairs of products, that overlap -}-overlappingPairs :: (Ord a) => T a -> [([Set.Set a], [Set.Set a])]+overlappingPairs :: (Enum a) => T a -> [([EnumSet a], [EnumSet a])] overlappingPairs set = do    prod0:rest <- ListHT.tails $ flattenFactors set    prod1 <- rest@@ -138,13 +139,13 @@ {- | for debugging: list all subsets, that are contained in more than one product -}-overlapping :: (Ord a) => T a -> [([Set.Set a], [[Set.Set a]])]+overlapping :: (Enum a) => T a -> [([EnumSet a], [[EnumSet a]])] overlapping set = do    let xs = flattenFactors set    subset <- Set.toList $ Set.fromList $ do       prod0:rest <- ListHT.tails xs       prod1 <- rest-      let sec = zipWith Set.intersection prod0 prod1-      guard $ all (not . Set.null) $ sec+      let sec = zipWith EnumSet.intersection prod0 prod1+      guard $ all (not . EnumSet.null) $ sec       return sec    return (subset, filter (not . disjointProduct subset) xs)
src/Game/Mastermind/HTML.hs view
@@ -7,6 +7,7 @@  import qualified Game.Mastermind.CodeSet.Tree as CodeSetTree import qualified Game.Mastermind.CodeSet as CodeSet+import qualified Game.Mastermind.NonEmptyEnumSet as NonEmptySet import qualified Game.Mastermind as MM import Game.Utility (readMaybe, nullToMaybe, randomSelect, ) @@ -17,11 +18,10 @@  import qualified Data.List as List import qualified Data.List.HT as ListHT-import qualified Data.NonEmpty.Set as NonEmptySet import qualified Data.NonEmpty as NonEmpty import Data.Foldable (fold, foldMap) import Data.NonEmpty ((!:))-import Data.Tuple.HT (mapPair, )+import Data.Tuple.HT (mapSnd) import Data.Maybe.HT (toMaybe, )  import qualified Control.Monad.Trans.State as MS@@ -162,11 +162,10 @@                       map (uncurry (MM.matching                               (NonEmptySet.flatten alphabet))) moves                    (attempt,newSeed) =-                      maybe-                         (Nothing, seed)-                         (mapPair (Just, fst . Rnd.random)) $-                      MS.runStateT-                         (MM.mixedRandomizedAttempt width remaining)+                      mapSnd (fst . Rnd.random) $+                      MS.runState+                         (MM.scanningRandomizedAttempt width+                            (NonEmptySet.flatten alphabet) moves remaining)                          (Rnd.mkStdGen seed)                in  state                       (width, symbols, newSeed, Just moves, attempt)
+ src/Game/Mastermind/NonEmptyEnumSet.hs view
@@ -0,0 +1,40 @@+module Game.Mastermind.NonEmptyEnumSet where++import qualified Data.NonEmpty as NonEmpty+import qualified Data.EnumSet as EnumSet+import Data.EnumSet (EnumSet)+import Data.Maybe.HT (toMaybe)+import Data.Ord.HT (comparing)+import Data.Eq.HT (equating)+++newtype T a = Cons {flatten :: EnumSet a}+   deriving (Show)++instance Eq (T a) where+   (==) = equating (EnumSet.enumSetToIntSet . flatten)++instance Ord (T a) where+   compare = comparing (EnumSet.enumSetToIntSet . flatten)+++size :: T a -> Int+size = EnumSet.size . flatten++member :: (Enum a) => a -> T a -> Bool+member x = EnumSet.member x . flatten++fromList :: (Enum a) => NonEmpty.T [] a -> T a+fromList = Cons . EnumSet.fromList . NonEmpty.flatten++toFlatList :: (Enum a) => T a -> [a]+toFlatList = EnumSet.toList . flatten++fetch :: EnumSet a -> Maybe (T a)+fetch set = toMaybe (not $ EnumSet.null set) (Cons set)++singleton :: (Enum a) => a -> T a+singleton = Cons . EnumSet.singleton++union :: T a -> T a -> T a+union (Cons a) (Cons b) = Cons $ EnumSet.union a b
− src/Game/Utility.hs
@@ -1,29 +0,0 @@-module Game.Utility where--import qualified System.Random as Rnd--import qualified Control.Monad.Trans.State as MS-import Control.Monad (liftM, )--import qualified Data.NonEmpty.Set as NonEmptySet-import qualified Data.NonEmpty as NonEmpty---readMaybe :: (Read a) => String -> Maybe a-readMaybe str =-   case reads str of-      [(a,"")] -> Just a-      _ -> Nothing--nullToMaybe :: [a] -> Maybe [a]-nullToMaybe [] = Nothing-nullToMaybe s  = Just s---- candidate for random-utility, cf. module htam:Election, markov-chain--- for Sets it would be more efficient to use Set.elemAt-randomSelect :: (Rnd.RandomGen g, Monad m) => [a] -> MS.StateT g m a-randomSelect items =-   liftM (items!!) $ MS.StateT $ return . Rnd.randomR (0, length items-1)--nonEmptySetToList :: NonEmptySet.T a -> [a]-nonEmptySetToList = NonEmpty.flatten . NonEmptySet.toAscList
test/Test/Mastermind.hs view
@@ -3,21 +3,25 @@ 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 Game.Utility (Choice, mergeChoice, noChoice)  import Control.Monad (liftM2, ) import Control.Applicative ((<$>), ) -import qualified Data.NonEmpty.Set as NonEmptySet+import qualified Data.NonEmpty.Class as NonEmptyC+import qualified Data.NonEmpty as NonEmpty import qualified Data.Traversable as Trav-import qualified Data.Set as Set+import qualified Data.EnumSet as EnumSet+import Data.EnumSet (EnumSet) import Data.NonEmpty ((!:))  import qualified Test.QuickCheck as QC import Test.QuickCheck (Property, Arbitrary(arbitrary), quickCheck, (==>), )  -alphabet :: Set.Set Int+alphabet :: EnumSet Int alphabet = NonEmptySet.flatten neAlphabet  neAlphabet :: NonEmptySet.T Int@@ -111,7 +115,7 @@ -} partitionSizes :: Code -> Bool partitionSizes (Code attempt) =-   fromIntegral (Set.size alphabet) ^ length attempt+   fromIntegral (EnumSet.size alphabet) ^ length attempt    ==    sum (map snd (MM.partitionSizes alphabet attempt)) @@ -126,8 +130,8 @@        take 100 (CodeSet.flatten set)  -genFixedLengthCodes :: Int -> QC.Gen [[Int]]-genFixedLengthCodes width = QC.listOf1 $ QC.vectorOf width genElement+genFixedLengthCodes :: (NonEmptyC.Gen f) => Int -> QC.Gen (f [Int])+genFixedLengthCodes width = NonEmptyC.genOf $ QC.vectorOf width genElement  bestSeparatingCode :: Property bestSeparatingCode =@@ -140,7 +144,7 @@             (MM.matching alphabet base0 eval0)             (MM.matching alphabet base1 eval1)    not (CodeSet.null set) ==>-      QC.forAll (fmap (take 10) $ genFixedLengthCodes width) $+      QC.forAll (fmap (NonEmpty.mapTail $ take 9) $ genFixedLengthCodes width) $          MM.propBestSeparatingCode width (set :: CodeSetInt)  intersections :: Property@@ -176,6 +180,41 @@ check member against intersection with singleton -} ++choiceLeftIdentity :: Choice Char -> Bool+choiceLeftIdentity a =+   a == mergeChoice noChoice a++choiceRightIdentity :: Choice Char -> Bool+choiceRightIdentity a =+   a == mergeChoice a noChoice++choiceCommutative :: Choice Char -> Choice Char -> Bool+choiceCommutative a b =+   mergeChoice a b == mergeChoice b a++{-+Unfortunately, this does not apply:++*Test.Mastermind EnumMap> let a = Choice (EnumMap.singleton 'x' 1) 1+*Test.Mastermind EnumMap> let b = Choice (EnumMap.singleton 'x' 1) 0+*Test.Mastermind EnumMap> let c = Choice (EnumMap.singleton 'y' 1) 1+*Test.Mastermind EnumMap> mergeChoice (mergeChoice a b) c+Choice (fromList [('x',1),('y',1)]) 2+*Test.Mastermind EnumMap> mergeChoice a (mergeChoice b c)+Choice (fromList [('x',1),('y',1)]) 1+*Test.Mastermind EnumMap> mergeChoice a b+Choice (fromList [('x',1)]) 1+*Test.Mastermind EnumMap> mergeChoice b c+Choice (fromList [('x',1),('y',1)]) 1+-}+_choiceAssociative :: Choice Char -> Choice Char -> Choice Char -> Bool+_choiceAssociative a b c =+   mergeChoice (mergeChoice a b) c+   ==+   mergeChoice a (mergeChoice b c)++ tests :: [(String, IO ())] tests =    ("matchingMember", quickCheck matchingMember) :@@ -188,4 +227,8 @@    ("bestSeparatingCode", quickCheck bestSeparatingCode) :    ("intersections", quickCheck intersections) :    ("solve", quickCheck solve) :+   ("choiceLeftIdentity", quickCheck choiceLeftIdentity) :+   ("choiceRightIdentity", quickCheck choiceRightIdentity) :+   ("choiceCommutative", quickCheck choiceCommutative) :+   -- ("choiceAssociative", quickCheck choiceAssociative) :    []