packages feed

game-probability (empty) → 1.0

raw patch · 6 files changed

+374/−0 lines, 6 filesdep +basedep +probabilitydep +randomsetup-changed

Dependencies added: base, probability, random

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2010, Neil Brown++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Neil Brown nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Numeric/Probability/Game/Dice.hs view
@@ -0,0 +1,101 @@++-- | A module containing various definitions of dice as random events, and a few+-- associated helper functions.  See 'DieRoll', which is really a synonym for 'EventM'+-- Int.+module Numeric.Probability.Game.Dice (+  -- * Main die roll type+  DieRoll, roll,+  -- * Dice definitions+  d4, d6, d8, z10, d10, d12, d20, d100, z100, d, z,+  -- * Dice helper functions+  rerollOn) where++import Numeric.Probability.Game.Event (EventM, enact, makeEvent, makeEventProb, outcomes)++-- | A type synonym for events with an integer outcome (i.e. all standard die rolls).+--+-- The 'Num' instance for @EventM Int@ allows you to add the results of two die+-- rolls, or subtract them (if it helps, @(+) = liftA2 (+)@).+--+-- Multiplication works as follows.  @d * e@ evaluates the first die roll,+-- then sums that many rolls of the second.  So @2 * d6@ rolls two d6 and adds+-- the outcomes.  However, this definition means that @d6 * 2@ rolls one d6,+-- then effectively scales the result by 2.  And @d6 * d4@ rolls one d6, then+-- rolls that number of @d4@, adding their results together.  The simple rule+-- when one of the terms is a constant is: use the constant on the left-hand+-- side to get more dice, and use the constant on the right-hand side to scale+-- the result.+type DieRoll = EventM Int+++-- | A die with an equal chance of rolling 1, 2, 3 or 4.+d4 :: DieRoll+d4 = d 4++-- | A die with an equal chance of rolling 1, 2, 3, 4, 5 or 6.+d6 :: DieRoll+d6 = d 6++-- | A die with an equal chance of rolling 1, 2, 3, 4, 5, 6, 7 or 8.+d8 :: DieRoll+d8 = d 8++-- | A die with an equal chance of rolling 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9.+z10 :: DieRoll+z10 = z 10++-- | A die with an equal chance of rolling 1, 2, 3, 4, 5, 6, 7, 8, 9 or 10.+d10 :: DieRoll+d10 = d 10++-- | A die with an equal chance of rolling 1 to 12 inclusive.+d12 :: DieRoll+d12 = d 12++-- | A die with an equal chance of rolling 1 to 20 inclusive.+d20 :: DieRoll+d20 = d 20++-- | A die with an equal chance of rolling 1 to 100 inclusive.+d100 :: DieRoll+d100 = d 100++-- | A die with an equal chance of rolling 0 to 99 inclusive.+z100 :: DieRoll+z100 = z 100++-- | Makes a die that has an equal chance of achieving the numbers 1 through the+-- number given.  @d 4@ has an equal chance of producing the outcomes 1, 2, 3 and+-- 4, @d 1@ is equivalent to @return 1@ (a certain result of 1), and+-- @d@ is undefined for any number below 1.  For convenience, all the standard dice are+-- provided, e.g. @d6 = d 6@.+d :: Int -> DieRoll+d n = makeEvent [1..n]++-- | Makes a die that has an equal chance of achieving the numbers 0 through+-- the one less than the number given.  @z 4@ has an equal chance of producing+-- the outcomes 0, 1, 2 and 3, while @z 1@ is equivalent to @return 0@ (a+-- certain result of 0), and @z@ is undefined for any number below 1.  For+-- convenience, several standard dice that can be interpreted with a lower+-- result of 0 are provided, e.g. @z10 = z 10@.+z :: Int -> DieRoll+z n = makeEvent [0 .. (n - 1)]++-- | Rerolls the die when the specified outcome(s) occur.  This has the effect+-- of removing the outcomes from the set of outcomes and rescaling all the other+-- probabilities linearly to sum to 1.  For example:+--+-- > d6 `rerollOn` [5,6] == d4+-- > chancePred (== 12) ((2*d6) `rerollOn` [7]) == 1/30+--+-- With the latter example, the standard chance of 12 on 2d6 is 1\/36, which+-- is rescaled by 36\/30, the reciprocal of the chance of /not/ hitting+-- a 7.+rerollOn :: DieRoll -> [Int] -> DieRoll+rerollOn dc ns = makeEventProb $ filter ((`notElem` ns) . fst) $ outcomes dc++-- | A nice synonym for 'enact': actually rolls the die and produces a single result according to the probabilities+-- in the @EventM a@ parameter.+roll :: DieRoll -> IO Int+roll = enact+
+ Numeric/Probability/Game/Event.hs view
@@ -0,0 +1,136 @@+-- | A module containing the central type of the library, 'EventM', and various+-- related helper functions.+module Numeric.Probability.Game.Event (EventM, makeEvent, makeEventProb, outcomes, enact,+  coinToss, subst) where++import Control.Applicative (Applicative(..), (<$>), liftA2)+import Control.Arrow (second)+import Control.Monad (ap, replicateM)+import Data.Ratio (denominator)+import Numeric.Probability.Distribution (T(..), certainly, decons, fromFreqs, norm, selectP, uniform)+import System.Random (randomRIO)++-- | A probabilistic event with an outcome of type 'a'.  See the 'enact' function+-- to actually run the event and randomly pick an outcome. +--+-- For an explanation of the 'Num' instance, see the DieRoll type in the "Numeric.Probability.Game.Dice"+-- module.+--+-- The 'Eq' instance compares the two distributions to see if they are equal.+-- This looks at all the outcomes and sees if their probabilities are equal on+-- the left-hand side and the right-hand side.  For example,+-- @coinToss == fmap (>= 4) d6@, but @d12 /= d6 + d6@.+--+-- The 'Show' instance will display a horizontal bar-chart of relative outcome+-- probability.  Note: this really is a relative probability -- common factors+-- are cancelled, and is not a count of the different outcomes.  If you wish to+-- show the raw numbers, use @show . outcomes@ instead.+-- +-- The 'Functor' instance allows you to modify the outcome values without changing+-- their associated probabilities.  For example, @fmap show d6@ changes the outcomes+-- into their String representations.+--+-- The 'Applicative' instance allows you to join together the results of two events+-- in a predetermined manner.  For example, @makeEvent [id, (* 2)] \<*\> d6@ allows+-- you to roll a d6 that has a 50% chance of being doubled.  Note that @pure+-- 6@ is an event that is certain to produce the outcome 6.+--+-- The 'Monad' instance allows you to base the choice of the next event on the+-- result of the previous event.  For example, @coinToss >>= \x -> if x then d6+-- else d4@ will roll a d4 50% of the time and a d6 the other 50%.  Note that @return+-- 6@ is an event that is certain to produce the outcome 6.+newtype EventM a = EventM (T Rational a)+  deriving (Monad, Functor)++instance Ord a => Eq (EventM a) where+-- Eq not defined properly for T prob a, work-around for now:+--(===) :: Ord a => EventM a -> EventM a -> Bool+  (==) (EventM a) (EventM b) = decons (norm a) == decons (norm b)++normEventM :: Ord a => EventM a -> EventM a+normEventM (EventM dc) = EventM (norm dc)++instance Num (EventM Int) where+  (+) x y = normEventM $ liftA2 (+) x y+  (-) x y = normEventM $ liftA2 (-) x y+  negate = fmap negate+  abs = normEventM . fmap abs+  signum = normEventM . fmap signum+  (*) = lotsOf+    where+      lotsOf :: EventM Int -> EventM Int -> EventM Int+      lotsOf x y = do n <- x+                      sum <$> replicateM n y+  fromInteger = EventM . certainly . fromInteger++instance (Show a, Ord a) => Show (EventM a) where+  show (EventM dc) = showBars (decons (norm dc))++showBars :: Show a => [(a, Rational)] -> String+showBars xs = unlines (map (makeBar . scale) xs)+  where+    den = fromIntegral $ foldr lcm 1 (map (denominator . snd) xs)+    scale (x, r) = (x, floor (r * den)) -- should be integral anyway++    width = maximum (map (length . show . fst) xs)++    makeBar (x, n) = pad (show x) ++ ": " ++ replicate n '#'+      where+        pad s = s ++ replicate (width - length s) ' '++instance Applicative EventM where+  pure = return+  (<*>) = ap++-- | Gets a list of all the outcomes of the event and their associated probability.+--  You can be sure that the probabilities will all sum to 1, and that there will+-- only be one item in the list per outcome.  It is possible that some of the outcomes+-- in the list will have zero probability. +outcomes :: Ord a => EventM a -> [(a, Rational)]+outcomes (EventM dc) = decons (norm dc)++-- | Makes an event that has an equal chance of taking on the value of each+-- entry in the list.  Note that duplicates in the list are permitted and do+-- have an effect: @makeEvent [True, False]@ has a 50% chance of giving a True+-- result, but @makeEvent [True, True, False, False, False]@ only has a 40%+-- chance of giving a True result.  If you do not want this behaviour, use+-- @makeEvent . nub@ to remove duplicates. +--+-- The result of passing the empty list is undefined.+makeEvent :: [a] -> EventM a+makeEvent = EventM . uniform++-- | Given a list of events and their associated probabilities, forms a+-- corresponding event.  The probabilities must be non-negative.  If the+-- probabilities do not sum to one, they are all scaled linearly so that their+-- sum is one.  Duplicate items will have their probabilities added.+--+-- The result of passing the empty list, a list containing negative probabilities,+-- or a list where all the probabilities are zero is undefined.+makeEventProb :: (Ord a, Real prob) => [(a, prob)] -> EventM a+makeEventProb = EventM . norm . fromFreqs . map (second toRational)++-- | An event with a 50% chance of giving True, and a 50% chance of giving False.+coinToss :: EventM Bool+coinToss = makeEvent [True, False]++-- | Actually enacts the event and produces a single result according to the probabilities+-- in the @EventM a@ parameter.+enact :: EventM a -> IO a+enact (EventM dc) = selectP (toDouble dc) <$> randomRIO (0, 1)+  where+    toDouble :: T Rational a -> T Double a+    toDouble = Cons . map (second fromRational) . decons++-- | If the @EventM a@ parameter returns a result equal to the first parameter,+-- it is changed to be the second parameter; otherwise it is left untouched.  For+-- example @replace 4 8 d4@ has an equal chance of producing the outcomes 1, 2,+-- 3 and 8, @replace 10 0 d10 == z10@, and @replace 10 20 d6 == d6@.+subst :: Eq a => a -> a -> EventM a -> EventM a+subst x y = fmap (\n -> if x == n then y else n)+++      -- a `lotsOf` b = [(bx,ap*bp) | (ax,ap)<-a,(bx,bp)<- sum (replicate ax b)]+      -- b `lotsOf` c = [(cx,bp*cp) | (bx,bp)<-b,(cx,cp)<- sum (replicate bx c)]+      -- a `lotsOf` (b `lotsOf` c)+      --   = [(bcx,ap*bcp) | (ax,ap)<-a,(bcx,bcp)<- sum (replicate ax [(cx,bp*cp) | (bx,bp)<-b,(cx,cp)<- sum (replicate bx c)])]
+ Numeric/Probability/Game/Query.hs view
@@ -0,0 +1,33 @@+-- | A module with functions for querying the probabilities of various outcomes.+module Numeric.Probability.Game.Query (chancePred, chanceRel, chanceTrue) where++import Control.Applicative ((<$>), liftA2)+import Data.Maybe (fromMaybe)++import Numeric.Probability.Game.Event (EventM, outcomes)++-- | Gets the probability that the outcome will satisfy the given predicate.  For+-- example:+--+-- > chancePred (<= 2) d6 == 1/3   -- The chance of getting 2 or less on a d6+-- > chancePred even d6 == 1/2     -- The chance of rolling an event number on a d6+chancePred :: (a -> Bool) -> EventM a -> Rational+chancePred f e = fromMaybe 0 $ lookup True (outcomes (f <$> e))++-- | Gets the probability that the given relation will hold between the two events.+--  For example:+--+-- > chanceRel (==) d6 d6 == 1/6   -- The chance of rolling doubles on d6+-- > chanceRel (>) (2*d6) d12      -- The chance of beating a d12 with two d6+chanceRel :: (a -> a -> Bool) -> EventM a -> EventM a -> Rational+chanceRel f a b = chanceTrue (liftA2 f a b)++-- | Gets the probability that the given boolean-outcome event will give a True+-- outcome.  For example:+--+-- > chanceTrue coinToss == 1/2+-- > chanceTrue ((== 3) <$> d6) == 1/6+--+-- (For the latter example, 'chancePred' is more concise.)+chanceTrue :: EventM Bool -> Rational+chanceTrue = chancePred id
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ game-probability.cabal view
@@ -0,0 +1,72 @@+Name:                game-probability+Version:             1.0+Synopsis:            Simple probability library for dice rolls and similar simple games of chance+-- A longer description of the package.+Description:         A library designed to aid in easily calculating the+                     probability of various outcomes of dice rolls.  The+		     central types are held in the+		     "Numeric.Probability.Game.Event" module, the dice are+		     defined in the "Numeric.Probability.Game.Dice" module,+		     and the functions for calculating probabilities are in+		     the "Numeric.Probability.Game.Query" module.  Various+		     examples are scattered throughout the library, but here+		     are some more:+                     .+                     Evalulates the chance of a coin toss turning up heads:+                     .+                     > chanceTrue coinToss+                     .+                     Shows the chances of each outcome of rolling two six-sided dice, as a textual bar chart:+                     .+                     > show (2*d6)+                     .+                     The chance of getting an 18 when rolling 3 six-sided dice and rerolling on any total less than 8:+                     .+                     > chancePred (== 18) ((3*d6) `rerollOn` [3..7])+                     .+                     As a more complex example, this implements my memory/understanding of the original+		     World of Darkness dice system+		     (<http://en.wikipedia.org/wiki/Storytelling_System>).+		     You roll a given number of 10-sided dice, rolling one+		     extra for every 10 you score if you are specialised.  The number of 1s on the+		     original roll are subtracted from the total number of+		     dice that equal or exceed the difficulty target:+                     .+                     > successes :: Int -> Int -> Bool -> EventM Int+                     > successes target dice specialised+                     >   = do initial <- replicateM dice d10+                     >        extra <- if specialised+                     >                   then replicateM (count (== 10) initial) d10+                     >                   else return []+                     >        return (count (>= target) (initial ++ extra) - count (== 1) initial)+                     >   where+                     >     count f xs = length (filter f xs)+                     .+                     If only all RPGs specified their rules in Haskell!+                     .+                     See also the blog post on the design of the library: <http://chplib.wordpress.com/2010/08/13/nice-dice-in-haskell/>+License:             BSD3+License-file:        LICENSE+Author:              Neil Brown+Maintainer:          neil@twistedsquare.com+-- Copyright:           ++Category:            Math+Build-type:          Simple++Cabal-version:       >=1.6+++Library+  -- Modules exported by the library.+  Exposed-modules:     Numeric.Probability.Game.Event+                       Numeric.Probability.Game.Dice+                       Numeric.Probability.Game.Query+  +  -- Packages needed in order to build this package.+  Build-depends:       base >= 4 && < 5, probability == 0.2.*, random == 1.0.*++  Extensions:          GeneralizedNewtypeDeriving FlexibleInstances++  GHC-Options:         -Wall+