From 8eb229672dbe8a831e24cd9e000a78cf5a04908c Mon Sep 17 00:00:00 2001 From: Karl Ostmo Date: Sat, 5 Nov 2022 14:43:50 -0700 Subject: [PATCH] New ranching scenario --- data/scenarios/Challenges/00-ORDER.txt | 1 + .../Challenges/Ranching/00-ORDER.txt | 1 + .../Ranching/_gated-paddock/bridged-gap.sw | 115 +++++ .../_gated-paddock/enclosure-checking.sw | 234 +++++++++ .../_gated-paddock/fence-construction.sw | 209 ++++++++ .../_gated-paddock/meandering-sheep.sw | 141 +++++ .../_gated-paddock/update-and-test.sh | 10 + .../Challenges/Ranching/gated-paddock.yaml | 481 ++++++++++++++++++ test/integration/Main.hs | 4 + 9 files changed, 1196 insertions(+) create mode 100644 data/scenarios/Challenges/Ranching/00-ORDER.txt create mode 100644 data/scenarios/Challenges/Ranching/_gated-paddock/bridged-gap.sw create mode 100644 data/scenarios/Challenges/Ranching/_gated-paddock/enclosure-checking.sw create mode 100644 data/scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw create mode 100644 data/scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw create mode 100755 data/scenarios/Challenges/Ranching/_gated-paddock/update-and-test.sh create mode 100644 data/scenarios/Challenges/Ranching/gated-paddock.yaml diff --git a/data/scenarios/Challenges/00-ORDER.txt b/data/scenarios/Challenges/00-ORDER.txt index 5dd1d8a913..b662ac0ffa 100644 --- a/data/scenarios/Challenges/00-ORDER.txt +++ b/data/scenarios/Challenges/00-ORDER.txt @@ -3,3 +3,4 @@ teleport.yaml 2048.yaml hanoi.yaml Mazes +Ranching diff --git a/data/scenarios/Challenges/Ranching/00-ORDER.txt b/data/scenarios/Challenges/Ranching/00-ORDER.txt new file mode 100644 index 0000000000..87ae54846c --- /dev/null +++ b/data/scenarios/Challenges/Ranching/00-ORDER.txt @@ -0,0 +1 @@ +gated-paddock.yaml diff --git a/data/scenarios/Challenges/Ranching/_gated-paddock/bridged-gap.sw b/data/scenarios/Challenges/Ranching/_gated-paddock/bridged-gap.sw new file mode 100644 index 0000000000..211e2d771e --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_gated-paddock/bridged-gap.sw @@ -0,0 +1,115 @@ +def isFenced = s <- scan forward; return (case s (\_. false) (\x. x == "gate")); end; +def isBlockedOrFenced = b <- blocked; f <- isFenced; return (b || f); end; +def boolToInt = \b. if (b) {return 1} {return 0}; end; + +/** If one is standing on a fence, that means the fence has just been placed, +because it is not possible to move onto a fence that already exists. +We then inspect the 8 surrounding tiles. If at least two of them are "blocked" +or are a "gate", then we know that we have just closed a gap. +It may not have been that final gap, but it's a good time to run the full +enclosure test. + +Invoke as follows: + + countAdjacentBlockagesBroken 4 + +FIXME: This has VERY strange behavior. +The values of 'recVal' and 'total' are often inconsistent. +*/ +def countAdjacentBlockagesBroken = \i. + if (i > 0) { + turn left; + say "======"; + say $ format i; + say "------"; + amBlocked <- isBlockedOrFenced; + + say $ "amBlocked: " ++ format amBlocked; + + val <- boolToInt amBlocked; + say $ "val:" ++ format val; + recVal <- countAdjacentBlockagesBroken (i - 1); + + say $ "recVal: " ++ format recVal; + + let total = val + recVal in + say $ "total: " ++ format total; + return total; + } { + return 0; + }; + end; + + +def countAdjacentBlockages = + + turn left; + b1 <- isBlockedOrFenced; + c1 <- boolToInt b1; + + turn left; + b2 <- isBlockedOrFenced; + c2 <- boolToInt b2; + + turn left; + b3 <- isBlockedOrFenced; + c3 <- boolToInt b3; + + turn left; + b4 <- isBlockedOrFenced; + c4 <- boolToInt b4; + + return $ c1 + c2 + c3 + c4; + end; + +// Step forward, observing left and right. +def observeLeftAndRight = + move; + turn left; + amBlockedLeft <- isBlockedOrFenced; + val1 <- boolToInt amBlockedLeft; + + turn back; + amBlockedRight <- isBlockedOrFenced; + val2 <- boolToInt amBlockedRight; + + turn right; + move; + return $ val1 + val2; + end; + + +/** If the four cardinal directions have at most +one blockage, then there will exist an orientation +where both that direction and its opposite direction +are clear. +So we can step that direction, check to the left and +right of us, then step in the opposite direction +and do the same. This allows us to check the 4 +blocks that touch the corners of the center block. +*/ +def countDiagonalBlockages = + // First, orient to the clear front-to-back path + amBlocked <- isBlockedOrFenced; + if amBlocked {turn left;} {}; + + // Second, step to both sides + fwdCount <- observeLeftAndRight; + backCount <- observeLeftAndRight; + return $ fwdCount + backCount; + end; + +def isStandingOnBridge = + onFence <- ishere "fence"; + if (onFence) { + adjCount <- countAdjacentBlockages; + if (adjCount > 1) { + return true; + } { + diagCount <- countDiagonalBlockages; + return $ (adjCount + diagCount) > 1; + }; + } {return false}; + end; + +isStandingOnBridge; \ No newline at end of file diff --git a/data/scenarios/Challenges/Ranching/_gated-paddock/enclosure-checking.sw b/data/scenarios/Challenges/Ranching/_gated-paddock/enclosure-checking.sw new file mode 100644 index 0000000000..cdbf884a9e --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_gated-paddock/enclosure-checking.sw @@ -0,0 +1,234 @@ +// Algorithm: +// ---------- +// Maintain current direction until a wall is encountered. +// Then enter "wall-following mode". +// This mode presumes the wall is not a loop. +// Wall-following mode exploits recursion to keep track of how many left turns were made +// and then unwinds them again by ensuring each is paired with a right turn. +// Once the recursion is fully unwound, the robot proceeds along its original direction +// (though it may now be laterally displaced). +// +// (If it was a loop, then an "oriented breadcrumb" would need to be left. +// The breadcrumb is oriented in case a single-width passage is backtracked +// along the opposite wall.) + +/** A "gate" is walkable, so we need to supplement the "blocked" check with this function. +Since fences are "unwalkable", they do not need to be mentioned in this function. +*/ +def isFenced = + s <- scan forward; + return ( + case s + (\_. false) + (\x. x == "gate") + ); + end; + +def isBlockedOrFenced = + b <- blocked; + f <- isFenced; + return (b || f); + end; + +// Returns true if we've already placed two +// breadcrumbs on a given tile, false otherwise. +def leaveBreadcrumbs = + + let bc1 = "fresh breadcrumb" in + let bc2 = "treaded breadcrumb" in + + wasTraversedOnce <- ishere bc1; + if wasTraversedOnce { + _crumb <- grab; + make bc2; + place bc2; + return false; + } { + wasTraversedTwice <- ishere bc2; + if wasTraversedTwice { + return true; + } { + // Make sure nothing's in the way before we place + // our breadcrumb: + x <- scan down; + case x return (\y. + // If we're on a water tile, get rid of + // it with our special "drilling" recipe + if (y == "water") { + drill down; + // Nothing will remain on the ground. + // after making the "steam" via + // the drilling recipe. + } { + grab; + return (); + }; + ); + + make bc1; + place bc1; + return false; + }; + }; + end; + +def goForwardToPatrol = \wasBlocked. + b <- isBlockedOrFenced; + if b { + turn left; + goForwardToPatrol true; + turn right; + goForwardToPatrol false; + } { + if wasBlocked { + isLoop <- leaveBreadcrumbs; + if isLoop { + fail "loop"; + } {}; + } {}; + move; + }; + end; + +/** +There should only be one place in the +code where an exception is thrown: that is, +if a treaded breadcrumb is encountered. +*/ +def checkIsEnclosedInner = + try { + goForwardToPatrol false; + // Water is the outer boundary + hasWater <- ishere "water"; + if hasWater { + return false; + } { + checkIsEnclosedInner; + }; + } { + return true; + }; + end; + +def checkIsEnclosed = + + // The "evaporator" drill is used + // to clear water tiles. + let specialDrill = "evaporator" in + create specialDrill; + install self specialDrill; + + // NOTE: System robots can walk on water + // so we only need this if we want to + // demo the algorithm with a player robot. +// create "boat"; +// install self "boat"; + + checkIsEnclosedInner; + end; + +def boolToInt = \b. if (b) {return 1} {return 0}; end; + +def countAdjacentBlockages = + + turn left; + b1 <- isBlockedOrFenced; + c1 <- boolToInt b1; + + turn left; + b2 <- isBlockedOrFenced; + c2 <- boolToInt b2; + + turn left; + b3 <- isBlockedOrFenced; + c3 <- boolToInt b3; + + turn left; + b4 <- isBlockedOrFenced; + c4 <- boolToInt b4; + + return $ c1 + c2 + c3 + c4; + end; + +// Step forward, observing left and right. +def observeLeftAndRight = + move; + turn left; + amBlockedLeft <- isBlockedOrFenced; + val1 <- boolToInt amBlockedLeft; + + turn back; + amBlockedRight <- isBlockedOrFenced; + val2 <- boolToInt amBlockedRight; + + turn right; + move; + return $ val1 + val2; + end; + + +/** If the four cardinal directions have at most +one blockage, then there will exist an orientation +where both that direction and its opposite direction +are clear. +So we can step that direction, check to the left and +right of us, then step in the opposite direction +and do the same. This allows us to check the 4 +blocks that touch the corners of the center block. +*/ +def countDiagonalBlockages = + // First, orient to the clear front-to-back path + amBlocked <- isBlockedOrFenced; + if amBlocked {turn left;} {}; + + // Second, step to both sides + fwdCount <- observeLeftAndRight; + backCount <- observeLeftAndRight; + return $ fwdCount + backCount; + end; + +def isStandingOnBridge = + onFence <- ishere "fence"; + onGate <- ishere "gate"; + if (onFence || onGate) { + adjCount <- countAdjacentBlockages; + if (adjCount > 1) { + return true; + } { + diagCount <- countDiagonalBlockages; + return $ (adjCount + diagCount) > 1; + }; + } {return false}; + end; + + +/** +There are 3 sheep. +They have indices 1, 2, 3. +(The base has index 0). +*/ +def allSheep = \predicateCmd. \i. + + if (i > 0) { + r <- robotnumbered i; + didSucceed <- as r {predicateCmd}; + if didSucceed { + allSheep predicateCmd $ i - 1; + } { + return false; + }; + } { + return true; + } + end; + + +justFilledGap <- as base { + isStandingOnBridge; +}; + +if (justFilledGap) { + allSheep checkIsEnclosed 3; +} { + return false; +} \ No newline at end of file diff --git a/data/scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw b/data/scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw new file mode 100644 index 0000000000..60894f3ca5 --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw @@ -0,0 +1,209 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +def processTree = + grab; + fenceCount <- count "fence"; + if (fenceCount < 90) { + make "log"; + make "board"; + doN 2 $ make "fence"; + } {}; + end; + +def makeHarvester = + doN 3 $ make "log"; + doN 3 $ make "board"; + make "box"; + doN 2 $ make "wooden gear"; + make "harvester"; + install self "harvester"; + end; + +def grabTwoRows = + doN 7 (processTree; move); + processTree; + turn right; + move; + turn right; + + doN 7 (processTree; move); + processTree; + end; + + +def grabTrees = + doN 7 move; + turn right; + + grabTwoRows; + + turn left; + move; + turn left; + + grabTwoRows; + + turn left; + move; + turn left; + + grabTwoRows; + + end; + + +def harvestIfClover = + x <- scan down; + case x return (\y. + if (y == "clover") { + harvest; + return (); + } {}; + ); + end; + + +def buildFence = + doN 6 move; + turn right; + doN 4 (place "fence"; move); + turn left; + doN 30 (place "fence"; move); + turn left; + doN 15 (place "fence"; move); + turn left; + doN 30 (place "fence"; move); + turn left; + doN 10 (place "fence"; move); + make "gate"; + place "gate"; + turn right; + end; + +def gatherClover = + doN 8 move; + turn left; + doN 8 move; + turn left; + + doN 19 (harvestIfClover; move;); + turn right; + move; + turn right; + doN 19 (harvestIfClover; move;); + turn right; + end; + +def plantCloverColumn = \direction. \extraStep. + doN 6 ( + extraStep; + harvest; + move; + ); + + turn direction; + move; + turn direction; + move; + end; + +def plantCloverField = + doN 4 move; + + plantCloverColumn right $place "clover"; + plantCloverColumn left $ place "clover"; + plantCloverColumn right $ place "clover"; + plantCloverColumn left $ place "clover"; + plantCloverColumn right $ place "clover"; + end; + + +def harvestCloverField = + doN 5 move; + turn right; + doN 5 move; + turn right; + wait 200; + + plantCloverColumn right $ return (); + plantCloverColumn left $ return (); + plantCloverColumn right $ return (); + plantCloverColumn left $ return (); + plantCloverColumn right $ return (); + end; + + +def travelRow = \action. + doN 28 (action; move); + action; + end; + +def placeCloverRow = + travelRow $ place "clover"; + end; + + +def distributeCloverInPaddock = + turn left; + doN 4 move; + placeCloverRow; + turn left; + doN 2 move; + turn left; + placeCloverRow; + turn right; + doN 2 move; + turn right; + placeCloverRow; + turn left; + doN 2 move; + turn left; + placeCloverRow; + end; + + +def pickupWool = + x <- scan down; + case x return (\y. + if (y == "wool") { + grab; + return (); + } {}; + ); + end; + +def sweep2rows = + travelRow pickupWool; + turn right; + move; + turn right; + travelRow pickupWool; + turn left; + move; + turn left; + end; + + +def sweepAreaForWool = + doN 5 sweep2rows; + turn left; + doN 10 move; + turn right; + end; + +def collectWool = + wait 500; + + turn back; + doN 4 sweepAreaForWool; + end; + + +grabTrees; +makeHarvester; +buildFence; +gatherClover; +plantCloverField; +doN 3 harvestCloverField; +distributeCloverInPaddock; +collectWool; \ No newline at end of file diff --git a/data/scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw b/data/scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw new file mode 100644 index 0000000000..e7f3e55769 --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw @@ -0,0 +1,141 @@ +// A "sheep" that wanders around randomly. + +/** A "gate" is walkable, so we need to supplement the "blocked" check with this function. +Since fences are "unwalkable", they do not need to be mentioned in this function. +*/ +def isFenced = + s <- scan forward; + return ( + case s + (\_. false) + (\x. x == "gate") + ); + end; + +def isBlockedOrFenced = + b <- blocked; + f <- isFenced; + return (b || f); + end; + +def elif = \p.\t.\e. {if p t e} end; + + +def turnToClover = \direction. + + x <- scan direction; + case x (\_. return false;) (\y. + if (y == "clover") { + turn direction; + return true; + } { + return false; + }; + ); + end; + + +/** +If there is adjacent clover, +turn that direction. +*/ +def turnCloverDirection = + + foundN <- turnToClover north; + if (foundN) {return true} { + foundE <- turnToClover east; + if (foundE) {return true} { + foundS <- turnToClover south; + if (foundS) {return true} { + turnToClover west; + } + } + } + end; + +def decideDirection = + + let randdir : cmd dir = + d <- random 4; + return $ if (d == 0) { + north + } $ elif (d == 1) { + east + } $ elif (d == 2) { + south + } { + west + } + in + + cloverCount <- count "clover"; + if (cloverCount > 4) { + d <- randdir; + turn d; + } { + nearClover <- turnCloverDirection; + if (nearClover) {} { + d <- randdir; + turn d; + } + } + end; + +let forever : cmd unit -> cmd unit = \c. c ; forever c in +let repeat : int -> cmd unit -> cmd unit = + \n. \c. if (n == 0) {} {c ; repeat (n-1) c} in + + +forever ( + n <- random 30; + wait (30 + n); + + decideDirection; + + dist <- random 3; + repeat dist ( + + b <- isBlockedOrFenced; + if b {} { + move; + }; + + // Sheep can drown. + hasWater <- ishere "water"; + if hasWater { + say "whoops!"; + selfdestruct; + } {}; + + // Eat clover. + x <- scan down; + case x return (\y. + if (y == "clover") { + harvest; + say "yum!" + } {}; + ); + ); + r <- random 5; + if (r == 0) { say "baaa" } {}; + + hasClover <- has "clover"; + if (hasClover) { + r <- random 10; + if (r == 0) { + let item = "wool" in + hasWool <- has item; + if (hasWool) { + // Make sure nothing's in the way before we place + // our wool: + x <- scan down; + case x return (\_. + grab; + return (); + ); + + place item; + } {}; + } {}; + } {}; +) diff --git a/data/scenarios/Challenges/Ranching/_gated-paddock/update-and-test.sh b/data/scenarios/Challenges/Ranching/_gated-paddock/update-and-test.sh new file mode 100755 index 0000000000..5dfd1d2c81 --- /dev/null +++ b/data/scenarios/Challenges/Ranching/_gated-paddock/update-and-test.sh @@ -0,0 +1,10 @@ +#!/bin/bash -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +PARENT_DIR=$SCRIPT_DIR/.. + +SCENARIO_FILE=$PARENT_DIR/gated-paddock.yaml + +PROGRAM=$(cat $SCRIPT_DIR/enclosure-checking.sw | sed -e 's/[[:blank:]]\+$//') yq -i '.objectives[0].condition = strenv(PROGRAM) | .objectives[].condition style="literal"' $SCENARIO_FILE + +stack run -- --scenario $SCENARIO_FILE --run $SCRIPT_DIR/fence-construction.sw --cheat \ No newline at end of file diff --git a/data/scenarios/Challenges/Ranching/gated-paddock.yaml b/data/scenarios/Challenges/Ranching/gated-paddock.yaml new file mode 100644 index 0000000000..a208365fd6 --- /dev/null +++ b/data/scenarios/Challenges/Ranching/gated-paddock.yaml @@ -0,0 +1,481 @@ +version: 1 +name: Pastoral Island +author: Karl Ostmo +description: | + Start a wool industry from the local fauna. +creative: false +objectives: + - goal: + - | + You've homesteaded on a small island in the ocean. + It's time to gather resources to trade. + - | + You encounter some feral sheep (@). They slowly wander the island and eat grass. + Your mind wanders to textiles... + - | + First, paddock the sheep so they don't drown. + Make sure there are no gaps in the fence! + - | + Note that you can use the "drill" command (by way of the "post puller" tool) + to demolish a fence that has been "placed". + condition: |- + // Algorithm: + // ---------- + // Maintain current direction until a wall is encountered. + // Then enter "wall-following mode". + // This mode presumes the wall is not a loop. + // Wall-following mode exploits recursion to keep track of how many left turns were made + // and then unwinds them again by ensuring each is paired with a right turn. + // Once the recursion is fully unwound, the robot proceeds along its original direction + // (though it may now be laterally displaced). + // + // (If it was a loop, then an "oriented breadcrumb" would need to be left. + // The breadcrumb is oriented in case a single-width passage is backtracked + // along the opposite wall.) + + /** A "gate" is walkable, so we need to supplement the "blocked" check with this function. + Since fences are "unwalkable", they do not need to be mentioned in this function. + */ + def isFenced = + s <- scan forward; + return ( + case s + (\_. false) + (\x. x == "gate") + ); + end; + + def isBlockedOrFenced = + b <- blocked; + f <- isFenced; + return (b || f); + end; + + // Returns true if we've already placed two + // breadcrumbs on a given tile, false otherwise. + def leaveBreadcrumbs = + + let bc1 = "fresh breadcrumb" in + let bc2 = "treaded breadcrumb" in + + wasTraversedOnce <- ishere bc1; + if wasTraversedOnce { + _crumb <- grab; + make bc2; + place bc2; + return false; + } { + wasTraversedTwice <- ishere bc2; + if wasTraversedTwice { + return true; + } { + // Make sure nothing's in the way before we place + // our breadcrumb: + x <- scan down; + case x return (\y. + // If we're on a water tile, get rid of + // it with our special "drilling" recipe + if (y == "water") { + drill down; + // Nothing will remain on the ground. + // after making the "steam" via + // the drilling recipe. + } { + grab; + return (); + }; + ); + + make bc1; + place bc1; + return false; + }; + }; + end; + + def goForwardToPatrol = \wasBlocked. + b <- isBlockedOrFenced; + if b { + turn left; + goForwardToPatrol true; + turn right; + goForwardToPatrol false; + } { + if wasBlocked { + isLoop <- leaveBreadcrumbs; + if isLoop { + fail "loop"; + } {}; + } {}; + move; + }; + end; + + /** + There should only be one place in the + code where an exception is thrown: that is, + if a treaded breadcrumb is encountered. + */ + def checkIsEnclosedInner = + try { + goForwardToPatrol false; + // Water is the outer boundary + hasWater <- ishere "water"; + if hasWater { + return false; + } { + checkIsEnclosedInner; + }; + } { + return true; + }; + end; + + def checkIsEnclosed = + + // The "evaporator" drill is used + // to clear water tiles. + let specialDrill = "evaporator" in + create specialDrill; + install self specialDrill; + + // NOTE: System robots can walk on water + // so we only need this if we want to + // demo the algorithm with a player robot. + // create "boat"; + // install self "boat"; + + checkIsEnclosedInner; + end; + + def boolToInt = \b. if (b) {return 1} {return 0}; end; + + def countAdjacentBlockages = + + turn left; + b1 <- isBlockedOrFenced; + c1 <- boolToInt b1; + + turn left; + b2 <- isBlockedOrFenced; + c2 <- boolToInt b2; + + turn left; + b3 <- isBlockedOrFenced; + c3 <- boolToInt b3; + + turn left; + b4 <- isBlockedOrFenced; + c4 <- boolToInt b4; + + return $ c1 + c2 + c3 + c4; + end; + + // Step forward, observing left and right. + def observeLeftAndRight = + move; + turn left; + amBlockedLeft <- isBlockedOrFenced; + val1 <- boolToInt amBlockedLeft; + + turn back; + amBlockedRight <- isBlockedOrFenced; + val2 <- boolToInt amBlockedRight; + + turn right; + move; + return $ val1 + val2; + end; + + + /** If the four cardinal directions have at most + one blockage, then there will exist an orientation + where both that direction and its opposite direction + are clear. + So we can step that direction, check to the left and + right of us, then step in the opposite direction + and do the same. This allows us to check the 4 + blocks that touch the corners of the center block. + */ + def countDiagonalBlockages = + // First, orient to the clear front-to-back path + amBlocked <- isBlockedOrFenced; + if amBlocked {turn left;} {}; + + // Second, step to both sides + fwdCount <- observeLeftAndRight; + backCount <- observeLeftAndRight; + return $ fwdCount + backCount; + end; + + def isStandingOnBridge = + onFence <- ishere "fence"; + onGate <- ishere "gate"; + if (onFence || onGate) { + adjCount <- countAdjacentBlockages; + if (adjCount > 1) { + return true; + } { + diagCount <- countDiagonalBlockages; + return $ (adjCount + diagCount) > 1; + }; + } {return false}; + end; + + + /** + There are 3 sheep. + They have indices 1, 2, 3. + (The base has index 0). + */ + def allSheep = \predicateCmd. \i. + + if (i > 0) { + r <- robotnumbered i; + didSucceed <- as r {predicateCmd}; + if didSucceed { + allSheep predicateCmd $ i - 1; + } { + return false; + }; + } { + return true; + } + end; + + + justFilledGap <- as base { + isStandingOnBridge; + }; + + if (justFilledGap) { + allSheep checkIsEnclosed 3; + } { + return false; + } + - goal: + - | + Next, feed the sheep some clover. + The sheep will eat it if they walk over it, + and they will move toward it on an adjacent tile. + - | + You may want to install a gate in the fence + to give yourself easier access. + condition: |- + def anySheep = \predicateCmd. \i. + + if (i > 0) { + r <- robotnumbered i; + didSucceed <- as r {predicateCmd}; + if didSucceed { + return true; + } { + anySheep predicateCmd $ i - 1; + }; + } { + return false; + } + end; + + anySheep (has "clover") 3; + - goal: + - | + Contented, well-fed sheep may drop wool. + Grab some and you win! + condition: |- + as base {has "wool"} +robots: + - name: base + dir: [0, 1] + devices: + - treads + - scanner + - dictionary + - branch predictor + - strange loop + - clock + - ADT calculator + - comparator + - workbench + - grabber + - lambda + - logger + - hearing aid + - counter + - mirror + - post puller + inventory: + - [0, fence] + - [0, gate] + - [10, hinge] + - name: sheep + description: + - meandering livestock + display: + invisible: false + char: '@' + system: true + dir: [0, 1] + inventory: + - [2, wool] + program: | + run "scenarios/Challenges/Ranching/_gated-paddock/meandering-sheep.sw"; +entities: + - name: fence + display: + char: '#' + description: + - Keeps sheep in. And some other things out. + properties: [known, portable, unwalkable] + - name: post puller + display: + char: 'P' + attr: rock + capabilities: [drill] + description: + - Good for dismantling fences. + properties: [known] + - name: scrap wood + display: + char: '\' + description: + - Scrap wood. Good for nothing, really. + properties: [known] + - name: clover + display: + attr: flower + char: '%' + description: + - A tasty stack for fluffy ruminants. + properties: [portable, growable] + growth: [80, 100] + - name: gate + display: + char: '/' + attr: rock + description: + - A gate permits the player to pass through, but sheep cannot. + properties: [known] + - name: hinge + display: + char: 'U' + attr: rock + description: + - Facilitates swinging action. + properties: [known] + - name: cabin + display: + char: Π + attr: rock + description: + - Home sweet home. + properties: [known, unwalkable] + - name: pier + display: + char: H + attr: rock + description: + - Docking area for ships + properties: [known] + - name: fresh breadcrumb + display: + char: '.' + description: + - A marker that can be put down and found again. Used only by judge robot. + properties: [portable] + - name: treaded breadcrumb + display: + char: 'x' + description: + - A marker that can be put down and found again (for a second time). Used only by judge robot. + properties: [portable] + - name: evaporator + display: + char: 'E' + description: + - A tool that allows clearing a water tile. Used only by judge robot. + properties: [portable] + capabilities: [drill] + - name: wool + display: + char: 'ω' + description: + - Raw animal fiber. + properties: [portable] + - name: steam + display: + char: 'Z' + description: + - What's left after evaporating water. Used only by judge robot. + properties: [portable] +recipes: + - in: + - [2, board] + out: + - [1, fence] + - in: + - [0, fresh breadcrumb] + out: + - [1, fresh breadcrumb] + - in: + - [1, fresh breadcrumb] + out: + - [1, treaded breadcrumb] + - in: + - [1, fence] + out: + - [1, scrap wood] + required: + - [1, post puller] + - in: + - [1, water] + out: + - [1, steam] + required: + - [1, evaporator] + - in: + - [1, hinge] + - [1, fence] + out: + - [1, gate] +known: [mountain, tree, water] +seed: 0 +solution: | + run "scenarios/Challenges/Ranching/_gated-paddock/fence-construction.sw" +world: + default: [dirt, water] + palette: + 'B': [grass, null, base] + '.': [grass] + 't': [dirt, tree] + 'x': [stone, mountain] + 'c': [stone, cabin] + 's': [grass, null, sheep] + '%': [grass, clover, null] + 'H': [stone, pier, null] + '~': [dirt, water] + upperleft: [-34, 11] + map: |- + ~~~~.......~~~~~~~~~~~~~.......................~~~~~~ + ~~.............~~~~.......%.%%%%%%..%%%%%.%.......~~~ + ~........................%%%%%%%%%%%%%..%%%%.......~~ + ~...................................................~ + .............................x.xx........tttttttt...~ + .........................................tttttttt...~ + ..........................xxxxx..........tttttttt...~ + .........................................tttttttt...~ + ...........s.............................tttttttt.... + ....x....................................tttttttt.... + ...xx................................................ + ..xx.....................................B......c.... + ...x................................................. + ..............................s...................... + ..................................................... + ....................................................~ + ~..............s...................................~~ + ~~.......xxx...........x.........................~~~~ + ~~~............xx............x............~~~~H~~~~~~ + ~~~.....................................~~~~~~H~~~~~~ + ~~.....................................~~~~~~~H~~~~~~ + ~~..............................~~~~~~~~~~~~~~H~~~~~~ + ~~~~...................~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/integration/Main.hs b/test/integration/Main.hs index c01177fb98..b27b597800 100644 --- a/test/integration/Main.hs +++ b/test/integration/Main.hs @@ -165,6 +165,10 @@ testScenarioSolution _ci _em = , testSolution Default "Challenges/Mazes/invisible_maze" , testSolution Default "Challenges/Mazes/loopy_maze" ] + , testGroup + "Ranching" + [ testSolution (Sec 30) "Challenges/Ranching/gated-paddock" + ] ] , testGroup "Regression tests"