module Wordify.Rules.LetterBag (
LetterBag,
makeBag,
takeLetters,
exchangeLetters,
shuffleBag, bagSize,
tiles,
bagFromTiles,
makeBagUsingGenerator,
getGenerator,
shuffleWithNewGenerator) where
import Wordify.Rules.Tile
import System.Random
import Data.Array.IO
import Control.Monad
import qualified Control.Exception as Exc
import Wordify.Rules.ScrabbleError
import Text.ParserCombinators.Parsec
import Data.Char
import Wordify.Rules.LetterBag.Internal
import System.IO
import Data.Array.ST
import Control.Monad.ST
import Data.STRef
makeBag :: FilePath -> IO (Either ScrabbleError LetterBag)
makeBag path = do
ioOutcome <- Exc.try $ withFile path ReadMode (hGetContents >=> parseBagString path) :: IO (Either Exc.IOException (Either ScrabbleError LetterBag))
case ioOutcome of
Left _ -> return $ Left (LetterBagFileNotOpenable path)
Right x -> return $ fmap shuffleBag x
parseBagString :: String -> String -> IO (Either ScrabbleError LetterBag)
parseBagString path bagString =
let parseResult = parseBag bagString in
case parseResult of
Left _ -> return $ Left (MalformedLetterBagFile path)
Right parsedTiles ->
do
gen <- newStdGen
return $ Right (LetterBag parsedTiles (length parsedTiles) gen)
bagFromTiles :: [Tile] -> IO LetterBag
bagFromTiles bagTiles = newStdGen >>= return . LetterBag bagTiles (length bagTiles)
takeLetters :: LetterBag -> Int -> Maybe ([Tile], LetterBag)
takeLetters (LetterBag bagTiles lettersLeft gen) numTake =
if (newNumLetters < 0) then Nothing
else Just (taken, LetterBag newLetters newNumLetters gen)
where
newNumLetters = lettersLeft numTake
(taken, newLetters) = splitAt numTake bagTiles
exchangeLetters :: LetterBag -> [Tile] -> (Maybe ([Tile], LetterBag))
exchangeLetters (LetterBag bagTiles lettersLeft gen) exchanged =
if (lettersLeft == 0) then Nothing else takeLetters (shuffleBag intermediateBag) numLettersGiven
where
numLettersGiven = length exchanged
intermediateBag = LetterBag (exchanged ++ bagTiles) (lettersLeft + numLettersGiven) gen
shuffleBag :: LetterBag -> LetterBag
shuffleBag (LetterBag _ 0 gen) = LetterBag [] 0 gen
shuffleBag (LetterBag bagTiles size gen) =
let (newTiles, newGenerator) = shuffle bagTiles gen size
in (LetterBag newTiles size newGenerator)
where
shuffle :: [a] -> StdGen -> Int -> ([a],StdGen)
shuffle xs randomGen listLength = runST (do
g <- newSTRef randomGen
let randomRST lohi = do
(a,s') <- liftM (randomR lohi) (readSTRef g)
writeSTRef g s'
return a
ar <- newArr n xs
xs' <- forM [1..n] $ \i -> do
j <- randomRST (i,n)
vi <- readArray ar i
vj <- readArray ar j
writeArray ar j vi
return vj
gen' <- readSTRef g
return (xs',gen'))
where
n = listLength
newArr :: Int -> [a] -> ST s (STArray s Int a)
newArr z zs = newListArray (1,z) zs
makeBagUsingGenerator :: [Tile] -> StdGen -> LetterBag
makeBagUsingGenerator bagTiles randomGenerator = LetterBag bagTiles (length bagTiles) randomGenerator
getGenerator :: LetterBag -> StdGen
getGenerator = generator
shuffleWithNewGenerator :: LetterBag -> IO LetterBag
shuffleWithNewGenerator letterBag = fmap (\newGen -> shuffleBag $ letterBag { generator = newGen }) newStdGen
parseBag :: String -> Either ParseError [Tile]
parseBag contents = parse bagFile "Malformed letter bag file" contents
where
bagFile =
do bagTiles <- many bagLine
eof
let flattenedTiles = concat bagTiles
return $ flattenedTiles
bagLine =
do bagTiles <- try (letterTiles) <|> blankTiles
return bagTiles
letterTiles =
do
tileCharacter <- letter
_ <- space
value <- many digit
_ <- space
distribution <- many digit
_ <- newline
return $ replicate (read distribution) (Letter (toUpper tileCharacter) (read value))
blankTiles =
do
_ <- char '_'
_ <- space
distribution <- many digit
_ <- newline
return $ replicate (read distribution) (Blank Nothing)