packages feed

Allure-0.4.2: src/DungeonState.hs

module DungeonState where

import qualified System.Random as R
import qualified Data.List as L
import qualified Control.Monad.State as MState
import qualified Data.Map as M
import qualified Data.IntMap as IM
import Control.Monad

import Utils.Assert
import Loc
import Level
import qualified Dungeon
import Random
import qualified Config
import WorldLoc
import State
import qualified Feature as F
import qualified Tile
import Content.CaveKind
import Cave
import Content.ItemKind
import qualified Kind
import Item
import Geometry
import Frequency

listArrayCfg :: Int -> Int -> TileMapXY -> TileMap
listArrayCfg cxsize cysize lmap =
  Kind.listArray (zeroLoc, toLoc cxsize (cxsize - 1, cysize - 1))
    (M.elems $ M.mapKeys (\ (x, y) -> (y, x)) lmap)

unknownTileMap :: Int -> Int ->  TileMap
unknownTileMap cxsize cysize =
  Kind.listArray (zeroLoc, toLoc cxsize (cxsize - 1, cysize - 1))
    (repeat Tile.unknownId)

mapToIMap :: X -> M.Map (X, Y) a -> IM.IntMap a
mapToIMap cxsize m =
  IM.fromList $ map (\ (xy, a) -> (toLoc cxsize xy, a)) (M.assocs m)

rollItems :: Int -> CaveKind -> TileMap -> Loc -> Rnd [(Loc, Item)]
rollItems n CaveKind{cxsize, citemNum} lmap ploc =
  do
    nri <- rollDice citemNum
    replicateM nri $
      do
        item <- newItem n
        l <- case iname (Kind.getKind (jkind item)) of
               "sword" ->
                 -- swords generated close to monsters; MUAHAHAHA
                 findLocTry 2000 lmap
                   (const Tile.isBoring)
                   (\ l _ -> distance cxsize ploc l > 30)
               _ -> findLoc lmap
                      (const Tile.isBoring)
        return (l, item)

-- | Create a level from a cave, from a cave kind.
buildLevel :: Cave -> Int -> Int -> Rnd Level
buildLevel Cave{dkind, dsecret, ditem, dmap, dmeta} n depth = do
  let cfg@CaveKind{cxsize, cysize, minStairsDistance} = Kind.getKind dkind
      cmap = listArrayCfg  cxsize cysize dmap
  -- Roll locations of the stairs.
  su <- findLoc cmap (const Tile.isBoring)
  sd <- findLocTry 2000 cmap
          (\ l t -> l /= su && Tile.isBoring t)
          (\ l _ -> distance cxsize su l >= minStairsDistance)
  let stairs =
        [(su, Tile.stairsUpId)]
        ++ if n == depth then [] else [(sd, Tile.stairsDownId)]
      lmap = cmap Kind.// stairs
  is <- rollItems n cfg lmap su
  let itemMap = mapToIMap cxsize ditem `IM.union` IM.fromList is
      litem = IM.map (\ i -> ([i], [])) itemMap
      level = Level
        { lheroes = emptyParty
        , lxsize = cxsize
        , lysize = cysize
        , lmonsters = emptyParty
        , lsmell = IM.empty
        , lsecret = mapToIMap cxsize dsecret
        , litem
        , lmap
        , lrmap = unknownTileMap cxsize cysize
        , lmeta = dmeta
        , lstairs = (su, sd)
        }
  return level

matchGenerator :: Maybe String -> Rnd (Kind.Id CaveKind)
matchGenerator Nothing = do
  (ci, _) <- frequency Kind.frequency
  return ci
matchGenerator (Just "caveRogue") = do
  let freq = filterFreq ((== CaveRogue) . clayout . snd) Kind.frequency
  (ci, _) <- frequency freq
  return ci
matchGenerator (Just "caveEmpty") = do
  let freq = filterFreq ((== CaveEmpty) . clayout . snd) Kind.frequency
  (ci, _) <- frequency freq
  return ci
matchGenerator (Just "caveNoise") =
  return $ Kind.getId (\ cc -> clayout cc == CaveNoise && cxsize cc < 100)
matchGenerator (Just s) =
  error $ "Unknown dungeon generator " ++ s

findGenerator :: Config.CP -> Int -> Int -> Rnd Level
findGenerator config n depth = do
  let ln = "LambdaCave_" ++ show n
      genName = Config.getOption config "dungeon" ln
  ci <- matchGenerator genName
  cave <- buildCave n ci
  buildLevel cave n depth

-- | Generate the dungeon for a new game.
generate :: Config.CP -> Rnd (Loc, LevelId, Dungeon.Dungeon)
generate config =
  let depth = Config.get config "dungeon" "depth"
      gen :: R.StdGen -> Int -> (R.StdGen, (LevelId, Level))
      gen g k =
        let (g1, g2) = R.split g
            res = MState.evalState (findGenerator config k depth) g1
        in (g2, (LambdaCave k, res))
      con :: R.StdGen -> ((Loc, LevelId, Dungeon.Dungeon), R.StdGen)
      con g =
        let (gd, levels) = L.mapAccumL gen g [1..depth]
            ploc = fst (lstairs (snd (head levels)))
        in ((ploc, LambdaCave 1, Dungeon.fromList levels), gd)
  in MState.state con

whereTo :: State -> Loc -> Maybe WorldLoc
whereTo state@State{slid, sdungeon} loc =
  let lvl = slevel state
      tile = lvl `at` loc
      k | Tile.hasFeature F.Climbable tile = -1
        | Tile.hasFeature F.Descendable tile = 1
        | otherwise = assert `failure` tile
      n = levelNumber slid
      nln = n + k
      ln = LambdaCave nln
      lvlTrg = sdungeon Dungeon.! ln
  in if (nln < 1)
     then Nothing
     else Just (ln, (if k == 1 then fst else snd) (lstairs lvlTrg))