module HangmanAscii.Game
( Skill(..)
, hangman
) where
import Data.Set
import Data.Char
import Data.Bool
import Control.Monad.Trans
import System.Console.Haskeline
import System.Random
import HangmanAscii.Shape
rows :: Int
rows = 8
maxMistakes :: Skill -> Int
maxMistakes Easy = 18
maxMistakes Medium = 10
maxMistakes Hard = 6
maxShapeProgress :: Int
maxShapeProgress = maximum $ pSeverity <$> shapeHangman
shapeProgress :: Skill -> Int -> Int
shapeProgress Easy invalids = invalids
shapeProgress s invalids = maxShapeProgress - (maxMistakes s) + invalids
data Skill = Easy | Medium | Hard
data Session = Session Int Int
data Game = Game String String Skill (Set Char) (Set Char)
data Status = Playing | GameOver| Won
randomPick :: [String] -> IO String
randomPick words = (words !!) <$> (randomRIO (0, end))
where end = (length words) - 1
alphabet :: Set Char -> String
alphabet hidden = get.chr <$> [97..122]
where
get :: Char -> Char
get c
| c `member` hidden = ' '
| otherwise = toUpper c
printState :: Session -> Status -> Game -> IO ()
printState (Session wins fails) st (Game word cat skill valid invalid) = do
putStr $ "\x1b[" ++ (show rows) ++ "A\x1b[0J"
mconcat $ putStrLn <$> zipWith fill
[ "Wins: " ++ (show wins) ++ " Fails: " ++ (show fails)
, mconcat $ (:" ") <$> alphabet (valid `union` invalid)
, ""
, mconcat $ (:" ").wordChar <$> word
, ""
-- , word
, hint st
, ""
] (shape st <$> [0..6])
where
fill x y = x ++ replicate (56 - length x) ' ' ++ y
errLen = length invalid
shape Won r = shapeWonRow r 0
shape _ r = shapeHangmanRow r $ shapeProgress skill errLen
wordChar c
| c `member` valid = c
| otherwise = '_'
hint GameOver = mconcat $ (:" ") <$> word
hint _ = "Category: " ++ cat
progress :: Char -> Game -> Game
progress c (Game word cat skill valid invalid)
| c `elem` word = Game word cat skill (insert cl valid) invalid
| otherwise = Game word cat skill valid (insert cl invalid)
where cl = toLower c
status :: Game -> Status
status (Game word cat skill valid invalid)
| (length invalid) == (maxMistakes skill) = GameOver
| (fromList word) == valid = Won
| otherwise = Playing
stateLoop :: Session -> Game -> InputT IO (Maybe Bool)
stateLoop s g = do
let st = status g
lift (printState s st g)
case st of
GameOver -> outputStrLn "Game Over!" >> return (Just False)
Won -> outputStrLn "You won !!!" >> return (Just True)
Playing -> do
key <- getInputChar "Guess a character, a ~ z: "
case key of
Nothing -> return Nothing
Just c -> stateLoop s (progress c g)
continue :: InputT IO Bool
continue = do
key <- getInputChar "Do you want to continue? [Y/n] "
case toLower <$> key of
Nothing -> return False
Just 'n' -> return False
Just 'y' -> return True
Just _ -> do
outputStr "\x1b[1A\x1b[0G"
continue
updateSession :: Bool -> Session -> Session
updateSession True (Session wins fails) = Session (wins + 1) fails
updateSession False (Session wins fails) = Session wins (fails + 1)
sessionLoop :: Session -> String -> [String] -> Skill -> InputT IO ()
sessionLoop session cat words skill = do
w <- lift (randomPick words)
won <- stateLoop session (Game w cat skill empty empty)
case won of
Nothing -> return ()
Just yes -> do
c <- continue
case c of
False -> return ()
True -> do
outputStrLn "\x1b[2A\x1b[0J"
sessionLoop (updateSession yes session) cat words skill
hangman :: String -> [String] -> Skill -> IO ()
hangman cat words skill = do
mconcat $ replicate rows (putStrLn "")
runInputT defaultSettings $ sessionLoop (Session 0 0) cat words skill