packages feed

roguestar-engine 0.4.0.0 → 0.4.0.1

raw patch · 13 files changed

+713/−1 lines, 13 files

Files

roguestar-engine.cabal view
@@ -1,5 +1,5 @@ name:                roguestar-engine-version:             0.4.0.0+version:             0.4.0.1 license:             OtherLicense license-file:        LICENSE author:              Christopher Lane Hinson@@ -45,6 +45,9 @@                      Tests, DBData, GridRayCaster, BeginGame,                      SpeciesData, TimeCoordinate, DB, AttributeGeneration,                      CreatureAttribute, Building, BuildingData, Town+                     Random, PlayerState, MakeData, DBErrorFlag, Construction,+                     Make, Activate, Contact, DeviceActivation, WorkCluster,+                     Planet, PlanetData  ghc-options:         -Wall -threaded -fno-warn-type-defaults ghc-prof-options:    -prof -auto-all
+ src/Activate.hs view
@@ -0,0 +1,47 @@+module Activate+    (ActivationOutcome,+     resolveActivation,+     executeActivation)+    where++import Tool+import ToolData+import Creature+import DB+import Control.Monad.Error+import Substances++-- | Outcome of activating a tool.+data ActivationOutcome =+    Heal CreatureRef Integer+  | ExpendTool ToolRef ActivationOutcome+  | NoEffect++resolveActivation :: (DBReadable db) => CreatureRef -> db ActivationOutcome+resolveActivation creature_ref =+    do tool_ref <- maybe (throwError $ DBErrorFlag NoToolWielded) return =<< dbGetWielded creature_ref+       tool <- dbGetTool tool_ref+       case tool of+           DeviceTool {} -> throwError $ DBErrorFlag ToolIs_Innapropriate+           Sphere (ChromaliteSubstance c) ->+               do x <- linearRoll $ chromalitePotency c+                  return $ if x == 0 then ExpendTool tool_ref $ NoEffect+                                     else Heal creature_ref x+           Sphere (MaterialSubstance m) ->+               do x <- linearRoll $ material_construction_value $ materialValue m+                  return $ ExpendTool tool_ref $ Heal creature_ref x+           Sphere (GasSubstance g) ->+               do x <- linearRoll $ gasValue g+                  return $ if x == 0 then ExpendTool tool_ref $ Heal creature_ref 1+                                     else Heal creature_ref 1++executeActivation :: ActivationOutcome -> DB ()+executeActivation (NoEffect) = return ()+executeActivation (Heal creature_ref x) = +    do healCreature x creature_ref+       dbPushSnapshot $ HealEvent creature_ref+executeActivation (ExpendTool tool_ref activation_outcome) =+    do executeActivation activation_outcome+       dbPushSnapshot $ ExpendToolEvent tool_ref+       deleteTool tool_ref+                      
+ src/Construction.hs view
@@ -0,0 +1,34 @@+-- | All construction (terrain clearing, etc) actions that a creature might take.+module Construction+    (modifyFacingTerrain,+     clearTerrain)+    where++import DB+import Plane+import TerrainData+import Facing+import Control.Monad+import Control.Monad.Maybe+import Control.Monad.Trans+import Position+import Data.Maybe++-- | Modifies terrain in the specified walking direction, returning+-- True iff any terrain modification actually occured.+modifyFacingTerrain :: (TerrainPatch -> TerrainPatch) -> Facing -> CreatureRef -> DB Bool+modifyFacingTerrain f face creature_ref = liftM (fromMaybe False) $ runMaybeT $+    do (plane_ref,position) <- MaybeT $ liftM extractLocation $ dbWhere creature_ref+       let target_position = offsetPosition (facingToRelative face) position+       prev_terrain <- lift $ terrainAt plane_ref target_position+       let new_terrain = f prev_terrain+       when (new_terrain == prev_terrain) $ fail ""+       lift $ setTerrainAt plane_ref target_position new_terrain+       return True++clearTerrain :: TerrainPatch -> TerrainPatch+clearTerrain RockFace = Rubble+clearTerrain Forest = Grass+clearTerrain DeepForest = Grass+clearTerrain Lava = Glass+clearTerrain x = x
+ src/Contact.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}++module Contact+    (findContacts,+     ContactMode(..),+     ContactModeType(..))+    where++import Position+import Facing+import DB+import CreatureData+import Control.Monad+import Plane+import Data.Ord+import Data.List as List+import Data.Maybe++-- | 'Touch' contacts are on the same or facing square as the subject.+-- 'Line' contacts are on any point starting on the same square and anywhere directly along a line traced in the+-- facing direction, out to infinity.  'Area' contacts lie inside a circle of radius 7, centered 7 squares in the+-- facing direction.  Use 'Area' 'Here' for a circle centerd on the subject.+data ContactMode = Touch | Line | Area++class ContactModeType a where+    contactMode :: a -> ContactMode++instance ContactModeType ContactMode where+    contactMode = id++instance ContactModeType CreatureInteractionMode where+    contactMode Unarmed = Touch+    contactMode Melee = Touch+    contactMode Ranged = Line+    contactMode Splash = Area++-- | Find contacts to a reference.  The result is sorted by from closest to farthest from the subject, except in the case+-- of area contacts, which are sorted from the center of the area.  The subject is never a contact of itself.+findContacts :: (DBReadable db,ReferenceType x,GenericReference a S,ContactModeType c) => c -> Reference x -> Facing -> db [a]+findContacts contact_mode attacker_ref face =+    do (m_l :: Maybe (PlaneRef,MultiPosition)) <- liftM (fmap location) $ getPlanarPosition attacker_ref+       let testF pos (x :: MultiPosition) = case contactMode contact_mode of+               Touch -> min (x `distanceBetweenChessboard` (offsetPosition (facingToRelative face) pos))+                            (x `distanceBetweenChessboard` pos) == 0+               Line -> isFacing (pos,face) x+               Area -> distanceBetweenSquared (offsetPosition (facingToRelative7 face) pos) x < 49+           center_pos pos = case contactMode contact_mode of+               Area -> offsetPosition (facingToRelative7 face) pos+               _ -> pos+       flip (maybe $ return []) m_l $ \(plane_ref,pos) ->+           liftM (mapMaybe fromLocation .+	          sortBy (comparing (distanceBetweenSquared (center_pos pos) . location)) .+	          filter ((/= generalizeReference attacker_ref) . entity) . +	          filter (testF pos . location)) $ +		      dbGetContents plane_ref+
+ src/DBErrorFlag.hs view
@@ -0,0 +1,19 @@+module DBErrorFlag+    (ErrorFlag(..))+    where++data ErrorFlag =+    BuildingApproachWrongAngle -- some buildings (like stargates) are sensitive to the angle of approach+  | NothingAtFeet              -- tried to pick something up, but there is nothing at your feet+  | NothingInInventory         -- tried to perform an inventory action, but your inventory is empty+  | NotStanding                -- the player is not standing on anything (e.g. race selection state)+  | NoStargateAddress          -- tried to move through the stargate network, but there was no destination+  | NoToolWielded              -- tried to perform an action that requires a wielded tool+  | ToolIs_NotAtFeet           -- tried to pick something up, but it isn't at your feet+  | ToolIs_NotInInventory      -- tried to perform an inventory action on a tool that isn't in inventory+  | ToolIs_NotWieldable        -- tried to wield a tool that can't be wielded.  As of March 2010, there are no such tools, so this is a bug.+  | ToolIs_Unreachable         -- tried to perform an action with a tool that isn't in reach (not in inventory, wielded, or at feet)+  | ToolIs_Innapropriate       -- tried to perform an action with a tool that can be used for that purpose+  | Unable                     -- you can't do it+    deriving (Eq,Ord,Read,Show)+
+ src/DeviceActivation.hs view
@@ -0,0 +1,37 @@+module DeviceActivation+    (DeviceActivationOutcomeType(..),+     DeviceActivationOutcome(..),+     resolveDeviceActivation)+    where++import DB+import Creature+import CreatureData+import ToolData+import Data.Ratio++data DeviceActivationOutcomeType = +    DeviceActivated+  | DeviceFailed+  | DeviceCriticalFailed++data DeviceActivationOutcome = DeviceActivationOutcome {+    dao_outcome_type :: DeviceActivationOutcomeType,+    dao_skill_roll :: Integer,+    dao_energy :: Integer,+    dao_activation_time :: Rational }++-- | Given a device, and a primary and secondary roll, determine the outcome of activating the device.+-- The better the primary roll, the less likely that the device will fail, while the better the secondary +-- roll, the more energy the device will output.+resolveDeviceActivation :: (DBReadable db,DeviceType d) => CreatureAbility -> CreatureAbility -> CreatureAbility -> d -> CreatureRef -> db DeviceActivationOutcome+resolveDeviceActivation primary_ability secondary_ability timing_ability device creature_ref =+    do primary_roll <- rollCreatureAbilityScore primary_ability (deviceAccuracy device) creature_ref+       secondary_roll <- rollCreatureAbilityScore secondary_ability (deviceOutput device) creature_ref+       timing_roll <- rollCreatureAbilityScore timing_ability (deviceSpeed device) creature_ref+       let timing = roll_ideal secondary_roll % (roll_ideal timing_roll + roll_ideal secondary_roll)+           daoF = case () of+                      () | roll_actual primary_roll == 0 -> DeviceActivationOutcome DeviceCriticalFailed 0 (deviceOutput device * deviceSize device)+                      () | roll_actual primary_roll <= deviceSize device -> DeviceActivationOutcome DeviceFailed (roll_actual primary_roll) (deviceOutput device)+                      () | otherwise -> DeviceActivationOutcome DeviceActivated (roll_actual primary_roll * deviceSize device) (roll_actual secondary_roll)+       return $ daoF timing
+ src/Make.hs view
@@ -0,0 +1,26 @@+module Make+    (module MakeData,+     MakeOutcome,+     resolveMake,+     executeMake)+    where++import MakeData+import DB+import Tool+import ToolData+import Data.List++data MakeOutcome = MakeSuccess CreatureRef Tool [ToolRef] | MakeFailed++resolveMake :: (DBReadable db) => CreatureRef -> PrepareMake -> db MakeOutcome+resolveMake c (PrepareMake (Just dk) (Just (ch,ch_tool_ref)) (Just (m,m_tool_ref)) (Just (g,g_tool_ref))) =+    return $ MakeSuccess c (improvised dk ch m g) [ch_tool_ref,m_tool_ref,g_tool_ref]+resolveMake _ _ = return MakeFailed++executeMake :: MakeOutcome -> DB ()+executeMake (MakeSuccess c t refs) =+    do mapM_ deleteTool $ nub refs+       _ <- dbAddTool t (Wielded c)+       return ()+executeMake MakeFailed = return ()
+ src/MakeData.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE FlexibleInstances #-}+module MakeData+    (PrepareMake(..),+     prepare_make,+     isFinished,+     needsKind,+     needsChromalite,+     needsMaterial,+     needsGas,+     hasChromalite,+     hasMaterial,+     hasGas,+     MakeWith(..))+    where++import DBData+import ToolData+import Substances++-- | Multi-step process for gathering the materials to make something.+data PrepareMake = PrepareMake {+    m_device_kind :: (Maybe DeviceKind),+    m_chromalite :: (Maybe (Chromalite,ToolRef)),+    m_material :: (Maybe (Material,ToolRef)),+    m_gas :: (Maybe (Gas,ToolRef)) } deriving (Read,Show)++-- | An empty prepare_make.+prepare_make :: PrepareMake+prepare_make = PrepareMake Nothing Nothing Nothing Nothing++isFinished :: PrepareMake -> Bool+isFinished (PrepareMake (Just _) (Just _) (Just _) (Just _)) = True+isFinished _ = False++needsKind :: PrepareMake -> Bool+needsKind (PrepareMake Nothing _ _ _) = True+needsKind _ = False++needsChromalite :: PrepareMake -> Bool+needsChromalite (PrepareMake _ Nothing _ _) = True+needsChromalite _ = False++needsMaterial :: PrepareMake -> Bool+needsMaterial (PrepareMake _ _ Nothing _) = True+needsMaterial _ = False++needsGas :: PrepareMake -> Bool+needsGas (PrepareMake _ _ _ Nothing) = True+needsGas _ = False++hasChromalite :: Tool -> Maybe Chromalite+hasChromalite (DeviceTool _ d) = Just $ deviceChromalite d+hasChromalite (Sphere (ChromaliteSubstance s)) = Just s+hasChromalite _ = Nothing++hasMaterial :: Tool -> Maybe Material+hasMaterial (DeviceTool _ d) = Just $ deviceMaterial d+hasMaterial (Sphere (MaterialSubstance s)) = Just s+hasMaterial _ = Nothing++hasGas :: Tool -> Maybe Gas+hasGas (DeviceTool _ d) = Just $ deviceGas d+hasGas (Sphere (GasSubstance s)) = Just s+hasGas _ = Nothing++class MakeWith a where+    makeWith :: PrepareMake -> a -> PrepareMake++instance MakeWith DeviceKind where+    makeWith make_prep x = make_prep { m_device_kind = Just x }++instance (SubstanceType s) => MakeWith (s,ToolRef) where+    makeWith make_prep (x,tool_ref) = makeWithSubstance make_prep (toSubstance x,tool_ref)++makeWithSubstance :: PrepareMake -> (Substance,ToolRef) -> PrepareMake+makeWithSubstance make_prep (ChromaliteSubstance s,tool_ref) = make_prep { m_chromalite = Just (s,tool_ref) }+makeWithSubstance make_prep (MaterialSubstance s,tool_ref) = make_prep { m_material = Just (s,tool_ref) }+makeWithSubstance make_prep (GasSubstance s,tool_ref) = make_prep { m_gas = Just (s,tool_ref) }+
+ src/Planet.hs view
@@ -0,0 +1,43 @@+module Planet+    (makePlanets,+     generatePlanetInfo)+    where++import PlanetData+import DB+import Plane+import TerrainData+import Control.Monad+import Data.Maybe+import Data.Ord+import Town+import Data.List++makePlanet :: (PlaneLocation l) => l -> PlanetInfo -> DB PlaneRef+makePlanet plane_location planet_info =+    do seed <- getRandom+       plane_ref <- dbNewPlane+          (planet_info_name planet_info)+          (TerrainGenerationData {+               tg_smootheness = 3,+	       tg_biome = planet_info_biome planet_info,+	       tg_placements = [recreantFactories seed] })+          plane_location+       town <- liftM catMaybes $ forM (planet_info_town planet_info) $ \(r,b) ->+           do p <- rationalRoll r+              return $ if p then Just b else Nothing+       _ <- createTown plane_ref town+       return plane_ref++makePlanets :: (PlaneLocation l) => l -> [PlanetInfo]  -> DB PlaneRef+makePlanets _ [] = return $ error "makePlanetarySystem: empty list"+makePlanets l (planet_info:rest) =+    do plane_ref <- makePlanet l planet_info+       _ <- makePlanets (Subsequent plane_ref) rest+       return plane_ref++generatePlanetInfo :: (DBReadable db) => [PlanetInfo] -> db [PlanetInfo]+generatePlanetInfo planet_infos = liftM (sortBy (comparing planet_info_priority)) $ forM planet_infos $ \planet_info ->+    do prio_bonus <- getRandomR (0.0,1.0) -- see documentation for 'PlanetData.PlanetInfo'+       return $ planet_info `addPriority` prio_bonus+
+ src/PlanetData.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings #-}+module PlanetData+    (PlanetInfo(..),+     addTown,+     addPriority,+     all_planets,+     pgto_planets)+    where++import TerrainData+import BuildingData+import Data.Ratio+import qualified Data.ByteString.Char8 as B++-- | Information used to construct new planets.+-- Whenever the player goes through a stargate to a new planet,+-- we pull a new 'PlanetInfo' record off of a stack and a construct+-- a planet based on that information.+--+-- 'PlanetInfo's are sorted by their 'planet_info_priority' fields.+--+data PlanetInfo = PlanetInfo {+    -- | Between 0 and 1 are randomly added to this value, and then all 'PlanetInfo's are sorted by priority.+    -- This gives the order in which players visit planets.+    planet_info_priority :: Double,+    -- | Some planets have names.+    planet_info_name :: Maybe B.ByteString,+    planet_info_biome :: Biome,+    planet_info_town :: [(Rational,BuildingType)] }+        deriving (Read,Show)++pgto :: B.ByteString -> Biome -> PlanetInfo+pgto "" biome = PlanetInfo {+    planet_info_priority = 0.25,+    planet_info_name = Nothing,+    planet_info_biome = biome,+    planet_info_town = [(1,Portal),(1%2,Monolith),(1%2,Monolith)] }+pgto name biome = PlanetInfo {+    planet_info_priority = 0.0,+    planet_info_name = Just name,+    planet_info_biome = biome,+    planet_info_town = [(1,Portal)] }++addTown :: PlanetInfo -> [(Rational,BuildingType)] -> PlanetInfo+addTown planet_info town = planet_info { planet_info_town = planet_info_town planet_info ++ town }++addPriority :: PlanetInfo -> Double -> PlanetInfo+addPriority planet_info prio = planet_info { planet_info_priority = planet_info_priority planet_info + prio }++all_planets :: [PlanetInfo]+all_planets = concat [pgto_planets]++pgto_planets :: [PlanetInfo]+pgto_planets = [+    pgto "" RockBiome,+    pgto "" IcyRockBiome,+    pgto "" TundraBiome,+    pgto "" DesertBiome,+    pgto "" MountainBiome,+    pgto "roanoke" SwampBiome,+    pgto "pamlico" SwampBiome,+    pgto "pungo" ForestBiome,+    pgto "neuse" ForestBiome,+    pgto "crabtree" SwampBiome,+    pgto "eno" SwampBiome `addTown` [(1%20,Monolith)],+    pgto "yadkin" SwampBiome,+    pgto "catawba" ForestBiome,+    pgto "pasquotank" ForestBiome,+    pgto "dogwood" GrasslandBiome `addPriority` 0.75,+    pgto "emerald" GrasslandBiome `addPriority` 0.75,+    pgto "cardinal" GrasslandBiome `addPriority` 0.75,+    pgto "currituck" OceanBiome `addPriority` 1.5,+    pgto "hatteras" OceanBiome `addPriority` 1.5,+    pgto "lookout" OceanBiome `addPriority` 1.5,+    pgto "ocracoke" OceanBiome `addPriority` 1.5]
+ src/PlayerState.hs view
@@ -0,0 +1,108 @@+module PlayerState+    (PlayerState(..),+     CreatureTurnMode(..),+     SnapshotEvent(..),+     creatureOf,+     subjectOf,+     menuIndex,+     modifyMenuIndex)+    where++import DBData+import CreatureData+import MakeData++data PlayerState = +    RaceSelectionState+  | ClassSelectionState Creature+  | PlayerCreatureTurn CreatureRef CreatureTurnMode+  | SnapshotEvent SnapshotEvent+  | GameOver+	     deriving (Read,Show)++data CreatureTurnMode =+    NormalMode+  | MoveMode+  | PickupMode Integer+  | DropMode Integer+  | WieldMode Integer+  | MakeMode Integer PrepareMake+  | AttackMode+  | FireMode+  | JumpMode+  | TurnMode+  | ClearTerrainMode+      deriving (Read,Show)++data SnapshotEvent = +    AttackEvent {+        attack_event_source_creature :: CreatureRef,+        attack_event_source_weapon :: Maybe ToolRef,+        attack_event_target_creature :: CreatureRef }+  | MissEvent {+        miss_event_creature :: CreatureRef,+	miss_event_weapon :: Maybe ToolRef }+  | KilledEvent {+        killed_event_creature :: CreatureRef }+  | WeaponOverheatsEvent {+        weapon_overheats_event_creature :: CreatureRef,+        weapon_overheats_event_weapon :: ToolRef }+  | WeaponExplodesEvent {+        weapon_explodes_event_creature :: CreatureRef,+        weapon_explodes_event_weapon :: ToolRef }+  | DisarmEvent {+        disarm_event_source_creature :: CreatureRef,+        disarm_event_target_creature :: CreatureRef,+        disarm_event_target_tool :: ToolRef }+  | SunderEvent {+        sunder_event_source_creature :: CreatureRef,+        sunder_event_source_weapon :: ToolRef,+        sunder_event_target_creature :: CreatureRef,+        sunder_event_target_tool :: ToolRef }+  | TeleportEvent {+        teleport_event_creature :: CreatureRef }+  | HealEvent {+        heal_event_creature :: CreatureRef }+  | ExpendToolEvent {+        expend_tool_event_tool :: ToolRef }+            deriving (Read,Show)++-- | Get the 'Creature' acting in the given 'PlayerState'.+creatureOf :: PlayerState -> Maybe CreatureRef+creatureOf state = case state of	  +    PlayerCreatureTurn creature_ref _ -> Just creature_ref+    SnapshotEvent event -> subjectOf event+    GameOver -> Nothing+    ClassSelectionState {} -> Nothing+    RaceSelectionState {} -> Nothing++-- | Get the subject creature of a 'SnapshotEvent', that is, the creature taking action.+subjectOf :: SnapshotEvent -> Maybe CreatureRef+subjectOf event = case event of+    AttackEvent { attack_event_source_creature = attacker_ref } -> Just attacker_ref+    MissEvent { miss_event_creature = attacker_ref } -> Just attacker_ref+    WeaponOverheatsEvent { weapon_overheats_event_creature = attacker_ref } -> Just attacker_ref+    WeaponExplodesEvent { weapon_explodes_event_creature = attacker_ref } -> Just attacker_ref+    KilledEvent killed_ref -> Just killed_ref+    DisarmEvent { disarm_event_source_creature = attacker_ref } -> Just attacker_ref+    SunderEvent { sunder_event_source_creature = attacker_ref } -> Just attacker_ref+    TeleportEvent { teleport_event_creature = creature_ref } -> Just creature_ref+    HealEvent { heal_event_creature = creature_ref } -> Just creature_ref+    ExpendToolEvent {} -> Nothing++-- | Current index into the menu, if there is one. +menuIndex :: PlayerState -> Maybe Integer+menuIndex state = fst $ modifyMenuIndex_ id state++-- | Modify the current index into the menu, if there is one (otherwise has no effect).+modifyMenuIndex :: (Integer -> Integer) -> PlayerState -> PlayerState+modifyMenuIndex f state = snd $ modifyMenuIndex_ f state++modifyMenuIndex_ :: (Integer -> Integer) -> PlayerState -> (Maybe Integer,PlayerState)+modifyMenuIndex_ f state = case state of+    PlayerCreatureTurn c (PickupMode n) -> (Just n,PlayerCreatureTurn c $ PickupMode $ f n)+    PlayerCreatureTurn c (DropMode n) -> (Just n,PlayerCreatureTurn c $ DropMode $ f n)+    PlayerCreatureTurn c (WieldMode n) -> (Just n,PlayerCreatureTurn c $ WieldMode $ f n)+    PlayerCreatureTurn c (MakeMode n make_prep) -> (Just n,PlayerCreatureTurn c $ MakeMode (f n) make_prep)+    x -> (Nothing,x)+
+ src/Random.hs view
@@ -0,0 +1,93 @@++module Random+    (pick,+     pickM,+     weightedPick,+     weightedPickM,+     linearRoll,+     fixedSumRoll,+     fixedSumLinearRoll,+     logRoll,+     opposedLinearPowerRatio,+     rationalRoll)+    where++import Data.List+import Data.Maybe+import System.Random ()+import Control.Monad.Random+import Control.Monad+import Data.Ratio++-- | Pick an element of a list at random.+pick :: (RandomGen g) => [a] -> g -> (a,g)+pick elems = runRand (pickM elems)++-- | Pick an element of a weighted list at random.  E.g. in "[(2,x),(3,y)]" "y" will be picked three times out of five while "x" will be picked 2 times out of five.+weightedPick :: (RandomGen g) => [(Integer,a)] -> g -> (a,g)+weightedPick elems = runRand (weightedPickM elems)++-- | 'pick' in MinadRandom+pickM :: (MonadRandom m) => [a] -> m a+pickM elems = weightedPickM (map (\x -> (1,x)) elems)++-- | 'weightedPick' in MonadRandom+weightedPickM :: (MonadRandom m) => [(Integer,a)] -> m a+weightedPickM [] = error "Tried to pick from an empty list."+weightedPickM elems = +    do let (weights,values) = unzip elems+       let (weight_total,weight_totals) = mapAccumL (\x y -> (x+y,x+y)) 0 weights+       weight_to_find <- getRandomR (1,weight_total)+       let index = fromJust $ findIndex (\x -> x >= weight_to_find) weight_totals+       return $ values !! index++-- | Roll an (n+1) sided die numbered zero to n.+linearRoll :: (MonadRandom m) => Integer -> m Integer+linearRoll n = getRandomR (0,n)++-- | fixedSumRoll using 'linearRoll', with optimizations.+-- REVISIT: this can be improved significantly, but performance doesn't seem to be a material problem so far.+fixedSumLinearRoll :: (MonadRandom m) => [Integer] -> Integer -> m [Integer]+fixedSumLinearRoll xs a = fixedSumRoll (map (linearRoll . min a) xs) a++-- | Roll a sequence of random variables, such that the sum of the result is a fixed value.+fixedSumRoll :: (MonadRandom m) => [m Integer] -> Integer -> m [Integer]+fixedSumRoll rs a = +    do xs <- sequence rs+       case sum xs == a of+           True -> return xs+           False -> fixedSumRoll rs a++-- | Roll a die where the typical outcome is the base-2 logarithm of the input.+-- This function has exactly the same probability of rolling exactly 0 as 'linearDiceRoll'.+--+logRoll :: (MonadRandom m) => Integer -> m Integer+logRoll n = liftM (min n) $ accumRoll 0 n+    where accumRoll c x = +              do x' <- linearRoll x+                 case x' of+                     0 -> return c+                     _ -> accumRoll (c+1) x'++-- | Roll on a rational number that is a probability between zero and one, to generate a boolean.+rationalRoll :: (MonadRandom m) => Rational -> m Bool+rationalRoll r =+    do p <- linearRoll (denominator r - 1)+       return $ p < numerator r++-- | 'opposedLinearPowerRatio' is used when a constant (non-random) power relationship needs to be+-- determined between two parties.  (For example, this is used in the Spot/Hide contest when determining+-- line of sight.)+--+-- It accepts negative values for either parameter, and is invertable, i.e., +-- @opposedLinearPowerRatio a b@ = @1 - opposedLinearPowerRatio b a@+--+-- One use is: @2 * (a%1) * opposedLinearPowerRatio a b@, whichs gives you roughly @a@ if @a@ and @b@ are equal,+-- or less or more than @a@ otherwise.+opposedLinearPowerRatio :: Integer -> Integer -> Rational+opposedLinearPowerRatio a b | a < 1 = opposedLinearPowerRatio 1 (b-a+1)+opposedLinearPowerRatio a b | b < 1 = opposedLinearPowerRatio (a-b+1) 1+opposedLinearPowerRatio a b | a >= b = ((a-b) % a) + (b % a)/2+opposedLinearPowerRatio a b | otherwise = 1 - opposedLinearPowerRatio b a++
+ src/WorkCluster.hs view
@@ -0,0 +1,92 @@+module WorkCluster+    (WorkCluster,+     WorkRequestType(..),+     WorkRequest,+     WorkResult,+     newWorkCluster,+     workRequest,+     replaceWorkOperation)+    where++import PrioritySync.PrioritySync+import Control.Concurrent.MVar+import Control.Monad+import Control.Applicative+import Data.IORef+import qualified Data.ByteString as B+import qualified Data.PSQueue as PSQ+import qualified Data.Map as Map+import DB++-- | Measure the number of 'Query's between 'Action's, and add this number to get the maximum size of the most-recent-request queue.+queue_size_bonus :: Int+queue_size_bonus = 10++data WorkRequestType = Query | Action deriving (Eq,Ord,Show)+type WorkRequest = (WorkRequestType,[B.ByteString])+type WorkResult = Either DBError (B.ByteString,DB_BaseType)++data WorkPrio = Now | Eventually deriving (Eq,Ord)++data WorkClusterData = WorkClusterData {+    wc_queue_max_size :: IORef Int,            -- how large the most-recent-requests queue should be allowed to grow+    wc_recent_request_count :: IORef Int,      -- number of manual requests since the last 'replaceWorkOperation'+    wc_abort_counter :: IORef Integer,         -- increments on every 'replaceWorkOperation'+    wc_request_counter :: IORef Integer,       -- increments on every manual request+    wc_recent_requests :: IORef (PSQ.PSQ WorkRequest Integer),+    wc_task_pool :: IORef (TaskPool WorkPrio ()),+    wc_request_operation :: IORef (WorkRequest -> IO WorkResult),+    wc_task_handles :: IORef (Map.Map WorkRequest (TaskHandle WorkPrio WorkResult)) }++data WorkCluster = WorkCluster (MVar WorkClusterData)++newWorkCluster :: IO WorkCluster+newWorkCluster = liftM WorkCluster . newMVar =<< (WorkClusterData <$>+    newIORef 0 <*> newIORef 0 <*> newIORef 0 <*> newIORef 0 <*> newIORef PSQ.empty <*> (newIORef =<< simpleTaskPool) <*>+    newIORef (error "newWorkCluster: please call 'replaceWorkOperation' at least once") <*>+    newIORef Map.empty)++startWork :: WorkCluster -> WorkRequest -> WorkPrio -> IO (TaskHandle WorkPrio WorkResult)+startWork (WorkCluster wc_data_var) work_request prio = liftM snd $ withMVar wc_data_var $ \wc_data ->+    do when (prio == Now) $ +           do modifyIORef (wc_recent_request_count wc_data) succ+              modifyIORef (wc_request_counter wc_data) succ+              c <- readIORef $ wc_request_counter wc_data+              modifyIORef (wc_recent_requests wc_data) $ PSQ.insertWith max work_request c+              queue_max_size <- readIORef $ wc_queue_max_size wc_data+              modifyIORef (wc_recent_requests wc_data)   $ \psq -> if PSQ.size psq >= queue_max_size then PSQ.deleteMin psq else psq+       handles <- readIORef (wc_task_handles wc_data)+       case (Map.lookup work_request handles) of+           Just handle -> +               do reprioritize handle $ min prio+                  return (wc_data, handle)+           Nothing -> +               do pool <- readIORef $ wc_task_pool wc_data+                  op <- readIORef $ wc_request_operation wc_data+                  result <- dispatch (schedule pool prio) $ op work_request+                  modifyIORef (wc_task_handles wc_data) $ Map.insert work_request result+                  return (wc_data, result)+       +workRequest :: WorkCluster -> WorkRequest -> IO WorkResult+workRequest work_cluster work_request = getResult =<< startWork work_cluster work_request Now++replaceWorkOperation :: WorkCluster -> (WorkRequest -> IO WorkResult) -> IO ()+replaceWorkOperation (WorkCluster wc_data_var) op = +    do recent_requests <- liftM snd $ withMVar wc_data_var $ \wc_data ->+           do pool <- readIORef $ wc_task_pool wc_data+              startQueue pool+              modifyIORef (wc_task_handles wc_data) $ const Map.empty+              modifyIORef (wc_abort_counter wc_data) succ+              id $ do queue_max_size <- readIORef $ wc_queue_max_size wc_data -- grow/shrink the most-recently-used request queue according to actual usage+                      recent_request_count <- readIORef $ wc_recent_request_count wc_data+                      writeIORef (wc_recent_request_count wc_data) 0+                      writeIORef (wc_queue_max_size wc_data) $ max (queue_max_size - 1 :: Int) ((recent_request_count :: Int) + queue_size_bonus)+              key_abort_index <- readIORef $ wc_abort_counter wc_data+              writeIORef (wc_request_operation wc_data) $ \wo ->+                  do current_abort_index <- readIORef $ wc_abort_counter wc_data+                     case key_abort_index == current_abort_index of+                         False -> return $ error "replaceWorkOperation: operation aborted"+                         True -> op wo+              liftM ((,) wc_data . PSQ.keys) $ readIORef $ wc_recent_requests wc_data+       forM_ recent_requests $ \work_request -> startWork (WorkCluster wc_data_var) work_request Eventually+