diff --git a/data/scenarios/Testing/1355-combustion.yaml b/data/scenarios/Testing/1355-combustion.yaml index 9c5b96261..f93fb13d7 100644 --- a/data/scenarios/Testing/1355-combustion.yaml +++ b/data/scenarios/Testing/1355-combustion.yaml @@ -21,6 +21,7 @@ solution: | ignite forward; turn right; move; move; move; + ignite left; turn right; move; ignite left; @@ -75,6 +76,18 @@ entities: duration: [8, 8] product: null properties: [known, pickable, combustible] + - name: slowfuse + display: + attr: silver + char: '~' + description: + - Reliably combustible, but burns more slowly + combustion: + ignition: 20 + duration: [64, 64] + delay: 63 + product: null + properties: [known, pickable, combustible] - name: dynamite display: attr: red @@ -103,6 +116,7 @@ world: 'b': [grass, board] 'i': [grass, cotton] 'F': [grass, fuse] + 'f': [grass, slowfuse] 'd': [grass, dynamite, judge] '.': [grass] upperleft: [0, 0] @@ -123,11 +137,11 @@ world: ......iiiiiiii........F...F...F...F.. iiiiiiiiiiiiiii..Ω.FFFF...FFFFF...d.. ......iiiiiiii....................... - iiiiiiiiiiiii.....qqqqqqqqqqqqqqqq... - ......iiiiii......qqqqqqqqqqqqqqqq... - iiiiiiiiiii.......qqqqqqqqqqqqqqqq... - ..................qqqqqqqqqqqqqqqq... - ..................qqqqqqqqqqqqqqqq... - ..................qqqqqqqqqqqqqqqq... - ..................qqqqqqqqqqqqqqqq... + iiiiiiiiiiiii..f..qqqqqqqqqqqqqqqq... + ......iiiiii...f..qqqqqqqqqqqqqqqq... + iiiiiiiiiii....f..qqqqqqqqqqqqqqqq... + ...............f..qqqqqqqqqqqqqqqq... + ..ffff...ffff..f..qqqqqqqqqqqqqqqq... + ..f..f...f..f..f..qqqqqqqqqqqqqqqq... + ..f..fffff..ffff..qqqqqqqqqqqqqqqq... ..................qqqqqqqqqqqqqqqq... diff --git a/data/schema/combustion.json b/data/schema/combustion.json index e3d428cd3..7cd1c6851 100644 --- a/data/schema/combustion.json +++ b/data/schema/combustion.json @@ -9,7 +9,7 @@ "ignition": { "default": 0.5, "type": "number", - "description": "Rate of ignition by a neighbor, per tick." + "description": "Rate of ignition by a neighbor. This is the rate parameter of a Poisson distribution, i.e. the expected number of times per tick that a combusting neighbor would tend to ignite this entity. Typically a number between 0 and 1, but it can be any nonnegative real number (default: 0.5)." }, "duration": { "type": "array", @@ -17,10 +17,15 @@ "$ref": "range.json", "description": "For combustible entities, a 2-tuple of integers specifying the minimum and maximum amount of time that the combustion shall persist." }, + "delay": { + "type": "number", + "default": 0.0, + "description": "Warmup delay, i.e. the number of ticks combustion must persist until this entity will potentially start igniting its neighbors (default: 0)." + }, "product": { "default": "ash", "type": ["string", "null"], - "description": "What entity, if any, is left over after combustion" + "description": "What entity, if any, is left over after combustion." } } } diff --git a/src/swarm-engine/Swarm/Game/Step/Combustion.hs b/src/swarm-engine/Swarm/Game/Step/Combustion.hs index 65a8d1e75..393f385c5 100644 --- a/src/swarm-engine/Swarm/Game/Step/Combustion.hs +++ b/src/swarm-engine/Swarm/Game/Step/Combustion.hs @@ -67,7 +67,11 @@ igniteCommand c d = do let selfCombustibility = (e ^. entityCombustion) ? defaultCombustibility createdAt <- getNow combustionDurationRand <- addCombustionBot e selfCombustibility createdAt loc - forM_ (getNeighborLocs loc) $ igniteNeighbor createdAt combustionDurationRand + let warmup = delay selfCombustibility + let neighborAffectDuration = max 0 (combustionDurationRand - warmup) + when (neighborAffectDuration > 0) $ + forM_ (getNeighborLocs loc) $ + igniteNeighbor createdAt warmup neighborAffectDuration where verb = "ignite" verbed = "ignited" @@ -117,7 +121,7 @@ addCombustionBot inputEntity combustibility ts loc = do ts return combustionDurationRand where - Combustibility _ durationRange maybeCombustionProduct = combustibility + Combustibility _ durationRange _ maybeCombustionProduct = combustibility -- | A system program for a "combustion robot", to burn an entity -- after it is ignited. @@ -141,7 +145,7 @@ addCombustionBot inputEntity combustibility ts loc = do -- cells. This would avoid polluting the logic of the currently burning cell -- with logic to manage probabilities of combustion propagation. combustionProgram :: Integer -> Combustibility -> TSyntax -combustionProgram combustionDuration (Combustibility _ _ maybeCombustionProduct) = +combustionProgram combustionDuration (Combustibility _ _ _ maybeCombustionProduct) = [tmQ| wait $int:combustionDuration; if ($int:invQuantity > 0) { @@ -156,18 +160,25 @@ combustionProgram combustionDuration (Combustibility _ _ maybeCombustionProduct) Nothing -> (0, "") Just p -> (1, p) --- | We treat the 'ignition' field in the 'Combustibility' record --- as a /rate/ in a Poisson distribution. --- Ignition of neighbors depends on that particular neighbor entity's --- combustion /rate/, but also on the duration --- that the current entity will burn. +-- | Possibly ignite a neighbor of a source entity that is combusting. +-- @creationTime@ is the time the source entity began to combust. +-- @warmup@ is the number of ticks of delay that the source entity +-- needs to burn before it will start affecting its neighbors; +-- @sourceDuration@ is the number of ticks that it will potentially +-- affect its neighbors. +-- +-- We treat the 'ignition' field in the 'Combustibility' record as a +-- /rate/ in a Poisson distribution. Ignition of neighbors depends +-- on that particular neighbor entity's combustion /rate/, but also +-- on the @sourceDuration@ time that the current entity will burn. igniteNeighbor :: Has (State GameState) sig m => TimeSpec -> Integer -> + Integer -> Cosmic Location -> m () -igniteNeighbor creationTime sourceDuration loc = do +igniteNeighbor creationTime warmup sourceDuration loc = do maybeEnt <- entityAt loc forM_ maybeEnt igniteEntity where @@ -177,7 +188,8 @@ igniteNeighbor creationTime sourceDuration loc = do when (probabilityOfIgnition >= threshold) $ do ignitionDelayRand <- uniform (0, 1) let ignitionDelay = - floor + (warmup +) + . floor . min (fromIntegral sourceDuration) . negate $ log ignitionDelayRand / rate diff --git a/src/swarm-scenario/Swarm/Game/Entity.hs b/src/swarm-scenario/Swarm/Game/Entity.hs index 56dac3c57..b99f08399 100644 --- a/src/swarm-scenario/Swarm/Game/Entity.hs +++ b/src/swarm-scenario/Swarm/Game/Entity.hs @@ -243,19 +243,30 @@ data Combustibility = Combustibility -- See . , duration :: (Integer, Integer) -- ^ min and max tick counts for combustion to persist + , delay :: Integer + -- ^ Delay until this entity may start igniting its neighbors. , product :: Maybe EntityName -- ^ what entity, if any, is left over after combustion } - deriving (Eq, Ord, Show, Read, Generic, Hashable, FromJSON, ToJSON) + deriving (Eq, Ord, Show, Read, Generic, Hashable, ToJSON) + +instance FromJSON Combustibility where + parseJSON = withObject "Combustibility" $ \v -> do + ignition <- v .: "ignition" + duration <- v .: "duration" + delay <- v .:? "delay" .!= 0 + product <- v .: "product" + pure Combustibility {..} -- | The default combustion specification for a combustible entity -- with no combustion specification: -- -- * ignition rate 0.5 -- * duration (100, 200) +-- * delay of 0 -- * product @ash@ defaultCombustibility :: Combustibility -defaultCombustibility = Combustibility 0.5 (100, 200) (Just "ash") +defaultCombustibility = Combustibility 0.5 (100, 200) 0 (Just "ash") ------------------------------------------------------------ -- Entity