LambdaHack-0.11.0.1: definition-src/Game/LambdaHack/Content/ModeKind.hs
-- | The type of game modes.
module Game.LambdaHack.Content.ModeKind
( pattern CAMPAIGN_SCENARIO, pattern INSERT_COIN
, ModeKind(..), makeData
, Caves, Roster
, mandatoryGroups
#ifdef EXPOSE_INTERNAL
-- * Internal operations
, validateSingle, validateAll, validateSingleRoster
#endif
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import qualified Data.Text as T
import Game.LambdaHack.Content.CaveKind (CaveKind)
import Game.LambdaHack.Content.FactionKind
(FactionKind (..), Outcome (..))
import Game.LambdaHack.Content.ItemKind (ItemKind)
import qualified Game.LambdaHack.Core.Dice as Dice
import Game.LambdaHack.Definition.ContentData
import Game.LambdaHack.Definition.Defs
import Game.LambdaHack.Definition.DefsInternal
-- | Game mode specification.
data ModeKind = ModeKind
{ mname :: Text -- ^ short description
, mfreq :: Freqs ModeKind -- ^ frequency within groups
, mtutorial :: Bool -- ^ whether to show tutorial messages, etc.
, mattract :: Bool -- ^ whether this is an attract mode
, mroster :: Roster -- ^ factions taking part in the game
, mcaves :: Caves -- ^ arena of the game
, mendMsg :: [(Outcome, Text)]
-- ^ messages displayed at each particular game ends; if message empty,
-- the screen is skipped
, mrules :: Text -- ^ rules note
, mdesc :: Text -- ^ description
, mreason :: Text -- ^ why/when the mode should be played
, mhint :: Text -- ^ hints in case player faces difficulties
}
deriving Show
-- | Requested cave groups for particular level intervals.
type Caves = [([Int], [GroupName CaveKind])]
-- | The specification of factions and of levels, numbers and groups
-- of their initial members.
type Roster = [( GroupName FactionKind
, [(Int, Dice.Dice, GroupName ItemKind)] )]
-- | Catch invalid game mode kind definitions.
validateSingle :: ContentData FactionKind -> ModeKind -> [Text]
validateSingle cofact ModeKind{..} =
[ "mname longer than 22" | T.length mname > 22 ]
++ let f cave@(ns, l) =
[ "not enough or too many levels for required cave groups:"
<+> tshow cave
| length ns /= length l ]
in concatMap f mcaves
++ validateSingleRoster cofact mcaves mroster
-- | Checks, in particular, that there is at least one faction with fneverEmpty
-- or the game would get stuck as soon as the dungeon is devoid of actors.
validateSingleRoster :: ContentData FactionKind -> Caves -> Roster -> [Text]
validateSingleRoster cofact caves roster =
let emptyGroups = filter (not . oexistsGroup cofact) $ map fst roster
in [ "the following faction kind groups have no representative with non-zero frequency:"
<+> T.intercalate ", " (map displayGroupName emptyGroups)
| not $ null emptyGroups ]
++ let fkKeepsAlive acc _ _ fk = acc && fneverEmpty fk
-- all of group elements have to keep level alive, hence conjunction
fkGroupKeepsAlive (fkGroup, _) =
ofoldlGroup' cofact fkGroup fkKeepsAlive True
in [ "potentially no faction keeps the dungeon alive"
| not $ any fkGroupKeepsAlive roster ]
++ let fkHasUIor acc _ _ fk = acc || fhasUI fk
-- single group element having UI already incurs the risk
-- of duplication, hence disjunction
fkGroupHasUIor (fkGroup, _) =
ofoldlGroup' cofact fkGroup fkHasUIor False
in [ "potentially more than one UI client"
| length (filter fkGroupHasUIor roster) > 1 ]
++ let fkHasUIand acc _ _ fk = acc && fhasUI fk
-- single group element missing UI already incurs the risk
-- of no UI in the whole game, hence disjunction
fkGroupHasUIand (fkGroup, _) =
ofoldlGroup' cofact fkGroup fkHasUIand True
in [ "potentially less than one UI client"
| not (any fkGroupHasUIand roster) ]
++ let fkTokens acc _ _ fk = fteam fk : acc
fkGroupTokens (fkGroup, _) = ofoldlGroup' cofact fkGroup fkTokens []
tokens = concatMap (nub . sort . fkGroupTokens) roster
nubTokens = nub . sort $ tokens
in [ "potentially duplicate team continuity token"
| length tokens /= length nubTokens ]
++ let keys = concatMap fst caves -- permitted to be empty, for tests
minD = minimum keys
maxD = maximum keys
f (_, l) = concatMap g l
g i3@(ln, _, _) =
[ "initial actor levels not among caves:" <+> tshow i3
| ln `notElem` keys ]
in concatMap f roster
++ [ "player is confused by both positive and negative level numbers"
| not (null keys) && signum minD /= signum maxD ]
++ [ "player is confused by level numer zero"
| 0 `elem` keys ]
-- | Validate game mode kinds together.
validateAll :: [ModeKind] -> ContentData ModeKind -> [Text]
validateAll _ _ = [] -- so far, always valid
-- * Mandatory item groups
mandatoryGroups :: [GroupName ModeKind]
mandatoryGroups =
[CAMPAIGN_SCENARIO, INSERT_COIN]
pattern CAMPAIGN_SCENARIO, INSERT_COIN :: GroupName ModeKind
pattern CAMPAIGN_SCENARIO = GroupName "campaign scenario"
pattern INSERT_COIN = GroupName "insert coin"
makeData :: ContentData FactionKind
-> [ModeKind] -> [GroupName ModeKind] -> [GroupName ModeKind]
-> ContentData ModeKind
makeData cofact content groupNamesSingleton groupNames =
makeContentData "ModeKind" mname mfreq (validateSingle cofact) validateAll
content
groupNamesSingleton
(mandatoryGroups ++ groupNames)