packages feed

LambdaHack-0.1.20110117: src/Display2.hs

module Display2 (module Display, module Display2) where

-- Display routines that are independent of the selected display frontend.

import Data.Set as S
import Data.List as L
import Data.Map as M
import Control.Monad.State hiding (State) -- for MonadIO, seems to be portable between mtl-1 and 2

import Message
import Display
import State
import Geometry
import Level
import LevelState
import Dungeon
import Perception
import Monster
import Item
import Keys as K

-- | Next event translated to a canonical form
nextCommand :: MonadIO m => Session -> m Key
nextCommand session =
  do
    e <- liftIO $ nextEvent session
    return (canonicalKey e)

-- | maps a key to the canonical key for the command it denotes
canonicalKey :: Key -> Key
canonicalKey e =
  case e of
    K.Char '8' -> K.Char 'K'
    K.Char '2' -> K.Char 'J'
    K.Char '4' -> K.Char 'H'
    K.Char '6' -> K.Char 'L'
    K.Char '7' -> K.Char 'Y'
    K.Char '9' -> K.Char 'U'
    K.Char '1' -> K.Char 'B'
    K.Char '3' -> K.Char 'N'
    K.Char '5' -> K.Char '.'
    K.Up       -> K.Char 'k'
    K.Down     -> K.Char 'j'
    K.Left     -> K.Char 'h'
    K.Right    -> K.Char 'l'
    K.Home     -> K.Char 'y'
    K.PgUp     -> K.Char 'u'
    K.End      -> K.Char 'b'
    K.PgDn     -> K.Char 'n'
    K.Begin    -> K.Char '.'
    k          -> k

-- | Displays a message on a blank screen. Waits for confirmation.
displayBlankConfirm :: Session -> String -> IO Bool
displayBlankConfirm session txt =
  let x = txt ++ more
  in  do
        display ((0, 0), normalLevelSize) session (const (attr, ' ')) x ""
        getConfirm session

-- | Waits for a space or return.
getConfirm :: MonadIO m => Session -> m Bool
getConfirm session =
  getOptionalConfirm return (const $ getConfirm session) session

getOptionalConfirm :: MonadIO m => (Bool -> m a) -> (Key -> m a) -> Session -> m a
getOptionalConfirm h k session =
  do
    e <- liftIO $ nextCommand session
    case e of
      K.Char ' ' -> h True
      K.Return   -> h True
      K.Esc      -> h False
      _          -> k e

-- | A yes-no confirmation.
getYesNo :: MonadIO m => Session -> m Bool
getYesNo session =
  do
    e <- liftIO $ nextCommand session
    case e of
      K.Char 'y' -> return True
      K.Char 'n' -> return False
      K.Esc      -> return False
      _          -> getYesNo session

-- | Configurable event handler for the direction keys. Is used to
--   handle player moves, but can also be used for directed commands
--   such as open/close.
handleDirection :: Key -> (Dir -> a) -> a -> a
handleDirection e h k =
  case e of
    K.Char 'k' -> h up
    K.Char 'j' -> h down
    K.Char 'h' -> h left
    K.Char 'l' -> h right
    K.Char 'y' -> h upleft
    K.Char 'u' -> h upright
    K.Char 'b' -> h downleft
    K.Char 'n' -> h downright
    _          -> k

-- | Configurable event handler for the upper direction keys. Is used to
--   handle player moves, but can also be used for directed commands
--   such as open/close.
handleUDirection :: Key -> (Dir -> a) -> a -> a
handleUDirection e h k =
  case e of
    K.Char 'K' -> h up
    K.Char 'J' -> h down
    K.Char 'H' -> h left
    K.Char 'L' -> h right
    K.Char 'Y' -> h upleft
    K.Char 'U' -> h upright
    K.Char 'B' -> h downleft
    K.Char 'N' -> h downright
    _          -> k

splitOverlay :: Int -> String -> [[String]]
splitOverlay s xs = splitOverlay' (lines xs)
  where
    splitOverlay' ls
      | length ls <= s = [ls]  -- everything fits on one screen
      | otherwise      = let (pre,post) = splitAt (s - 1) ls
                         in  (pre ++ [more]) : splitOverlay' post

-- | Returns a function that looks up the characters in the
-- string by location. Takes the height of the display plus
-- the string. Returns also the number of screens required
-- to display all of the string.
stringByLocation :: Y -> String -> (Int, Loc -> Maybe Char)
stringByLocation sy xs =
  let
    ls   = splitOverlay sy xs
    m    = M.fromList (zip [0..] (L.map (M.fromList . zip [0..]) (concat ls)))
    k    = length ls
  in
    (k, \ (y,x) -> M.lookup y m >>= \ n -> M.lookup x n)

displayLevel :: Session -> Perception -> State -> Message -> Maybe String -> IO Bool
displayLevel session per
             (state@(State { splayer = player@(Monster { mhpmax = phpmax, mhp = php, mdir = pdir, mloc = ploc }),
                             stime   = time,
                             sassocs = assocs,
                             slevel  = lvl@(Level nm sz@(sy,sx) ms smap nlmap lmeta) }))
             msg moverlay =
    let
      overlay = maybe "" id moverlay
      reachable = preachable per
      visible   = pvisible per
      sSml    = ssensory state == Smell
      sVis    = case ssensory state of Vision _ -> True; _ -> False
      sOmn    = sdisplay state == Omniscient
      sTer    = case sdisplay state of Terrain n -> n; _ -> 0
      lAt     = if sOmn || sTer > 0 then at else rememberAt
      lVision = if sVis
                  then \ vis rea ->
                       if      vis then setBG blue
                       else if rea then setBG magenta
                                   else id
                  else \ vis rea -> id
      (n,over) = stringByLocation (sy+1) overlay -- n is the number of overlay screens
      gold    = maybe 0 (icount . fst) $ findItem (\ i -> iletter i == Just '$') (mitems player)
      disp n msg =
        display ((0,0),sz) session
                 (\ loc -> let tile = nlmap `lAt` loc
                               sml  = ((smap ! loc) - time) `div` 100
                               vis  = S.member loc visible
                               rea  = S.member loc reachable
                               (rv,ra) = case L.find (\ m -> loc == mloc m) (player:ms) of
                                           _ | sTer > 0          -> viewTerrain sTer False (tterrain tile)
                                           Just m | sOmn || vis  -> viewMonster (mtype m)
                                           _ | sSml && sml >= 0  -> viewSmell sml
                                             | otherwise         -> viewTile vis tile assocs
                               vision = lVision vis rea
                           in
                             case over (loc `shift` ((sy+1) * n, 0)) of
                               Just c  ->  (attr, c)
                               _       ->  (ra . vision $ attr, rv))
                msg
                (take 40 (levelName nm ++ repeat ' ') ++
                 take 10 ("$: " ++ show gold ++ repeat ' ') ++
                 take 15 ("HP: " ++ show php ++ " (" ++ show phpmax ++ ")" ++ repeat ' ') ++
                 take 10 ("T: " ++ show (time `div` 10) ++ repeat ' '))
      msgs = splitMsg sx msg
      perf k []     = perfo k ""
      perf k [xs]   = perfo k xs
      perf k (x:xs) = disp n (x ++ more) >> getConfirm session >>= \ b ->
                      if b then perf k xs else return False
      perfo k xs
        | k < n - 1 = disp k xs >> getConfirm session >>= \ b ->
                      if b then perfo (k+1) xs else return False
        | otherwise = disp k xs >> return True
    in perf 0 msgs