diff --git a/data/scenarios/Challenges/Ranching/00-ORDER.txt b/data/scenarios/Challenges/Ranching/00-ORDER.txt index f3f14650b..718f86884 100644 --- a/data/scenarios/Challenges/Ranching/00-ORDER.txt +++ b/data/scenarios/Challenges/Ranching/00-ORDER.txt @@ -3,3 +3,4 @@ capture.yaml powerset.yaml fishing.yaml gated-paddock.yaml +pied-piper.yaml diff --git a/data/scenarios/Challenges/Ranching/_pied-piper/rat.sw b/data/scenarios/Challenges/Ranching/_pied-piper/rat.sw new file mode 100644 index 000000000..18145aa6c --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_pied-piper/rat.sw @@ -0,0 +1,107 @@ +def moveWithMorbidity = + moldHere <- ishere "mold"; + if moldHere { + try { + // handle race conditions in which + // another robot grabs it first + m <- harvest; + let spores = "mold spores" in + if (m == spores) { + say $ "Yuck, " ++ spores ++ "! I'm outta here."; + selfdestruct; + } {}; + } {}; + } {}; + move; + end; + +def goFoodDir = \f. \r. + let d = fst r in + if (d == down) { + foodHere <- ishere "oats"; + if foodHere { + grab; return () + } {}; + f; + return () + } { + turn d; + + // An obstruction might arise after + // navigation direction is determined + // but before we move. + try { + moveWithMorbidity; + } {}; + f; + } + end; + +def goHomeDir = \f. \r. + let d = fst r in + if (d == down) { + return () + } { + turn d; + + // An obstruction might arise after + // navigation direction is determined + // but before we move. + try { + moveWithMorbidity; + } {}; + f; + } + end; + +def findGoodDirection = + isBlocked <- blocked; + if isBlocked { + turn left; + findGoodDirection; + } {}; + end; + +def moveUntilBlocked = + isBlocked <- blocked; + if isBlocked { + } { + moveWithMorbidity; + moveUntilBlocked; + }; + end; + +def pauseAtRandom = + r <- random 3; + if (r == 0) { + r2 <- random 12; + wait $ 6 + r2; + } {} + end; + +def returnHome = \homeLoc. + nextDir <- path (inL ()) (inL homeLoc); + case nextDir return $ goHomeDir $ returnHome homeLoc; + end; + +def pursueFood = \hadSensedFood. \homeLoc. + nextDir <- path (inR 5) (inR "oats"); + case nextDir (\_. if hadSensedFood {returnHome homeLoc} {return ()}) $ + goFoodDir $ pursueFood true homeLoc; + end; + +def doMovement = \startLoc. + findGoodDirection; + moveUntilBlocked; + pauseAtRandom; + pursueFood false startLoc; + end; + +/** Loop */ +def go = \startLoc. + doMovement startLoc; + go startLoc; + end; + +startLoc <- whereami; +go startLoc; diff --git a/data/scenarios/Challenges/Ranching/_pied-piper/solution.sw b/data/scenarios/Challenges/Ranching/_pied-piper/solution.sw new file mode 100644 index 000000000..dddd857ec --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_pied-piper/solution.sw @@ -0,0 +1,191 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + + +def intersperse = \n. \f2. \f1. if (n > 0) { + f1; + if (n > 1) { + f2; + } {}; + intersperse (n - 1) f2 f1; + } {}; + end; + +def uTurn = \d. + turn d; + move; + turn d; + end; + +/** +Starting at bottom right of field +*/ +def harvestField = \fieldWidth. + intersperse fieldWidth move harvest; + uTurn right; + intersperse fieldWidth move harvest; + uTurn left; + intersperse fieldWidth move harvest; + uTurn right; + intersperse fieldWidth move harvest; + end; + +def makeOatsCrumb = \spacing. + place "oats"; + doN spacing move; + end; + +def waitUntilRatDisappeared = + found <- scout east; + if found { + wait 1; + waitUntilRatDisappeared; + } {}; + end; + +/** +Place a trail of breadcrumbs, and return +one crumb short of the first crumb. +*/ +def makeOatsTrail = \spacing. \segments. + doN segments $ makeOatsCrumb spacing; + turn back; + doN (segments * spacing) move; + turn back; + end; + +def placeHorizontalTrail = \horizontalSpacing. + turn east; + doN (2*horizontalSpacing) move; + place "oats"; + turn back; + doN horizontalSpacing move; + place "oats"; + doN horizontalSpacing move; + turn left; + end; + +def waitForRatToPass = + + // Wait until the rat ate this crumb + watch down; + wait 2000; + waitUntilRatDisappeared; + end; + +def makeTrails = + let spacing = 4 in + let segments = 5 in + + wait 30; + placeHorizontalTrail 5; + makeOatsTrail spacing $ segments + 1; + end; + +def getKey = + turn east; + doN 15 move; + turn right; + move; + _k <- grab; + turn right; + doN 5 move; + turn right; + doN 6 move; + turn left; + doN 18 move; + turn left; + doN 20 move; + turn right; + doN 2 move; + use "unlocker" forward; + doN 6 move; + turn right; + doN 8 move; + turn left; + move; + turn right; + doN 6 move; + turn left; + move; + end; + +def placeMold = \moldItem. + turn east; + doN 11 move; + sow moldItem; + end; + +def go = + getKey; + harvestField 20; + + turn left; + doN 2 move; + turn left; + harvestField 20; + + + move; + turn right; + doN 14 move; + turn left; + + // Get the mold + doN 4 move; + turn left; + doN 3 move; + moldItem <- grab; + turn back; + doN 3 move; + + turn right; + doN 3 move; + turn left; + + // Head back to the house + doN 8 move; + turn left; + doN 8 move; + turn left; + + // Start laying trail + intersperse 5 (doN 4 move) $ place "oats"; + placeHorizontalTrail 5; + + waitForRatToPass; + + makeTrails; + waitForRatToPass; + + + makeOatsTrail 4 10; + placeHorizontalTrail 5; + waitForRatToPass; + + makeOatsTrail 4 12; + placeHorizontalTrail 5; + waitForRatToPass; + + turn east; + doN 2 move; + + placeHorizontalTrail 4; + makeOatsTrail 4 4; + waitForRatToPass; + + makeOatsTrail 4 6; + placeHorizontalTrail 4; + + waitForRatToPass; + makeOatsTrail 4 10; + placeHorizontalTrail 4; + + + waitForRatToPass; + makeOatsTrail 4 12; + placeHorizontalTrail 4; + + placeMold moldItem; + end; + +go; diff --git a/data/scenarios/Challenges/Ranching/pied-piper.yaml b/data/scenarios/Challenges/Ranching/pied-piper.yaml new file mode 100644 index 000000000..10fbbb58f --- /dev/null +++ b/data/scenarios/Challenges/Ranching/pied-piper.yaml @@ -0,0 +1,461 @@ +version: 1 +name: Infestation +author: Karl Ostmo +description: | + Rid the village of pests. +creative: false +seed: 0 +attrs: + - name: clay + bg: "#313020" + - name: oats + fg: "#444444" + bg: "#F5DEB3" + - name: bridge + bg: "#806040" + - name: brick + bg: "#880000" + - name: mold + fg: "#406020" + bg: "#403020" +terrains: + - name: bridge + attr: bridge + description: | + Wooden raised path + - name: clay + attr: clay + description: | + Damp, sandy soil +robots: + - name: base + loc: + subworld: overworld + loc: [-1, 8] + dir: south + devices: + - ADT calculator + - binoculars + - branch predictor + - comparator + - compass + - counter + - dictionary + - grabber + - harvester + - lambda + - logger + - net + - rolex + - scanner + - seed spreader + - strange loop + - string + - treads + - unlocker + - tweezers + inventory: [] + walkable: + never: + - locked door + - name: rat + system: true + dir: west + display: + invisible: false + char: 'r' + devices: + - logger + program: | + run "scenarios/Challenges/Ranching/_pied-piper/rat.sw" + walkable: + never: + - ladder +objectives: + - teaser: Bait + goal: + - The village dwellings have been infested by `rat`{=robot}s. You are charged with clearing the infestation. + - Scurrying feet can be heard through cracks under each `locked door`{=entity}. Unfortunately you've not been entrusted with `key`{=entity}s to the residences. + - Perhaps there is something that would entice them out? North of the river you spy fields of `oats`{=entity} and a `storage silo`{=structure}. + - Get some `oats`{=entity}. + condition: | + as base { + has "oats"; + } + - teaser: Silo + id: silo + goal: + - Inspect the `storage silo`{=structure}. + condition: | + as base { + loc <- locateme; + return $ fst loc == "silo"; + } + - teaser: Moldy + prerequisite: silo + id: moldy + goal: + - A `rat`{=robot} will `selfdestruct` if it comes in contact with `mold`{=entity}. + - Obtain some `mold spores`{=entity}. + condition: | + as base { + has "mold spores"; + } + - teaser: Corral + prerequisite: moldy + goal: + - Dispatch the `rat`{=robot}s with `mold`{=entity}. + - Note that `mold spores`{=entity} will only spread when `sow`n in a dark, damp environment. + condition: | + try { + r <- robotNamed "rat"; + return false; + } {return true} + - teaser: Spelunker + prerequisite: silo + goal: + - Find a secret passage. + - To unlock a door, you need to `use "unlocker"`, which will consume a `key`{=entity}. + condition: | + as base { + loc <- locateme; + return $ fst loc == "tunnel"; + } +solution: | + run "scenarios/Challenges/Ranching/_pied-piper/solution.sw" +entities: + - name: seed spreader + display: + char: 's' + description: + - A device to distribute seeds and spores + properties: [known] + capabilities: [sow] + - name: brick + display: + char: '#' + attr: brick + description: + - Sturdy building material + properties: [known, unwalkable] + - name: fence + display: + char: '#' + attr: wood + description: + - Keeps interlopers away from the fields. + properties: [known, unwalkable] + - name: locked door + display: + char: 'D' + attr: wood + description: + - door + properties: [known] + - name: unlocked door + display: + char: '/' + attr: wood + description: + - Unlocked door + properties: [known] + - name: key + display: + char: 'k' + description: + - Used by an `unlocker`{=entity} to open a a `locked door`{=entity} + properties: [known] + - name: unlocker + display: + char: 'u' + description: + - Facilitates use of a `key`{=entity} + properties: [known] + - name: ladder + display: + char: 'H' + attr: wood + description: + - Ladder. Rodents cannot climb this. + properties: [known] + - name: chute + display: + char: 'C' + description: + - A trapdoor to the netherworld + properties: [known] + - name: wall + display: + char: 'W' + attr: wood + description: + - wall + properties: [known, unwalkable] + - name: oats + display: + attr: oats + char: 't' + description: + - Grain + properties: [known, pickable, growable] + growth: + duration: [40, 80] + biomes: [dirt] + - name: mold + display: + attr: mold + char: 'm' + description: + - Spreads in dark, damp environments. + - Toxic to `rat`{=robot}s. + - Yields `mold spores`{=entity} when `harvest`ed or `grab`bed. + properties: [known, pickable, growable] + yields: mold spores + growth: + duration: [15, 30] + spread: + radius: 2 + density: 0.3 + biomes: [clay] + - name: mold spores + display: + attr: mold + char: '*' + description: + - Grows into `mold`{=entity} in dark, damp environments. + - In spore form, has no effect on `rat`{=robot}s. + properties: [known, pickable, growable] + growth: + duration: [15, 30] + mature: mold + spread: + radius: 2 + density: 0.3 + biomes: [clay] +known: [water, boulder] +structures: + - name: storage silo + recognize: + - north + structure: + mask: '.' + palette: + 'x': [stone, brick] + '=': [stone] + 'c': + cell: [stone, chute] + waypoint: + name: chute_entrance + 'a': [blank, oats] + 'H': + cell: [dirt, ladder] + waypoint: + name: well_top + map: | + ......xxxxxH. + .....x===aax. + ...xx===aaaax + ....c==aaaaax + ...xx=aaaaaax + .....xaaaaax. + ......xxxxx.. + - name: field + structure: + palette: + 'a': [dirt, oats] + map: | + aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaa +subworlds: + - name: silo + mask: '+' + palette: + '.': [clay] + 's': [stone, boulder] + 'k': [clay, key] + 'w': + cell: [clay] + waypoint: + name: chute_exit + 'H': + cell: [stone, ladder] + waypoint: + name: well_bottom + portals: + - entrance: well_bottom + exitInfo: + exit: well_top + subworldName: overworld + upperleft: [-1, 1] + map: | + +++++sHsss+++++ + +++ss.....ss+++ + ++s.........s++ + +s...........s+ + +s.....w.....s+ + +s.........k.s+ + ++s.........s++ + +++ss.....ss+++ + +++++sssss+++++ + - name: tunnel + mask: '+' + palette: + '.': [dirt] + 'x': [stone, boulder] + 'm': [dirt, mold] + 'H': + cell: [dirt, ladder] + waypoint: + name: underground_field_ladder + 'I': + cell: [dirt, ladder] + waypoint: + name: underground_house_ladder + portals: + - entrance: underground_field_ladder + exitInfo: + exit: field_ladder + subworldName: overworld + consistent: true + - entrance: underground_house_ladder + exitInfo: + exit: house_ladder + subworldName: overworld + consistent: true + upperleft: [-1, 1] + map: | + ++xHx+xx+ + ++x.xx.mx + ++x.xxm.x + ++x.xxx.x + ++x.....x + +++x.xxxx + +++x.x+++ + +++x.x+++ + +++xIx+++ +recipes: + - in: + - [1, locked door] + - [1, key] + out: + - [1, unlocked door] + required: + - [1, unlocker] +world: + name: overworld + structures: + - name: house ladder + structure: + palette: + 'w': [stone, wall] + 'H': + cell: [dirt, ladder] + waypoint: + name: house_ladder + map: | + www + wHw + - name: field ladder + structure: + palette: + 'H': + cell: [dirt, ladder] + waypoint: + name: field_ladder + map: H + - name: house + structure: + palette: + 'd': [stone, locked door] + 'x': [stone, wall] + '.': [stone] + 'r': [stone, erase, rat] + map: | + xxxxxxxxx + x.......x + d.......x + x.......x + x....r..x + x.......x + xxxxxxxxx + - name: street pair + structure: + placements: + - src: house + offset: [0, 0] + orient: + up: south + - src: house + offset: [16, 0] + orient: + flip: true + map: "" + - name: block + structure: + placements: + - src: street pair + offset: [0, 0] + orient: + flip: true + - src: street pair + offset: [0, 8] + map: "" + placements: + - src: block + offset: [-10, -14] + - src: block + offset: [-10, -36] + - src: storage silo + offset: [8, 9] + - src: field + offset: [-28, 12] + - src: field + offset: [-28, 7] + - src: field + offset: [-52, 12] + - src: field + offset: [-52, 7] + - src: field ladder + offset: [-8, 2] + - src: house ladder + offset: [-8, -5] + upperleft: [-2, 2] + portals: + - entrance: well_top + exitInfo: + exit: well_bottom + subworldName: silo + - entrance: chute_entrance + exitInfo: + exit: chute_exit + subworldName: silo + - entrance: field_ladder + exitInfo: + exit: underground_field_ladder + subworldName: tunnel + - entrance: house_ladder + exitInfo: + exit: underground_house_ladder + subworldName: tunnel + dsl: | + overlay + [ {grass} + , mask (x == -7 && y >= 3 || y == 3 && x <= -7) {fence} + , mask (y > -2 && y < 2) {water} + , mask (x > -3 && x < 3) {stone} + , mask (y > -25 && y < -19) {stone} + ] + palette: + 'b': [bridge, erase] + 'M': + cell: [bridge, erase] + waypoint: + name: bridge_top + map: | + Mbbbb + bbbbb + bbbbb + bbbbb + bbbbb diff --git a/scripts/gen/commands-wiki.sh b/scripts/gen/commands-wiki.sh new file mode 100755 index 000000000..b0b566981 --- /dev/null +++ b/scripts/gen/commands-wiki.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e + +cd $(git rev-parse --show-toplevel) + +cabal run -j -O0 -- swarm:swarm-docs cheatsheet --commands diff --git a/src/swarm-scenario/Swarm/Game/Scenario/Topography/Navigation/Portal.hs b/src/swarm-scenario/Swarm/Game/Scenario/Topography/Navigation/Portal.hs index 1138529c2..b9f192c18 100644 --- a/src/swarm-scenario/Swarm/Game/Scenario/Topography/Navigation/Portal.hs +++ b/src/swarm-scenario/Swarm/Game/Scenario/Topography/Navigation/Portal.hs @@ -28,6 +28,7 @@ import Data.List.NonEmpty (NonEmpty ((:|))) import Data.List.NonEmpty qualified as NE import Data.Map (Map) import Data.Map qualified as M +import Data.Map.NonEmpty qualified as NEM import Data.Maybe (fromMaybe, listToMaybe) import Data.Text qualified as T import Data.Tuple (swap) @@ -37,7 +38,7 @@ import Swarm.Game.Location import Swarm.Game.Scenario.Topography.Navigation.Waypoint import Swarm.Game.Universe import Swarm.Language.Syntax.Direction -import Swarm.Util (allEqual, binTuples, both, failT, quote, showT) +import Swarm.Util (allEqual, binTuples, both, commaList, failT, quote, showT) type WaypointMap = M.Map WaypointName (NonEmpty Location) @@ -239,12 +240,28 @@ ensureSpatialConsistency :: [(Cosmic Location, AnnotatedDestination Location)] -> m () ensureSpatialConsistency xs = - unless (null nonUniform) $ + forM_ (NEM.nonEmptyMap nonUniform) $ \nonUniformMap -> failT [ "Non-uniform portal distances:" - , showT nonUniform + , renderNonUniformPairs nonUniformMap ] where + renderNonUniformPairs nem = + commaList $ NE.toList $ renderPair <$> NEM.toList nem + where + renderPair (k, v) = + T.unwords + [ renderKey k <> ":" + , commaList $ NE.toList $ showT <$> v + ] + renderKey (sw1, sw2) = + T.unwords + [ "Between subworlds" + , renderQuotedWorldName sw1 + , "and" + , renderQuotedWorldName sw2 + ] + consistentPairs :: [(Cosmic Location, Cosmic Location)] consistentPairs = map (fmap destination) $ filter (enforceConsistency . snd) xs diff --git a/src/swarm-tui/Swarm/TUI/View/Attribute/Attr.hs b/src/swarm-tui/Swarm/TUI/View/Attribute/Attr.hs index 23e24528c..e35e2ba46 100644 --- a/src/swarm-tui/Swarm/TUI/View/Attribute/Attr.hs +++ b/src/swarm-tui/Swarm/TUI/View/Attribute/Attr.hs @@ -36,6 +36,7 @@ module Swarm.TUI.View.Attribute.Attr ( cyanAttr, lightCyanAttr, yellowAttr, + beigeAttr, blueAttr, greenAttr, redAttr, @@ -115,6 +116,7 @@ swarmAttrMap = , (greenAttr, fg V.green) , (blueAttr, fg V.blue) , (yellowAttr, fg V.yellow) + , (beigeAttr, fg (V.rgbColor @Int 238 217 196)) , (cyanAttr, fg V.cyan) , (lightCyanAttr, fg (V.rgbColor @Int 200 255 255)) , (magentaAttr, fg V.magenta) @@ -183,11 +185,12 @@ customEditFocusedAttr :: AttrName customEditFocusedAttr = attrName "custom" <> E.editFocusedAttr -- | Some basic colors used in TUI. -redAttr, greenAttr, blueAttr, yellowAttr, cyanAttr, lightCyanAttr, magentaAttr, grayAttr :: AttrName +redAttr, greenAttr, blueAttr, yellowAttr, beigeAttr, cyanAttr, lightCyanAttr, magentaAttr, grayAttr :: AttrName redAttr = attrName "red" greenAttr = attrName "green" blueAttr = attrName "blue" yellowAttr = attrName "yellow" +beigeAttr = attrName "beige" cyanAttr = attrName "cyan" lightCyanAttr = attrName "lightCyan" magentaAttr = attrName "magenta" diff --git a/src/swarm-tui/Swarm/TUI/View/Util.hs b/src/swarm-tui/Swarm/TUI/View/Util.hs index bb1aa384c..b9314b6c8 100644 --- a/src/swarm-tui/Swarm/TUI/View/Util.hs +++ b/src/swarm-tui/Swarm/TUI/View/Util.hs @@ -151,6 +151,7 @@ drawMarkdown d = do "entity" -> greenAttr "structure" -> redAttr "tag" -> yellowAttr + "robot" -> beigeAttr "type" -> magentaAttr _snippet -> highlightAttr -- same as plain code diff --git a/swarm.cabal b/swarm.cabal index b26427638..fcdca6e6a 100644 --- a/swarm.cabal +++ b/swarm.cabal @@ -571,6 +571,7 @@ library swarm-scenario filepath, fused-effects, hashable, + nonempty-containers, hsnoise, lens, linear, diff --git a/test/integration/Main.hs b/test/integration/Main.hs index f425aebd6..73525360e 100644 --- a/test/integration/Main.hs +++ b/test/integration/Main.hs @@ -263,6 +263,7 @@ testScenarioSolutions rs ui key = , testSolution (Sec 20) "Challenges/Ranching/powerset" , testSolution (Sec 10) "Challenges/Ranching/fishing" , testSolution (Sec 30) "Challenges/Ranching/gated-paddock" + , testSolution (Sec 30) "Challenges/Ranching/pied-piper" ] , testGroup "Sokoban"