packages feed

tcod-haskell-0.2.0.0: src/Game/TCOD/Mersenne.hs

{-# LANGUAGE QuasiQuotes #-}
-- | This toolkit is an implementation of two fast and high quality pseudorandom number generators:
-- * a Mersenne twister generator,
-- * a Complementary-Multiply-With-Carry generator.
-- CMWC is faster than MT (see table below) and has a much better period
-- (1039460 vs. 106001). It is the default algo since libtcod 1.5.0.
module Game.TCOD.Mersenne(
    randomGetInstance
  , randomNew
  , randomSave
  , randomRestore
  , randomNewFromSeed
  , randomDelete
  , randomSetDistribution
  , randomGetInt
  , randomGetDouble
  , randomGetIntMean
  , randomGetDoubleMean
  , randomDiceNew
  , randomDiceRoll
  , randomDiceRollS
  ) where

import Foreign
import Foreign.C
import Game.TCOD.Context as C
import Game.TCOD.MersenneTypes
import Game.TCOD.Noise

context tcodContext
verbatim "#define TCOD_SDL2"
include "libtcod/mersenne.h"

-- | Default generator
--
-- The simplest way to get random number is to use the default generator.
-- The first time you get this generator, it is initialized by calling
-- TCOD_random_new. Then, on successive calls, this function returns the same
-- generator (singleton pattern).
randomGetInstance :: IO TCODRandom
randomGetInstance = TCODRandom <$> [C.exp| void* {TCOD_random_get_instance()} |]

-- | Generators with random seeds
--
-- You can also create as many generators as you want with a random seed
-- (the number of seconds since Jan 1 1970 at the time the constructor is called).
-- Warning ! If you call this function several times in the same second, it will
-- return the same generator.
randomNew :: RandomAlgorithm -> IO TCODRandom
randomNew algo = do
  let algo' = fromIntegral . fromEnum $ algo
  TCODRandom <$> [C.exp| void* { TCOD_random_new((TCOD_random_algo_t)$(int algo')) } |]

-- | Saving a RNG state into generator-backup
randomSave :: TCODRandom -> IO TCODRandom
randomSave (TCODRandom r) = TCODRandom <$> [C.exp| void* { TCOD_random_save($(void* r)) } |]

-- | Restoring a saved state
--
-- And restore it later. This makes it possible to get the same series of number
-- several times with a single generator.
randomRestore :: TCODRandom -- ^ generator that will be restored from backup
  -> TCODRandom -- ^ backup-generator, returned by 'randomSave'
  -> IO ()
randomRestore (TCODRandom r) (TCODRandom bp) = [C.exp| void { TCOD_random_restore($(void* r), $(void* bp)) } |]

-- | Generators with user defined seeds
--
-- Finally, you can create generators with a specific seed. Those allow you to
-- get a reproducible set of random numbers. You can for example save a dungeon
-- in a file by saving only the seed used for its generation (provided you have
-- a determinist generation algorithm)
randomNewFromSeed :: RandomAlgorithm -> Word -> IO TCODRandom
randomNewFromSeed algo s = do
  let algo' = fromIntegral . fromEnum $ algo
      s' = fromIntegral s
  TCODRandom <$> [C.exp| void* { TCOD_random_new_from_seed((TCOD_random_algo_t)$(int algo'), $(uint32_t s')) } |]

-- | Destroying a RNG
--
-- To release resources used by a generator, use the function.
-- NB : do not delete the default random generator !
randomDelete :: TCODRandom -> IO ()
randomDelete (TCODRandom r) = [C.exp|void {TCOD_random_delete($(void* r))}|]

-- | Setting the default RNG distribution
--
-- Note: See docs for 'Distribution'
randomSetDistribution :: TCODRandom -> Distribution -> IO ()
randomSetDistribution (TCODRandom r) d = do
  let d' = fromIntegral . fromEnum $ d
  [C.exp| void { TCOD_random_set_distribution($(void* r), (TCOD_distribution_t)$(int d')) } |]

-- | Getting an integer
--
-- Once you obtained a generator (using one of those methods), you can get random
-- numbers using the following functions, using either the explicit or simplified
-- API where applicable:
randomGetInt :: TCODRandom
  -> Int -- ^ min. Range of values returned. Each time you call this function, you get a number between (including) min and max
  -> Int -- ^ max
  -> IO Int
randomGetInt (TCODRandom r) minv maxv = do
  let minv' = fromIntegral minv
      maxv' = fromIntegral maxv
  fromIntegral <$> [C.exp| int { TCOD_random_get_int($(void* r), $(int minv'), $(int maxv')) } |]

-- | Getting a floating point number
--
-- Once you obtained a generator (using one of those methods), you can get random
-- numbers using the following functions, using either the explicit or simplified
-- API where applicable:
randomGetDouble :: TCODRandom
  -> Double -- ^ min. Range of values returned. Each time you call this function, you get a number between (including) min and max
  -> Double -- ^ max
  -> IO Double
randomGetDouble (TCODRandom r) minv maxv = do
  let minv' = realToFrac minv
      maxv' = realToFrac maxv
  fromIntegral <$> [C.exp| int { TCOD_random_get_double($(void* r), $(float minv'), $(float maxv')) } |]

-- | Getting an integer
--
-- Once you obtained a generator (using one of those methods), you can get random
-- numbers using the following functions, using either the explicit or simplified
-- API where applicable:
randomGetIntMean :: TCODRandom
  -> Int -- ^ min. Range of values returned. Each time you call this function, you get a number between (including) min and max
  -> Int -- ^ max
  -> Int -- ^ mean. This is used to set a custom mean, ie, not min+((max-min)/2). It can even be outside of the min-max range. Using a mean will force the use of a weighted (Gaussian) distribution, even if linear is set.
  -> IO Int
randomGetIntMean (TCODRandom r) minv maxv m = do
  let minv' = fromIntegral minv
      maxv' = fromIntegral maxv
      m' = fromIntegral m
  fromIntegral <$> [C.exp| int { TCOD_random_get_int_mean($(void* r), $(int minv'), $(int maxv'), $(int m')) } |]

-- | Getting a floating point number
--
-- Once you obtained a generator (using one of those methods), you can get random
-- numbers using the following functions, using either the explicit or simplified
-- API where applicable:
randomGetDoubleMean :: TCODRandom
  -> Double -- ^ min. Range of values returned. Each time you call this function, you get a number between (including) min and max
  -> Double -- ^ max
  -> Double -- ^ mean. This is used to set a custom mean, ie, not min+((max-min)/2). It can even be outside of the min-max range. Using a mean will force the use of a weighted (Gaussian) distribution, even if linear is set.
  -> IO Double
randomGetDoubleMean (TCODRandom r) minv maxv m = do
  let minv' = realToFrac minv
      maxv' = realToFrac maxv
      m' = realToFrac m
  realToFrac <$> [C.exp| float { TCOD_random_get_double_mean($(void* r), $(float minv'), $(float maxv'), $(float m')) } |]

-- | Create dnd-like dice from description string like "3d6+2"
randomDiceNew :: String -> IO Dice
randomDiceNew str = withCString str $ \str' -> alloca $ \res -> do
  [C.exp| void { *$(TCOD_dice_t* res) = TCOD_random_dice_new($(const char* str')) } |]
  peek res

-- | Roll dice and return resulted dice side
randomDiceRoll :: TCODRandom -> Dice -> IO Int
randomDiceRoll (TCODRandom r) d = with d $ \d' ->
  fromIntegral <$> [C.exp| int {TCOD_random_dice_roll($(void* r), *$(TCOD_dice_t* d'))}|]

-- | Roll dice without initiating, see 'randomDiceNew'
randomDiceRollS :: TCODRandom -> String -> IO Int
randomDiceRollS (TCODRandom r) str = withCString str $ \str' ->
  fromIntegral <$> [C.exp| int {TCOD_random_dice_roll_s($(void* r), $(const char* str'))}|]