packages feed

wholepixels-1.1: src/WholePixels/Random.hs

module WholePixels.Random where

import Control.Monad.Random
import Relude
import qualified System.Random.Shuffle
import WholePixels.Color
import WholePixels.Geometry

disturbedSequence :: MonadRandom m => [Double] -> Double -> m [Double]
disturbedSequence xs amp = do
  dxs <- replicateM (length xs) $ getRandomR (-amp, amp)
  pure $ zipWith (+) xs dxs

filterRandomly :: MonadRandom m => Double -> [a] -> m [a]
filterRandomly probability xs = do
  ps <- replicateM (length xs) $ getRandomR (0, 1)
  pure [x | (x, p) <- zip xs ps, p < probability]

coinToss :: MonadRandom m => m Bool
coinToss = uniform [False, True]

genGrid :: MonadRandom m => Int -> Int -> m a -> m [(Int, Int, a)]
genGrid colCount rowCount genElement =
  genGridWithBoundaries colCount rowCount (const genElement)

genGridWithBoundaries :: MonadRandom m => Int -> Int -> ([Direction] -> m a) -> m [(Int, Int, a)]
genGridWithBoundaries colCount rowCount genElement = do
  let numberGrid = [(x, y) | y <- [0.. rowCount - 1], x <- [0.. colCount - 1]]
  forM numberGrid $ \(x, y) -> do
    let dirs = [R | x == 0] <> [D | y == 0] <> [L | x == colCount - 1] <> [U | y == rowCount - 1]
    (x,y,) <$> genElement dirs

genGrid' :: MonadRandom m => GridSpec -> ([Direction] -> m a) -> m [(Int, Int, a)]
genGrid' (GridSpec gridSpec) genElement = do
  let rowCount = length gridSpec
      colCount = case gridSpec of
        [] -> 0
        (row : _) -> length row
      numberedGrid :: [(Int, Int, CellSpec)]
      numberedGrid =
        concat $
          zipWith
            (\j row -> map (\(i, c) -> (i, j, c)) row)
            [0.. rowCount]
            ( map
              (zip [0.. colCount])
              gridSpec
            )
  fmap catMaybes . forM numberedGrid $ \(x, y, c) -> do
    let dirs = [R | x == 0] <> [D | y == 0] <> [L | x == colCount - 1] <> [U | y == rowCount - 1]
    case c of
      N -> pure Nothing
      Y -> (Just . (x,y,)) <$> genElement dirs

probably :: MonadRandom m => Double -> a -> m (Maybe a)
probably probability thing = do
  x <- getRandomR (0, 1)
  pure $
    if x < probability
    then Just thing
    else Nothing

shuffleM :: MonadRandom m => [a] -> m [a]
shuffleM = System.Random.Shuffle.shuffleM

data PaletteStrategy
  = Analogous
  | Complementary
  | SplitComplementary
  | Triangle

genPalette :: MonadRandom m => m Palette
genPalette = do
  strategy <- uniform [Analogous, Complementary, SplitComplementary, Triangle]
  genPaletteWithStrategy strategy

genPaletteWithStrategy :: MonadRandom m => PaletteStrategy -> m Palette
genPaletteWithStrategy strategy = do
  baseHue <- getRandomR (0, 360)
  baseSaturation <- getRandomR (0, 1)
  baseValue <- getRandomR (0, 0.5)
  bgToFgHueDifference <- uniform [180, 0, 30, 120, 240]
  let baseColor = HSV baseHue baseSaturation baseValue
      c = HSV (fixHue $ baseHue + bgToFgHueDifference) 1 1
      colors = case strategy of
        Analogous -> take 4 $ analogous c
        Complementary -> take 3 (analogous c) <> [complementary c]
        SplitComplementary ->
          let (c1, c2) = splitComplementary c
          in [c, c1, c2]
        Triangle ->
          let (c1, c2) = splitTriangle c
          in [c, c1, c2]
  pure $ Palette {..}

genMonochromePaletteForColor :: MonadRandom m => HSV -> m Palette
genMonochromePaletteForColor baseColor@(HSV bh bs bv) = do
  let colors = [HSV bh bs v | v <- [bv + 0.15, bv + 0.2.. 1]]
  pure $ Palette {..}

genMonochromePalette :: MonadRandom m => Double -> m Palette
genMonochromePalette maxSaturation = do
  hue <- getRandomR (0, 360)
  saturation <- getRandomR (0.0, maxSaturation)
  baseValue <- getRandomR (0, 0.5)
  let baseColor = HSV hue saturation baseValue
      colors = [HSV hue saturation v | v <- [baseValue + 0.15, baseValue + 0.2.. 1]]
  pure $ Palette {..}

genColor' :: MonadRandom m => Palette -> m HSV
genColor' pal = genColor pal 0.5 1.0

genColor :: MonadRandom m => Palette -> Double -> Double -> m HSV
genColor Palette {..} expected sigma2 = do
  let colorCount = length (baseColor : take 10 colors)
      colorPositions = take colorCount [0.0, (1.0 / fromIntegral colorCount)..]
      weights =
        map
          (\p -> toRational $ 1000.0 * exp (-(p - expected) * (p - expected) / sigma2))
          colorPositions
  weighted $ zip (baseColor : colors) weights

genRectSubdivision :: forall m. MonadRandom m => Int -> Rect -> m [Rect]
genRectSubdivision depth r = foldr (>=>) pure (replicate depth step) [r]
  where
    step :: [Rect] -> m [Rect]
    step rs = concat <$> mapM go rs
    go :: Rect -> m [Rect]
    go r' = do
      let hbonusWeight = if rh r' > rw r' then 15 else 0
          vbonusWeight = if rw r' > rh r' then 15 else 0
      f <-
        weighted
          [ (pure . pure, 3)
          , (pure . hsplit, 1 + hbonusWeight)
          , (pure . hsplitGolden, 3 + hbonusWeight)
          , (pure . hsplitReverseGolden, 3 + hbonusWeight)
          , (pure . vsplit, 1 + vbonusWeight)
          , (pure . vsplitGolden, 3 + vbonusWeight)
          , (pure . vsplitReverseGolden, 3 + vbonusWeight)
          ]
      f r'