Allure-0.4.2: src/Level.hs
module Level
( SmellTime(..), SmellMap, SecretMap
, ItemMap, TileMap, Level(..)
, updateHeroes, updateMonsters, updateLMap, updateLRMap, updateIMap
, updateSmell , emptyParty, at, rememberAt, iat, irememberAt
, accessible, openable, findLoc, findLocTry, dropItemsAt
) where
import Data.Binary
import qualified Data.List as L
import qualified Data.IntMap as IM
import Utils.Assert
import Geometry
import Loc
import Actor
import Item
import Content.TileKind
import Random
import qualified Tile
import qualified Feature as F
import qualified Kind
newtype SmellTime = SmellTime{smelltime :: Time} deriving Show
instance Binary SmellTime where
put = put . smelltime
get = fmap SmellTime get
type SmellMap = IM.IntMap SmellTime
type SecretMap = IM.IntMap Tile.SecretStrength
type ItemMap = IM.IntMap ([Item], [Item])
type TileMap = Kind.Array Loc TileKind
data Level = Level
{ lheroes :: Party -- ^ all heroes on the level
, lxsize :: X
, lysize :: Y
, lmonsters :: Party -- ^ all monsters on the level
, lsmell :: SmellMap
, lsecret :: SecretMap
, litem :: ItemMap
, lmap :: TileMap
, lrmap :: TileMap
, lmeta :: String
, lstairs :: (Loc, Loc) -- ^ here the stairs (down, up) from other levels end
}
deriving Show
updateLMap :: (TileMap -> TileMap) -> Level -> Level
updateLMap f lvl = lvl { lmap = f (lmap lvl) }
updateLRMap :: (TileMap -> TileMap) -> Level -> Level
updateLRMap f lvl = lvl { lrmap = f (lrmap lvl) }
updateIMap :: (IM.IntMap ([Item], [Item]) ->
IM.IntMap ([Item], [Item])) -> Level
-> Level
updateIMap f lvl = lvl { litem = f (litem lvl) }
updateSmell :: (SmellMap -> SmellMap) -> Level -> Level
updateSmell f lvl = lvl { lsmell = f (lsmell lvl) }
updateMonsters :: (Party -> Party) -> Level -> Level
updateMonsters f lvl = lvl { lmonsters = f (lmonsters lvl) }
updateHeroes :: (Party -> Party) -> Level -> Level
updateHeroes f lvl = lvl { lheroes = f (lheroes lvl) }
emptyParty :: Party
emptyParty = IM.empty
instance Binary Level where
put (Level hs sx sy ms ls le li lm lrm lme lstairs) =
do
put hs
put sx
put sy
put ms
put ls
put le
put (assert
(IM.null (IM.filter (\ (is1, is2) ->
L.null is1 && L.null is2) li)
`blame` li) li)
put lm
put lrm
put lme
put lstairs
get = do
hs <- get
sx <- get
sy <- get
ms <- get
ls <- get
le <- get
li <- get
lm <- get
lrm <- get
lme <- get
lstairs <- get
return (Level hs sx sy ms ls le li lm lrm lme lstairs)
at, rememberAt :: Level -> Loc -> (Kind.Id TileKind)
at l p = lmap l Kind.! p
rememberAt l p = lrmap l Kind.! p
-- Note: representations with 2 maps leads to longer code and slower 'remember'.
iat, irememberAt :: Level -> Loc -> [Item]
iat l p = fst $ IM.findWithDefault ([], []) p (litem l)
irememberAt l p = snd $ IM.findWithDefault ([], []) p (litem l)
-- Check whether one location is accessible from the other.
-- Precondition: the two locations are next to each other.
-- Currently only implements that the target location has to be open.
-- TODO: in the future check flying for chasms, swimming for water, etc.
accessible :: Level -> Loc -> Loc -> Bool
accessible lvl _source target =
let tgt = lvl `at` target
in Tile.isWalkable tgt
-- check whether the location contains a door of secrecy level lower than k
openable :: Level -> Tile.SecretStrength -> Loc -> Bool
openable lvl k target =
let le = lsecret lvl
tgt = lvl `at` target
in Tile.hasFeature F.Openable tgt ||
(Tile.hasFeature F.Hidden tgt &&
le IM.! target < k)
-- Do not scatter items around, it's too much work for the player.
dropItemsAt :: [Item] -> Loc -> Level -> Level
dropItemsAt [] _loc = id
dropItemsAt items loc =
let joinItems = L.foldl' (\ acc i -> snd (joinItem i acc))
adj Nothing = Just (items, [])
adj (Just (i, ri)) = Just (joinItems items i, ri)
in updateIMap (IM.alter adj loc)
findLoc :: TileMap -> (Loc -> (Kind.Id TileKind) -> Bool) -> Rnd Loc
findLoc lmap p =
let search = do
loc <- randomR $ Kind.bounds lmap
let tile = lmap Kind.! loc
if p loc tile
then return loc
else search
in search
findLocTry ::
Int -> -- try to pTry only so many times
TileMap ->
(Loc -> Kind.Id TileKind -> Bool) -> -- loop until satisfied
(Loc -> Kind.Id TileKind -> Bool) -> -- only try to satisfy k times
Rnd Loc
findLocTry numTries lmap p pTry =
let search k = do
loc <- randomR $ Kind.bounds lmap
let tile = lmap Kind.! loc
if p loc tile && pTry loc tile
then return loc
else if k > 1
then search (k - 1)
else findLoc lmap p
in assert (numTries > 0) $
search numTries