packages feed

Randometer-0.1.0.0: randometer.hs

{-# LANGUAGE OverloadedStrings #-}
module Main (main, moreOrLess, decide, chooseOne) where
import Control.Applicative
import Control.Monad (replicateM)
import Data.Char (toLower)
import Data.Random
import Text.Printf (printf)
import System.IO (hFlush, stdout)

size :: (Int, Int)
size = (24, 6)

data Trial = On | Off deriving Eq
instance Show Trial where
    show On = "●"
    show Off = "○"

invert :: Trial -> Trial
invert On = Off
invert Off = On

trial :: IO Trial
trial = runRVar (randomElement [On, Off]) StdRandom

mutedTrial :: Double -> Trial -> IO Trial
mutedTrial propensity memory = do
    rand <- runRVar (uniform 0 1) StdRandom
    pure $ if rand < propensity then invert memory else memory

biasedTrial :: Double -> IO Trial
biasedTrial propensity = do
    rand <- runRVar (uniform 0 1) StdRandom
    pure $ if rand < propensity then On else Off

generator :: Int -> Double -> IO [Trial]
generator i = (trial >>=) . generator' i where
    generator' 0 _ _ = pure []
    generator' cap propensity previous = (previous:) <$> genrest where
        gennext = mutedTrial propensity previous
        genrest = gennext >>= generator' (cap - 1) propensity

biasedGenerator :: Int -> Double -> IO [Trial]
biasedGenerator n p = replicateM n $ biasedTrial p

boxed :: [String] -> [String]
boxed xs = header : map addSides xs ++ [footer] where
    len = maximum $ map length xs
    header = '┌' : replicate len '─' ++ "┐"
    addSides line = '│' : take len (line ++ repeat ' ') ++ "│"
    footer = '└' : replicate len '─' ++ "┘"

puzzle :: Int -> [Trial] -> [String]
puzzle _ [] = []
puzzle n ys = boxed $ map (concatMap show) (groupsOf n ys)

groupsOf :: Int -> [a] -> [[a]]
groupsOf _ [] = []
groupsOf n xs = take n xs : groupsOf n (drop n xs)

askN :: String -> [Int] -> IO Int
askN prompt ints = do
    printf "%s\n» " prompt
    hFlush stdout
    guess <- getLine
    if guess `elem` map show ints
    then pure $ read guess
    else askN prompt ints

askBool :: String -> IO Bool
askBool prompt = do
    printf "%s\n» " prompt
    hFlush stdout
    guess <- getLine
    case map toLower guess of
        "y" -> return True
        "yes" -> return True
        "n" -> return False
        "no" -> return False
        _ -> askBool prompt

numberedBox :: Int -> [String] -> [String]
numberedBox n xs = pad x0 : (leader ++ x1) : map pad xN where
    leader = printf "%d. " n
    pad = (map (const ' ') leader ++)
    x0 = head xs
    x1 = head $ tail xs
    xN = tail $ tail xs

shuffleBoards :: [[Trial]] -> IO [Int]
shuffleBoards boards = runRVar (shuffle [0 .. pred $ length boards]) StdRandom

displayBoards :: [[Trial]] -> [Int] -> IO ()
displayBoards boards key = do
    let display n = putStr . unlines . numberedBox n . puzzle (fst size)
    mapM_ (uncurry display) (zip [1 ..] $ map (boards !!) key)

chooseOne :: Double -> [Double] -> IO ()
chooseOne x xs = do
    boards <- mapM (generator $ uncurry (*) size) (x:xs)
    key <- shuffleBoards boards
    displayBoards boards key

    guess <- pred <$> askN "Which is truly random?" [1 .. length boards]
    putStrLn ""
    putStrLn (if key !! guess == 0 then "Correct!" else "Wrong.")

    let reveal n = printf "%d: %0.2f switch\n" n ((x:xs) !! (key !! pred n))
    mapM_ reveal [1 .. length boards]

chooseUnbiased :: Double -> [Double] -> IO ()
chooseUnbiased x xs = do
    boards <- mapM (biasedGenerator $ uncurry (*) size) (x:xs)
    key <- shuffleBoards boards
    displayBoards boards key

    guess <- pred <$> askN "Which is unbiased?" [1 .. length boards]
    putStrLn ""
    putStrLn (if key !! guess == 0 then "Correct!" else "Wrong.")

    let reveal n = printf "%d: %0.2f ●\n" n ((x:xs) !! (key !! pred n))
    mapM_ reveal [1 .. length boards]

decide :: Double -> [Double] -> IO ()
decide x xs = do
    boards <- mapM (generator $ uncurry (*) size) (x:xs)
    choice <- runRVar (randomElement [0 .. pred $ length boards]) StdRandom
    putStr $ unlines $ puzzle (fst size) $ boards !! choice
    guess <- askBool "Was this generated by a fair random generator?"
    putStrLn (if guess == (choice == 0) then "Correct!" else "Wrong.")
    printf "The dots flip with %0.2f chance.\n" $ (x:xs) !! choice

decideBiased :: Double -> [Double] -> IO ()
decideBiased x xs = do
    boards <- mapM (biasedGenerator $ uncurry (*) size) (x:xs)
    choice <- runRVar (randomElement [0 .. pred $ length boards]) StdRandom
    putStr $ unlines $ puzzle (fst size) $ boards !! choice
    guess <- askBool "Was this generated by an unbiased random generator?"
    putStrLn (if guess == (choice == 0) then "Correct!" else "Wrong.")
    printf "The dots flip with %0.2f chance.\n" $ (x:xs) !! choice

moreOrLess :: IO ()
moreOrLess = do
    var <- runRVar stdUniform StdRandom
    board <- generator (uncurry (*) size) var
    putStr $ unlines $ puzzle (fst size) board
    guess <- askBool "Are these dots more likely to flip than chance?"
    putStrLn (if guess == (var > 0.5) then "Correct!" else "Wrong.")
    putStrLn ""
    printf "The dots flipped %0.6f times out of one.\n" var

identifyBias :: IO ()
identifyBias = do
    var <- runRVar stdUniform StdRandom
    board <- biasedGenerator (uncurry (*) size) var
    putStr $ unlines $ puzzle (fst size) board
    guess <- askBool "Are these dots biased towards ●?"
    putStrLn (if guess == (var > 0.5) then "Correct!" else "Wrong.")
    putStrLn ""
    printf "The were biased %0.6f ●.\n" var

main :: IO ()
main = do
    game <- askN "Would you like to play a game? (Select [0-5])" [0..5]
    case game of
        0 -> chooseOne (1/2) [1/4, 1/3, 2/3, 3/4]
        1 -> decide (1/2) [1/4, 1/3, 2/3, 3/4]
        2 -> moreOrLess
        3 -> chooseUnbiased (1/2) [1/4, 1/3, 2/3, 3/4]
        4 -> decideBiased (1/2) [1/4, 1/3, 2/3, 3/4]
        5 -> identifyBias
        _ -> putStrLn "Wait, how did you do that?"