From 1b9def626c562524507a6384f5a5bdc3754a5229 Mon Sep 17 00:00:00 2001 From: Karl Ostmo Date: Sat, 21 Jan 2023 13:40:55 -0800 Subject: [PATCH] Whack-a-mole scenario --- data/scenarios/Challenges/00-ORDER.txt | 1 + data/scenarios/Challenges/_gopher/gopher.sw | 146 ++++++++++++++++++ data/scenarios/Challenges/_gopher/solution.sw | 121 +++++++++++++++ data/scenarios/Challenges/gopher.yaml | 136 ++++++++++++++++ test/integration/Main.hs | 1 + 5 files changed, 405 insertions(+) create mode 100644 data/scenarios/Challenges/_gopher/gopher.sw create mode 100644 data/scenarios/Challenges/_gopher/solution.sw create mode 100644 data/scenarios/Challenges/gopher.yaml diff --git a/data/scenarios/Challenges/00-ORDER.txt b/data/scenarios/Challenges/00-ORDER.txt index aeae346f27..bc1192ca65 100644 --- a/data/scenarios/Challenges/00-ORDER.txt +++ b/data/scenarios/Challenges/00-ORDER.txt @@ -2,6 +2,7 @@ chess_horse.yaml teleport.yaml 2048.yaml word-search.yaml +gopher.yaml ice-cream.yaml hanoi.yaml bucket-brigade.yaml diff --git a/data/scenarios/Challenges/_gopher/gopher.sw b/data/scenarios/Challenges/_gopher/gopher.sw new file mode 100644 index 0000000000..d367c4ab8d --- /dev/null +++ b/data/scenarios/Challenges/_gopher/gopher.sw @@ -0,0 +1,146 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; +def abs = \n. if (n < 0) {-n} {n} end; + +def randSign = \x. + opposite <- random 2; + if (opposite == 1) { + return (-x); + } { + return x; + } + end; + +def converge = \dest. \currentLoc. + + let xDist = fst currentLoc - fst dest in + let yDist = snd currentLoc - snd dest in + + if (xDist < 0) { + turn east; + } { + if (xDist > 0) { + turn west; + } {}; + }; + doN (abs xDist) move; + + if (yDist < 0) { + turn north; + } { + if (yDist > 0) { + turn south; + } {}; + }; + doN (abs yDist) move; + end; + +/** +TODO: Randomly alternate between horizontal and vertical +movement. +*/ +def navigateTo = \destTuple. + loc <- whereami; + converge destTuple loc; + end; + +def arrive = \fieldWidth. \fieldHeight. + + let leadDist = 20 in + + newDestinationX <- random fieldWidth; + newDestinationYtemp <- random fieldHeight; + let newDestinationY = -newDestinationYtemp in + + offsetXrand <- random $ leadDist / 2; + + // The manhattan-distance of the offset + // must total some preset amount. + let offsetXunsigned = (leadDist / 2) + offsetXrand in + let offsetYunsigned = leadDist - offsetXunsigned in + + offsetX <- randSign offsetXunsigned; + offsetY <- randSign offsetYunsigned; + + let startX = newDestinationX + offsetX in + let startY = newDestinationY + offsetY in + + teleport self (startX, startY); + navigateTo (newDestinationX, newDestinationY); + turn down; + end; + +def getTauntStage = \startingAmount. \newCount. + if ((newCount * 5) / startingAmount < 1) { + return (0, "Hey, maybe we can work this out?"); + } { + if ((newCount * 5) / startingAmount < 2) { + return (1, "I didn't hear no bell!"); + } { + if ((newCount * 5) / startingAmount < 3) { + return (2, "Why don't you just give up?"); + } { + if ((newCount * 5) / startingAmount < 4) { + return (3, "Close one!"); + } { + if (newCount < startingAmount - 2) { + return (4, "OK, no more Mr. Nice Gopher!"); + } { + if (newCount < startingAmount - 1) { + return (5, "Bet you can't do that again!"); + } { + if (newCount < startingAmount) { + return (6, "Beginner's luck!"); + } { + return (7, "You'll never catch me!"); + }; + }; + }; + }; + }; + }; + }; + end; + +def waitWhileHere = \e. + here <- isHere e; + if here { + wait 1; + waitWhileHere e; + } {}; + end; + +def go = \lastTauntIndex. \startingAmount. \dropping. + newCount <- count dropping; + if (newCount > 0) { + tauntStage <- getTauntStage startingAmount newCount; + let tauntIndex = fst tauntStage in + if (tauntIndex != lastTauntIndex) { + say $ snd tauntStage; + } {}; + + appear "o"; + arrive 48 18; + + place dropping; + appear "G"; + waitWhileHere dropping; + go tauntIndex startingAmount dropping; + } { + say "Argh! I give up."; + + // Allow the player to salvage their robots + let reward = "toolkit" in + try { + place reward; + } { + swap reward; + return (); + }; + + selfdestruct; + }; + end; + +let dropping = "mound" in +startingAmount <- count dropping; +go (-1) startingAmount dropping; \ No newline at end of file diff --git a/data/scenarios/Challenges/_gopher/solution.sw b/data/scenarios/Challenges/_gopher/solution.sw new file mode 100644 index 0000000000..7e43ed9a22 --- /dev/null +++ b/data/scenarios/Challenges/_gopher/solution.sw @@ -0,0 +1,121 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +def makeSigned = \b. \x. + if b { + return (-x); + } { + return x; + } + end; + +def getDirection = \n. + if (n == 0) { + return forward; + } { + if (n == 1) { + return right; + } { + if (n == 2) { + return back; + } { + if (n == 3) { + return left; + } { + return down; + } + } + } + } + end; + +/** +Loops forever +*/ +def scanDirections = \n. + d <- getDirection n; + out <- scan d; + shouldContinue <- case out + (\_. return true) + (\x. if (x == "mound") { + drill d; + return true; + } { + // A "flower" shall serve as + // a semaphore to terminate the loop, + // so that the base can `salvage` us. + return $ x != "flower"; + }); + + if shouldContinue { + if (n > 0) { + scanDirections $ n - 1; + } { + scanDirections 4; + }; + } {}; + + end; + +def deploySensor = + _s <- build {scanDirections 0;}; + return (); + end; + +def isDivisibleBy = \dividend. \divisor. + (dividend / divisor) * divisor == dividend; + end; + +// stagger at every fifth cell +def deployRow = \offset. \cellCount. + if (isDivisibleBy (cellCount + offset) 5) { + deploySensor; + } {}; + if (cellCount > 1) { + move; + deployRow offset $ cellCount - 1; + } {}; + end; + +def isEven = \x. + isDivisibleBy x 2 + end; + +def deployGrid = \width. \height. + + if (height > 0) { + + let nowEven = isEven height in + + offsetVal <- makeSigned nowEven $ height * 2; + extraOffset <- if nowEven { + return (-1); + } { + return 0; + }; + deployRow (offsetVal + extraOffset) width; + + d <- if nowEven { + return right; + } { + return left; + }; + + turn d; + move; + turn d; + + deployGrid width $ height - 1; + } {}; + end; + +def recenter = \height. + turn left; + doN (height / 2) move; + end; + +def go = \width. \height. + deployGrid width height; + recenter height; + end; + +go 50 20; \ No newline at end of file diff --git a/data/scenarios/Challenges/gopher.yaml b/data/scenarios/Challenges/gopher.yaml new file mode 100644 index 0000000000..624987b9fd --- /dev/null +++ b/data/scenarios/Challenges/gopher.yaml @@ -0,0 +1,136 @@ +version: 1 +name: Gopher +author: Karl Ostmo +description: | + Dispatch a pesky gopher +creative: false +seed: 0 +objectives: + - goal: + - | + A gopher (G) is defiling your immaculate garden! + - | + He will burrow (o) underground awhile, then pop up + anywhere within the rectangular grassy region + to gloat atop his "mound" of dirt. + `drill` the "mound" to drive him + away. Eventually you should wear down his resolve! + condition: | + try { + robotnamed "gopher"; + return false; + } { + return true; + } +robots: + - name: base + loc: [-1, 1] + display: + attr: blue + dir: [1, 0] + devices: + - ADT calculator + - 3D printer + - branch predictor + - treads + - antenna + - clock + - comparator + - workbench + - grabber + - dictionary + - lambda + - logger + - welder + - hearing aid + - scanner + - strange loop + - drill + inventory: + - [200, solar panel] + - [200, strange loop] + - [200, scanner] + - [200, logger] + - [200, lambda] + - [200, dictionary] + - [200, grabber] + - [200, clock] + - [200, comparator] + - [200, ADT calculator] + - [200, swivel] + - [200, branch predictor] + - [200, drill] + - [200, flower] + - name: gopher + system: true + dir: [0, 1] + display: + char: 'G' + invisible: false + attr: 'wood' + inventory: + - [100, mound] + - [1, toolkit] + program: | + run "data/scenarios/Challenges/_gopher/gopher.sw" +entities: + - name: mound + display: + char: 'M' + attr: wood + description: + - An unsightly pile of dirt + properties: [known] + - name: swivel + display: + char: 's' + attr: gold + capabilities: [turn] + description: + - Allows a robot to "turn" but not "move". + properties: [known] + - name: uranium + display: + char: 'U' + attr: silver + description: + - Unearthed by industrious gophers. + properties: [known, portable] +recipes: + - in: + - [1, mound] + out: + - [1, uranium] + required: + - [1, drill] +solution: | + run "scenarios/Challenges/_gopher/solution.sw" +known: [] +world: + upperleft: [-1, 1] + offset: false + palette: + 'x': [dirt] + '.': [grass] + 'g': [grass, null, gopher] + map: | + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x................................................x + x...............................................gx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/test/integration/Main.hs b/test/integration/Main.hs index af8256bff5..3333bd2c6d 100644 --- a/test/integration/Main.hs +++ b/test/integration/Main.hs @@ -162,6 +162,7 @@ testScenarioSolution _ci _em = , testSolution (Sec 5) "Challenges/2048" , testSolution (Sec 3) "Challenges/word-search" , testSolution (Sec 3) "Challenges/ice-cream" + , testSolution (Sec 3) "Challenges/gopher" , testSolution (Sec 10) "Challenges/hanoi" , testSolution Default "Challenges/friend" , testGroup