diff --git a/data/scenarios/Fun/00-ORDER.txt b/data/scenarios/Fun/00-ORDER.txt index 162469def..c639ec5b9 100644 --- a/data/scenarios/Fun/00-ORDER.txt +++ b/data/scenarios/Fun/00-ORDER.txt @@ -1,3 +1,4 @@ GoL.yaml logo-burst.yaml -horton.yaml \ No newline at end of file +horton.yaml +snake.yaml \ No newline at end of file diff --git a/data/scenarios/Fun/_snake/snake.sw b/data/scenarios/Fun/_snake/snake.sw new file mode 100644 index 000000000..3266533b4 --- /dev/null +++ b/data/scenarios/Fun/_snake/snake.sw @@ -0,0 +1,160 @@ +/** +Uses a string to maintain a queue of coordinates. +*/ + +def coordsToString : (int * int) -> text = \coords. + format (fst coords) ++ "," ++ format (snd coords) + end + +def indexOfRec : int -> text -> text -> (unit + int) = \pos. \inputString. \targetChar. + if (pos >= chars inputString) { + inL () + } { + if (toChar (charAt pos inputString) == targetChar) { + inR pos + } { + indexOfRec (pos + 1) inputString targetChar + } + } + end + +def indexOf : text -> text -> (unit + int) = + indexOfRec 0 + end + +// Drops the first character of a string +def strTail : text -> text = \inputString. + snd $ split 1 inputString + end + +def splitOnFirstChar : text -> text -> (text * text) = \inputString. \splitChar. + case (indexOf inputString splitChar) (\_. + // Did not find the split character, so return the original string + (inputString, "") + ) (\foundIdx. + let parts = split foundIdx inputString in + (fst parts, strTail $ snd parts) + ) + end + +def getDecimalCharValue = \inputString. \idx. + charAt idx inputString - charAt 0 "0" + end + +// Works from right to left +def parseDecimalRec : int -> text -> int = \charsRemaining. \inputString. + if (charsRemaining > 0) { + getDecimalCharValue inputString (charsRemaining - 1) + 10 * parseDecimalRec (charsRemaining - 1) inputString + } {0} + end + +def parseDecimal : text -> int = \inputString. + let isNegative = toChar (charAt 0 inputString) == "-" in + let negationMultiplier = if isNegative {-1} {1} in + let modifiedString = if isNegative {strTail inputString} {inputString} in + let stringLength = chars modifiedString in + negationMultiplier * parseDecimalRec stringLength modifiedString; + end + +// Comma (",") is the separator between abscissa and ordinate +def stringToCoords : text -> (int * int) = \coordsString. + let pair = splitOnFirstChar coordsString "," in + (parseDecimal $ fst pair, parseDecimal $ snd pair) + end + +// APPEND to string representation of a coordinate list +def snoc : (int * int) -> text -> text = \coords. \strList. + let delimiter = if (chars strList > 0) {";"} {""} in + strList ++ delimiter ++ coordsToString coords; + end + +// Extracts the first element and returns the shortened list +def pop : text -> (unit + ((int * int) * text)) = \strList. + if (chars strList > 0) { + let pair = splitOnFirstChar strList ";" in + inR (stringToCoords $ fst pair, snd pair) + } { + inL (); + } + end + +def getDir = \dest. + path (inL ()) (inL dest); + end; + +def doAtLoc = \currLoc. \targetLoc. \func. + teleport self targetLoc; + x <- func; + teleport self currLoc; + return x; + end; + +def moveTail = \tailList. + emptyHere <- isempty; + if emptyHere { + let maybeShifted = pop tailList in + case maybeShifted (\_. + // Nothing to pick up or replace + return tailList; + + ) (\newPair. + + let farthestTail = fst newPair in + let newInit = snd newPair in + newLoc <- whereami; + grabbedItem <- doAtLoc newLoc farthestTail grab; + place grabbedItem; + + return $ snoc newLoc newInit; + ); + } { + return tailList; + } + end; + +def moveOneStep = \tailList. + + // This robot will always be sitting atop the apple + r <- robotnamed "spawn"; + targetLoc <- as r {whereami}; + + maybeD <- getDir targetLoc; + case maybeD (\_. say "Dead!"; return "") (\d. + turn $ fst d; + newList <- moveTail tailList; + move; + return newList + ); + end + +// Invariant: No tail pieces shall ever be moved underneath the +// snake robot, unless an apple was just picked up. +def moveToApple = \tailList. + + myLoc <- whereami; + + appleHere <- ishere "apple"; + if appleHere { + + modifiedTailList <- try { + make "tail"; + swap "tail"; + return $ snoc myLoc tailList; + } { + grab; + return tailList; + }; + // Need to move here so that we get out of the way + // if the tail gets elongated + moveOneStep modifiedTailList; + } { + moveOneStep tailList; + } + end; + +def go = \tailList. + newList <- instant $ moveToApple tailList; + go newList; + end; + +go ""; diff --git a/data/scenarios/Fun/_snake/spawn.sw b/data/scenarios/Fun/_snake/spawn.sw new file mode 100644 index 000000000..d4dd165b8 --- /dev/null +++ b/data/scenarios/Fun/_snake/spawn.sw @@ -0,0 +1,35 @@ +def waitForConsumption = + watch down; + wait 2000; + appleHere <- ishere "apple"; + if appleHere { + waitForConsumption; + } {} + end; + +// TODO Ensure we don't spawn inside a coil of the tail +def placeAtOpenLocation = \range. + randX <- random range; + randY <- random range; + let x = range/2 - randX in + let y = range/2 - randY in + teleport self (x, y); + emptyHere <- isempty; + if emptyHere { + place "apple"; + } { + placeAtOpenLocation range; + } + end; + +def repeatedlyPlaceApple = \range. + placeAtOpenLocation range; + waitForConsumption; + end; + +def go = + instant $ repeatedlyPlaceApple 20; + go; + end; + +go; diff --git a/data/scenarios/Fun/snake.yaml b/data/scenarios/Fun/snake.yaml new file mode 100644 index 000000000..7fb47fc49 --- /dev/null +++ b/data/scenarios/Fun/snake.yaml @@ -0,0 +1,122 @@ +version: 1 +name: Snake +author: Karl Ostmo +seed: 1 +description: | + Watch the snake eat the apples +creative: false +objectives: + - goal: + - | + Eat and "digest" many apples + condition: | + r <- robotnamed "snake"; + as r { + appleCoreCount <- count "apple core"; + return $ appleCoreCount >= 40; + } +robots: + - name: base + dir: north + devices: + - ADT calculator + - branch predictor + - clock + - comparator + - compass + - dictionary + - grabber + - hourglass + - hearing aid + - keyboard + - lambda + - logger + - net + - scanner + - strange loop + - string + - treads + - wayfinder + - name: snake + dir: north + system: true + display: + attr: green + invisible: false + devices: + - rattle + program: | + run "scenarios/Fun/_snake/snake.sw" + - name: spawn + dir: north + system: true + display: + invisible: true + inventory: + - [100, apple] + program: | + run "scenarios/Fun/_snake/spawn.sw" +solution: | + noop +entities: + - name: wayfinder + display: + char: 'w' + description: + - | + Enables the `path` command + properties: [known, portable] + capabilities: [path] + - name: apple + display: + char: 'a' + attr: red + description: + - | + Tasty snack + properties: [known, portable] + - name: apple core + display: + char: 'I' + attr: wood + description: + - | + Remains of an eaten apple + properties: [known, portable] + - name: rattle + display: + char: 'r' + attr: green + description: + - | + Device unique to a snake + properties: [known] + - name: tail + display: + char: '@' + attr: green + description: + - | + Segment of snake's tail + properties: [known, unwalkable] +recipes: + - in: + - [3, apple] + out: + - [1, tail] + - [3, apple core] + required: + - [1, rattle] +known: [] +world: + dsl: | + {grass} + upperleft: [0, 0] + palette: + 'B': [grass, erase, base] + 'S': [grass, erase, snake] + 's': [grass, erase, spawn] + '.': [grass, erase] + map: | + B.. + s.S diff --git a/test/integration/Main.hs b/test/integration/Main.hs index a51c9cc5f..ea902feac 100644 --- a/test/integration/Main.hs +++ b/test/integration/Main.hs @@ -217,6 +217,10 @@ testScenarioSolutions rs ui = , testTutorialSolution Default "Tutorials/world101" , testTutorialSolution (Sec 5) "Tutorials/farming" ] + , testGroup + "Fun" + [ testSolution (Sec 10) "Fun/snake" + ] , testGroup "Challenges" [ testSolution Default "Challenges/chess_horse"