diff --git a/data/scenarios/Challenges/00-ORDER.txt b/data/scenarios/Challenges/00-ORDER.txt index eb26f5eec..c1c1f6c3b 100644 --- a/data/scenarios/Challenges/00-ORDER.txt +++ b/data/scenarios/Challenges/00-ORDER.txt @@ -16,6 +16,7 @@ wave.yaml gallery.yaml wolf-goat-cabbage.yaml blender.yaml +dna.yaml friend.yaml pack-tetrominoes.yaml dimsum.yaml diff --git a/data/scenarios/Challenges/_dna/lab.sw b/data/scenarios/Challenges/_dna/lab.sw new file mode 100644 index 000000000..6e7aa2d6b --- /dev/null +++ b/data/scenarios/Challenges/_dna/lab.sw @@ -0,0 +1,177 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +def getBaseForNumber = \n. + if (n == 0) { + "guanine"; + } { + if (n == 1) { + "cytosine"; + } { + if (n == 2) { + "adenine"; + } { + "thymine"; + }; + }; + }; + end; + +def getNumberForBase = \n. + if (n == "guanine") { + 0; + } { + if (n == "cytosine") { + 1; + } { + if (n == "adenine") { + 2; + } { + 3; + }; + }; + }; + end; + +/** Toggle the lowest bit */ +def getComplementNumber = \n. + if (n == 0) { + 1; + } { + if (n == 1) { + 0; + } { + if (n == 2) { + 3; + } { + 2; + }; + }; + }; + end; + +def waitUntilSomethingExists = + maybeItemHere <- scan down; + case maybeItemHere (\_. + watch down; + wait 1000; + waitUntilSomethingExists; + ) return; + end; + +def waitUntilHere = \item. + hereNow <- ishere item; + if hereNow {} { + watch down; + wait 1000; + waitUntilHere item; + }; + end; + +def waitUntilOccupied = + stillEmpty <- isempty; + if stillEmpty { + watch down; + wait 1000; + waitUntilOccupied; + } {}; + end; + +def myStandby = \receptacleLoc. + teleport self receptacleLoc; + entToClone <- grab; + teleport self (36, -11); + turn back; + return $ inR entToClone; + end; + +def placeBase = \standbyFunc. \n. + + if (n > 0) { + idx <- random 4; + let entTemp = getBaseForNumber idx in + let ent = entTemp in + create ent; + place ent; + move; + + clonedOrganism <- placeBase standbyFunc $ n - 1; + + // Unwinds the stack; verifies the original placement order + placedEnt <- instant waitUntilSomethingExists; + let isGood = ent == placedEnt in + move; + + if isGood { + return clonedOrganism; + } { + return $ inL (); + } + } { + // Returns the clonedOrganism + standbyFunc; + }; + end; + +def makeDnaStrand = \receptacleLoc. + teleport self (5, -2); + + dims <- floorplan "DNA decoder"; + let decoderWidth = fst dims in + eitherClonedOrganism <- placeBase (myStandby receptacleLoc) decoderWidth; + + case eitherClonedOrganism (\_. + create "pixel (R)"; + ) (\clonedItem. + instant $ ( + teleport self (0, -11); + waitUntilHere "switch (on)"; + + bottomWaypointQuery <- waypoint "receiver" 1; + let receptacleLoc2 = snd bottomWaypointQuery in + teleport self receptacleLoc2; + + sow clonedItem; + create clonedItem; + k <- robotnamed "keeper"; + give k clonedItem; + + let slideBox = "slide box" in + create slideBox; + give base slideBox; + say $ "You got a new \"" ++ slideBox ++ "\""; + ); + ); + end; + +def waitForCloneableOrganism = + + waypointQuery <- waypoint "receiver" 0; + let receptacleLoc = snd waypointQuery in + organism <- instant ( + teleport self receptacleLoc; + + waitUntilOccupied; + + thingHere <- scan down; + case thingHere (\x. return $ inL x) (\item. + isOrganism <- hastag item "organism"; + return $ inR item; + ); + ); + + case organism (\_. + say "Not a cloneable organism!"; + waitForCloneableOrganism; + ) (\item. + create "pixel (G)"; + makeDnaStrand receptacleLoc; + ); + end; + +def go = + waitForCloneableOrganism; + turn east; + go; + end; + +go; diff --git a/data/scenarios/Challenges/_dna/mirrorbot.sw b/data/scenarios/Challenges/_dna/mirrorbot.sw new file mode 100644 index 000000000..c9ce6abef --- /dev/null +++ b/data/scenarios/Challenges/_dna/mirrorbot.sw @@ -0,0 +1,99 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +def getBaseForNumber = \n. + if (n == 0) { + "guanine"; + } { + if (n == 1) { + "cytosine"; + } { + if (n == 2) { + "adenine"; + } { + "thymine"; + }; + }; + }; + end; + +def getNumberForBase = \n. + if (n == "guanine") { + 0; + } { + if (n == "cytosine") { + 1; + } { + if (n == "adenine") { + 2; + } { + if (n == "thymine") { + 3; + } {-1}; + }; + }; + }; + end; + +/** Toggle the lowest bit */ +def getComplementNumber = \n. + if (n == 0) { + 1; + } { + if (n == 1) { + 0; + } { + if (n == 2) { + 3; + } { + 2; + }; + }; + }; + end; + +def placeComplementOf = \item. + let baseNumber = getNumberForBase item in + if (baseNumber >= 0) { + let complementNumber = getComplementNumber baseNumber in + let newItem = getBaseForNumber complementNumber in + move; + create newItem; + place newItem; + } { + let sludge = "organic sludge" in + create sludge; + place sludge; + } + end; + +def waitUntilHere = + watch down; + wait 1000; + + maybeItemDown <- scan down; + case maybeItemDown (\_. waitUntilHere) (\itemHere. + placeComplementOf itemHere; + ); + end; + +def waitUntilEmpty = + watch down; + wait 1000; + emptyHere <- isempty; + if emptyHere { + // reset the position + turn back; + move; + turn back; + } { + waitUntilEmpty; + } + end; + +def go = + instant waitUntilHere; + waitUntilEmpty; + go; + end; + +go; diff --git a/data/scenarios/Challenges/_dna/resetter.sw b/data/scenarios/Challenges/_dna/resetter.sw new file mode 100644 index 000000000..a88379175 --- /dev/null +++ b/data/scenarios/Challenges/_dna/resetter.sw @@ -0,0 +1,48 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +/** +Assumes we are on the left edge of a row of nucleobases +*/ +def resetPlayfieldRow = + dims <- floorplan "DNA decoder"; + let decoderWidth = fst dims in + doN decoderWidth (grab; move;); + end; + +def resetPlayfield = + teleport self (5, -2); + resetPlayfieldRow; + teleport self (5, -3); + resetPlayfieldRow; + + teleport self (5, -11); + resetPlayfieldRow; + teleport self (5, -12); + resetPlayfieldRow; + end; + +def watchSwitch = \lastState. + watch down; + wait 1000; + found <- scan down; + case found return (\item. + if (item != lastState) { + if (item == "switch (off)") { + loc <- whereami; + resetPlayfield; + teleport self loc; + } { + }; + } {}; + watchSwitch item; + ); + end; + +def go = + instant $ ( + found <- scan down; + case found return watchSwitch; + ); + end; + +go; diff --git a/data/scenarios/Challenges/_dna/solution.sw b/data/scenarios/Challenges/_dna/solution.sw new file mode 100644 index 000000000..d63f598b3 --- /dev/null +++ b/data/scenarios/Challenges/_dna/solution.sw @@ -0,0 +1,262 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +def moveUntilBlocked = + blockedHere <- blocked; + if blockedHere {} { + move; + moveUntilBlocked; + } + end; + +def getBaseForNumber = \n. + if (n == 0) { + "guanine"; + } { + if (n == 1) { + "cytosine"; + } { + if (n == 2) { + "adenine"; + } { + "thymine"; + }; + }; + }; + end; + +def getNumberForBase = \n. + if (n == "guanine") { + 0; + } { + if (n == "cytosine") { + 1; + } { + if (n == "adenine") { + 2; + } { + 3; + }; + }; + }; + end; + +/** Toggle the lowest bit */ +def getComplementNumber = \n. + if (n == 0) { + 1; + } { + if (n == 1) { + 0; + } { + if (n == 2) { + 3; + } { + 2; + }; + }; + }; + end; + +def waitWhileHere = \item. + stillHere <- ishere item; + if stillHere { + watch down; + wait 1000; + waitWhileHere item; + } {}; + end; + +def waitUntilHere = \item. + hereNow <- ishere item; + if hereNow {} { + watch down; + wait 1000; + waitUntilHere item; + }; + end; + +def moveToPattern = + turn back; + doN 5 move; + turn left; + doN 4 move; + turn left; + move; + end; + +def moveToOtherRow = + turn right; + doN 2 move; + turn right; + doN 4 move; + turn left; + doN 4 move; + turn left; + doN 4 move; + turn right; + doN 2 move; + turn right; + move; + end; + +def waitForItem : Dir -> Cmd Text = \d. + item <- scan d; + case item (\_. + watch d; + wait 1000; + waitForItem d; + ) return; + end; + +def waitForSpecificItem = \item. \d. + itemIsHere <- ishere item; + if itemIsHere { + } { + watch d; + wait 1000; + waitForSpecificItem item d; + } + end; + +def placeComplementOf = \item. + let baseNumber = getNumberForBase item in + let complementNumber = getComplementNumber baseNumber in + let newItem = getBaseForNumber complementNumber in + place newItem; + end; + +/** +Store the observed entities in the recursion stack. +*/ +def replicatePattern = \standbyFunc. \n. + if (n > 0) { + thingTemp <- waitForItem left; + let thing = thingTemp in + placeComplementOf thing; + move; + replicatePattern standbyFunc $ n - 1; + + place thing; + move; + } { + standbyFunc; + } + end; + +/** +Position self at entrance +*/ +def pickFlowerAndWater = + doN 6 move; + dahlia <- grab; + + turn left; + doN 8 move; + turn right; + doN 31 move; + clover <- grab; + turn back; + doN 35 move; + d <- grab; + doN 10 move; + + turn left; + + doN 18 move; + use "siphon" forward; + turn left; + doN 7 ( + move; + use "siphon" right; + ); + doN 4 move; + turn right; + doN 16 move; + + mushroom <- grab; + + turn back; + doN 23 move; + + turn right; + return dahlia; + + // return mushroom; + // return d; + end; + + +def waitUntilOccupied = + stillEmpty <- isempty; + if stillEmpty { + watch down; + wait 1000; + waitUntilOccupied; + } {}; + end; + +def returnToInputReceptacle = + turn back; + doN 5 move; + turn left; + moveUntilBlocked; + turn left; + doN 29 move; + turn right; + moveUntilBlocked; + turn left; + doN 4 move; + turn right; + doN 6 move; + turn right; + moveUntilBlocked; + end; + +def completeDnaTask = \sentinel. + place sentinel; + make "specimen slide"; + + doN 16 $ make "cytosine"; + + waitWhileHere sentinel; + moveToPattern; + replicatePattern moveToOtherRow 32; + + // Activate the switch + doN 3 move; + drill forward; + wait 2; + drill forward; + + turn left; + doN 6 move; + turn left; + doN 32 move; + turn left; + doN 2 move; + turn left; + move; + + waitUntilOccupied; + grab; + + returnToInputReceptacle; + end; + +def iterateOrganisms = \idx. + result <- tagmembers "organism" idx; + let totalCount = fst result in + if (idx < totalCount) { + completeDnaTask $ snd result; + iterateOrganisms $ idx + 1; + } {}; + end; + +def go = + _sentinel <- pickFlowerAndWater; + moveUntilBlocked; + + iterateOrganisms 0; + end; + +go; diff --git a/data/scenarios/Challenges/_dna/topchecker.sw b/data/scenarios/Challenges/_dna/topchecker.sw new file mode 100644 index 000000000..abb4b63da --- /dev/null +++ b/data/scenarios/Challenges/_dna/topchecker.sw @@ -0,0 +1,107 @@ +def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end; + +def getBaseForNumber = \n. + if (n == 0) { + "guanine"; + } { + if (n == 1) { + "cytosine"; + } { + if (n == 2) { + "adenine"; + } { + "thymine"; + }; + }; + }; + end; + +def getNumberForBase = \n. + if (n == "guanine") { + 0; + } { + if (n == "cytosine") { + 1; + } { + if (n == "adenine") { + 2; + } { + if (n == "thymine") { + 3; + } {-1}; + }; + }; + }; + end; + +/** Toggle the lowest bit */ +def getComplementNumber = \n. + if (n == 0) { + 1; + } { + if (n == 1) { + 0; + } { + if (n == 2) { + 3; + } { + 2; + }; + }; + }; + end; + +def waitUntilHere = \remainingCount. + if (remainingCount > 0) { + maybeItemHere <- scan down; + case maybeItemHere (\_. + watch down; + wait 1000; + waitUntilHere remainingCount; + ) (\itemHere. + + maybeItemAbove <- scan left; + case maybeItemAbove (\_. fail "Expected an item here.") (\itemAbove. + let num = getNumberForBase itemAbove in + if (num >= 0) { + let complementNum = getComplementNumber num in + let complementItem = getBaseForNumber complementNum in + if (complementItem == itemHere) { + move; + waitUntilHere $ remainingCount - 1; + } { + create "pixel (R)"; + } + } { + fail "Expected nonnegative item index." + } + ); + ); + } { + create "pixel (G)"; + }; + end; + +def waitUntilEmpty = + watch down; + wait 1000; + emptyHere <- isempty; + if emptyHere {} { + waitUntilEmpty; + } + end; + +def waitForReset = + backup; + waitUntilEmpty; + end; + +def go = \startingLoc. + instant $ waitUntilHere 32; + waitForReset; + teleport self startingLoc; + go startingLoc; + end; + +loc <- whereami; +go loc; diff --git a/data/scenarios/Challenges/dna.yaml b/data/scenarios/Challenges/dna.yaml new file mode 100644 index 000000000..369510ff4 --- /dev/null +++ b/data/scenarios/Challenges/dna.yaml @@ -0,0 +1,593 @@ +version: 1 +name: DNA +author: Karl Ostmo +description: | + Copy strands of DNA in the lab to replicate organisms +creative: false +seed: 9 +attrs: + - name: floorattr + bg: "#111111" + - name: wall2 + fg: "#88ee22" + - name: pink + fg: "#ff99bb" + - name: lemon + fg: "#eeee99" + - name: clover + bg: "#225522" + - name: beige + fg: "#f5f5dc" + - name: soup + fg: "#338822" + bg: "#774422" +terrains: + - name: floor + attr: floorattr + description: | + Laboratory floor +objectives: + - id: place_flower + teaser: Place subject + goal: + - | + The laboratory building ahead is neatly landscaped, though + its decorative `pond`{=structure} exudes a peculiar quality. + There may be a device you can `use` to sample its contents. + + The DNA lab has two large apparatus inside. + The first is a `DNA sequencer`{=structure} that + determines the arrangement of ATCG pairs within an `organism`{=tag}'s genome. + + The `DNA decoder`{=structure} in the lab's south wing can reconstitute + an `organism`{=tag} from its genome. + + To get started, borrow a `dahlia`{=entity} from the flowerbed at the lab + entrance and place it in the receptacle directly to your east. + condition: | + r <- robotnamed "lab"; + as r { + has "pixel (G)"; + } + - id: place_complements + teaser: Complement strand + prerequisite: place_flower + goal: + - | + The `DNA sequencer`{=structure} will begin to decode the subject's DNA. + The subject will be dematerialized once the top row + of the "double helix" has been decoded. + + You must then complete the double helix (fill in the second row within the device) + with complementary base pairs. + Use your `soup strainer`{=entity} to obtain ingredients. + Consult your *Compendium* for appropriate pairings. + + Note that `scan`ning has a cost, and you have a limited supply of + `specimen slide`{=entity}s (in a `slide box`{=entity}) with which to + `scan` DNA bases using your `microscope`{=entity}. + condition: | + r <- robotnamed "topchecker"; + as r { + has "pixel (G)"; + } + - id: create_clone + prerequisite: place_complements + teaser: Create clone + goal: + - | + Now let's make a clone! Recreate the top half of the + DNA strand in the south wing's `DNA decoder`{=structure} + (you remember the sequence, right?). + + Each element you `place` will be automatically complemented. + + When all segments are placed, `drill` the switch on the western wall to + commence rematerialization. `grab` the clone (once matured) as a souvenir! + condition: | + def checkMember = \idx. + result <- tagmembers "organism" idx; + let totalCount = fst result in + hasThis <- has $ snd result; + if hasThis { + return true; + } { + if (idx < totalCount - 1) { + checkMember $ idx + 1; + } { + return false; + } + }; + end; + + k <- robotnamed "keeper"; + as k { + checkMember 0; + } + - prerequisite: create_clone + teaser: More clones + goal: + - | + Toggle the switch off to reset the lab. + - | + Find three more different `organism`{=tag}s to clone. + Explore outside nearby the lab for specimens. + condition: | + def countMembers = \memberCount. \idx. + result <- tagmembers "organism" idx; + let totalCount = fst result in + + if (idx < totalCount) { + hasThis <- has $ snd result; + let memberCountAddition = if hasThis {1} {0} in + countMembers (memberCount + memberCountAddition) $ idx + 1; + } { + return memberCount; + } + end; + + k <- robotnamed "keeper"; + as k { + c <- countMembers 0 0; + return $ c >= 4; + } +robots: + - name: base + dir: east + loc: [-8, 4] + devices: + - antenna + - ADT calculator + - barcode reader + - branch predictor + - comparator + - compass + - dictionary + - feeler + - grabber + - hearing aid + - lambda + - logger + - microscope + - rolex + - siphon + - soup strainer + - strange loop + - switch puller + - treads + - welder + - workbench + inventory: + - [1, slide box] + - name: lab + system: true + dir: east + display: + invisible: true + attr: 'robot' + devices: + - switch puller + inventory: + - [1, branch predictor] + - [1, treads] + - [1, beaglepuss] + - [1, antenna] + - [1, comparator] + - [1, clock] + - [1, workbench] + - [1, grabber] + - [1, ADT calculator] + - [1, dictionary] + - [1, lambda] + - [1, logger] + - [1, welder] + - [1, hearing aid] + - [1, scanner] + - [1, strange loop] + - [1, solar panel] + program: | + run "data/scenarios/Challenges/_dna/lab.sw" + - name: topchecker + system: true + dir: east + display: + invisible: true + attr: 'robot' + program: | + run "data/scenarios/Challenges/_dna/topchecker.sw" + - name: mirrorbot + system: true + dir: south + display: + invisible: true + attr: 'robot' + program: | + run "data/scenarios/Challenges/_dna/mirrorbot.sw" + - name: resetter + description: resets the apparatus after completion by watching the switch + system: true + dir: east + display: + invisible: true + attr: 'robot' + program: | + run "data/scenarios/Challenges/_dna/resetter.sw" + - name: keeper + description: keeps track of cloned specimens + system: true + dir: south + display: + invisible: true + attr: 'robot' +solution: | + run "data/scenarios/Challenges/_dna/solution.sw" +structures: + - name: pond + recognize: + - north + structure: + mask: '.' + palette: + 'x': [grass, rock] + 'w': [dirt, pond broth] + map: | + ...xxxxxxxx.. + ..xwwwwwwwwx. + ..xwwwwwwwwwx + ..xwwwwwwwwwx + .xwwwwwwxxxx. + xwwwwwwx..... + xwwwwwwx..... + .xxxxxx...... + - name: fairy ring + structure: + mask: '.' + palette: + 'm': [grass, mushroom] + map: | + ....mm.m... + ..m........ + .........m. + ..m........ + ...m...m... + - name: receptacle + structure: + mask: '.' + palette: + 'x': [floor, wall] + 'w': [floor, wall2] + 'f': + cell: [floor, erase] + waypoint: + name: receiver + map: | + ww........ + fwxxxxxxxx + ww.x.x.x.x + - name: decoder base + structure: + palette: + '┬': [stone, down and horizontal wall] + '┴': [stone, up and horizontal wall] + ':': [floor, erase] + map: | + ┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬ + :::::::::::::::::::::::::::::::: + :::::::::::::::::::::::::::::::: + ┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴ + - name: copybots overlay + structure: + mask: '.' + palette: + 'm': [floor, erase, mirrorbot] + map: | + ................................ + mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + ................................ + ................................ + - name: decoder overlay + structure: + mask: '.' + palette: + 'c': [floor, erase, topchecker] + map: | + ................................ + ................................ + c............................... + ................................ + - name: DNA decoder + recognize: + - north + structure: + placements: + - src: decoder base + - src: copybots overlay + - src: receptacle + offset: [18, -4] + orient: + up: south + map: "" + - name: DNA sequencer + recognize: + - north + structure: + placements: + - src: decoder base + - src: decoder overlay + - src: receptacle + offset: [4, 3] + map: "" +entities: + - name: barcode reader + display: + attr: red + char: 'S' + description: + - Reads the 'tag' of an item + properties: [pickable] + capabilities: [hastag, tagmembers] + - name: dahlia + display: + char: '*' + attr: pink + description: + - Brightly colored flower + properties: [known, pickable, growable] + growth: + duration: [20, 30] + tags: [organism] + - name: daisy + display: + char: 'x' + attr: lemon + description: + - Pretty flower + properties: [pickable, growable] + growth: + duration: [15, 30] + tags: [organism] + - name: clover + display: + char: 'c' + attr: clover + description: + - A tiny, 3-leaf plant + properties: [pickable, growable] + growth: + duration: [10, 20] + tags: [organism] + - name: mushroom + display: + char: 'm' + attr: beige + description: + - Invasive fungus + properties: [pickable, growable] + growth: + duration: [15, 25] + spread: + radius: 2 + density: 0.5 + biomes: [floor, stone] + tags: [organism] + - name: wall2 + display: + char: 'w' + attr: wall2 + description: + - Just a wall + properties: [known, unwalkable, boundary] + - name: primordial soup + display: + char: 's' + attr: soup + description: + - Can be decomposed into nucleic acids + properties: [pickable, liquid] + - name: pond broth + display: + char: 's' + attr: soup + description: + - Can be decomposed into nucleic acids + properties: [pickable, liquid] + - name: soup strainer + display: + char: 'r' + description: + - Extracts nucleic acids from `primordial soup`{=entity} + properties: [known, pickable] + - name: feeler + display: + char: 'm' + description: + - Senses presence of specific entities + properties: [known, pickable] + capabilities: [blocked, ishere, isempty] + - name: microscope + display: + char: 'm' + description: + - Scan for the cost of 1 `specimen slide`{=entity} + properties: [known, pickable] + capabilities: + - capability: scan + cost: + - [1, "specimen slide"] + - name: specimen slide + display: + char: 'i' + attr: device + description: + - Can take a sample of DNA for scanning + - name: slide box + display: + char: 'b' + attr: device + description: + - Contains `specimen slides`{=entity} + - name: guanine + display: + char: 'G' + attr: red + description: + - One of the four nucleobases in DNA. + - Is paired with `cytosine`{=entity}. + properties: [known] + - name: cytosine + display: + char: 'C' + attr: blue + description: + - One of the four nucleobases in DNA. + - Is paired with `guanine`{=entity}. + properties: [known] + - name: adenine + display: + char: 'A' + attr: green + description: + - One of the four nucleobases in DNA. + - Is paired with `thymine`{=entity}. + properties: [known] + - name: thymine + display: + char: 'T' + attr: gold + description: + - One of the four nucleobases in DNA. + - Is paired with `adenine`{=entity}. + properties: [known] + - name: switch (off) + display: + attr: red + char: '•' + description: + - A control in the deactivated position. + properties: [known] + - name: switch (on) + display: + attr: green + char: '•' + description: + - A control in the activated position. + properties: [known] + - name: organic sludge + display: + attr: green + char: 'S' + description: + - A repulsive, shuddering mass of slime. + properties: [pickable] + - name: switch puller + display: + char: 'P' + attr: gold + capabilities: [drill] + description: + - Can pull switches + - name: siphon + display: + char: 's' + attr: device + capabilities: [drill] + description: + - Can retrieve liquid +recipes: + - in: + - [1, slide box] + out: + - [32, specimen slide] + - in: + - [1, primordial soup] + out: + - [4, cytosine] + - [4, guanine] + - [4, adenine] + - [4, thymine] + required: + - [1, soup strainer] + - in: + - [1, switch (off)] + out: + - [1, switch (on)] + required: + - [1, switch puller] + - in: + - [1, switch (on)] + out: + - [1, switch (off)] + required: + - [1, switch puller] + - in: + - [1, pond broth] + out: + - [8, primordial soup] + required: + - [1, siphon] +known: [flower, rock] +world: + dsl: | + let + flowerNoise = perlin seed 1 0.15 0.0, + soupNoise = perlin seed 2 0.04 1.0, + + flowers = flowerNoise > 0.70, + soup = soupNoise > 0.9 + in + overlay + [ {grass} + , mask (flowers && (x - y) % 3 == 0) {daisy} + , mask soup {clover} + ] + upperleft: [-2, 4] + mask: 'q' + palette: + 'z': [stone, wall] + '.': [stone, erase] + 'G': + cell: [stone, erase] + 'o': [stone, erase] + 'd': [dirt, dahlia] + 'v': [stone, erase, lab, keeper] + '0': + cell: [stone, switch (off), resetter] + waypoint: + name: switch + 'D': + structure: + name: DNA decoder + cell: [floor, erase] + 'E': + structure: + name: DNA sequencer + cell: [floor, erase] + placements: + - src: pond + offset: [-17, -10] + - src: fairy ring + offset: [-5, -25] + map: | + dzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + dzv.........................................z + zzz........oo...............................z + ............o...............................z + zzz........oo...............................z + dz.....Eooooooooooooooooooooooooooooooo.....z + dz.....o..............................o.....z + dz.....o..............................o.....z + dz.....oooooooooooooooooooooooooooooooo.....z + qz..........................................z + qzzzzzzzzz.zzzzzzzzzzzzzzzzzzzzzzzz.zzzzzzzzz + qqqqqqqqqzGzqqqqqqqqqqqqqqqqqqqqqqzGzqqqqqqqq + qzzzzzzzzz.zzzzzzzzzzzzzzzzzzzzzzzz.zzzzzzzzz + qz..........................................z + qzz....D....................................z + qz0.........................................z + qzz.........................................z + qz..........................................z + qz..........................................z + qz..........................................z + qz..........................................z + qz..........................................z + qzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz diff --git a/src/swarm-doc/Swarm/Doc/Wiki/Cheatsheet.hs b/src/swarm-doc/Swarm/Doc/Wiki/Cheatsheet.hs index 68265bd54..61feb18ad 100644 --- a/src/swarm-doc/Swarm/Doc/Wiki/Cheatsheet.hs +++ b/src/swarm-doc/Swarm/Doc/Wiki/Cheatsheet.hs @@ -42,7 +42,7 @@ import Swarm.Language.Syntax qualified as Syntax import Swarm.Language.Text.Markdown as Markdown (docToMark) import Swarm.Language.Typecheck (inferConst) import Swarm.Pretty (prettyText, prettyTextLine) -import Swarm.Util (maximum0, showT) +import Swarm.Util (applyWhen, maximum0, showT) -- * Types @@ -170,10 +170,7 @@ capabilityRow PageAddress {..} em cap = then t else addLink (entityAddress <> "#" <> T.replace " " "-" t) t linkCommand c = - ( if T.null commandsAddress - then id - else addLink (commandsAddress <> "#" <> showT c) - ) + applyWhen (not $ T.null commandsAddress) (addLink $ commandsAddress <> "#" <> showT c) . codeQuote $ constSyntax c diff --git a/src/swarm-engine/Swarm/Game/State.hs b/src/swarm-engine/Swarm/Game/State.hs index d02dcf40e..5c1df4d5e 100644 --- a/src/swarm-engine/Swarm/Game/State.hs +++ b/src/swarm-engine/Swarm/Game/State.hs @@ -120,7 +120,7 @@ import Swarm.Language.Pipeline (processTermEither) import Swarm.Language.Syntax (SrcLoc (..), TSyntax, sLoc) import Swarm.Language.Value (Env) import Swarm.Log -import Swarm.Util (uniq) +import Swarm.Util (applyWhen, uniq) import Swarm.Util.Lens (makeLensesNoSigs) newtype Sha1 = Sha1 String @@ -294,7 +294,7 @@ messageNotifications = to getNotif new = takeWhile (\l -> l ^. leTime > gs ^. messageInfo . lastSeenMessageTime) $ reverse allUniq -- creative players and system robots just see all messages (and focused robots logs) unchecked = gs ^. creativeMode || fromMaybe False (focusedRobot gs ^? _Just . systemRobot) - messages = (if unchecked then id else focusedOrLatestClose) (gs ^. messageInfo . messageQueue) + messages = applyWhen (not unchecked) focusedOrLatestClose (gs ^. messageInfo . messageQueue) allMessages = Seq.sort $ focusedLogs <> messages focusedLogs = maybe Empty (view robotLog) (focusedRobot gs) -- classic players only get to see messages that they said and a one message that they just heard @@ -332,7 +332,7 @@ recalcViewCenterAndRedraw :: GameState -> GameState recalcViewCenterAndRedraw g = g & robotInfo .~ newRobotInfo - & (if ((/=) `on` (^. viewCenter)) oldRobotInfo newRobotInfo then needsRedraw .~ True else id) + & applyWhen (((/=) `on` (^. viewCenter)) oldRobotInfo newRobotInfo) (needsRedraw .~ True) where oldRobotInfo = g ^. robotInfo newRobotInfo = recalcViewCenter oldRobotInfo diff --git a/src/swarm-engine/Swarm/Game/Step/Const.hs b/src/swarm-engine/Swarm/Game/Step/Const.hs index 1c62d0ad0..74b336f13 100644 --- a/src/swarm-engine/Swarm/Game/Step/Const.hs +++ b/src/swarm-engine/Swarm/Game/Step/Const.hs @@ -1415,7 +1415,7 @@ execConst runChildProg c vs s k = do return $ Out v s k else do time <- use $ temporal . ticks - return . (if remTime <= 1 then id else Waiting (addTicks (fromIntegral remTime) time)) $ + return . applyWhen (remTime > 1) (Waiting (addTicks (fromIntegral remTime) time)) $ Out v s (FImmediate c wf rf : k) where remTime = r ^. recipeTime diff --git a/src/swarm-engine/Swarm/Game/Step/Util/Command.hs b/src/swarm-engine/Swarm/Game/Step/Util/Command.hs index a6dd19d87..037cb008c 100644 --- a/src/swarm-engine/Swarm/Game/Step/Util/Command.hs +++ b/src/swarm-engine/Swarm/Game/Step/Util/Command.hs @@ -63,6 +63,7 @@ import Swarm.Language.Requirements.Type qualified as R import Swarm.Language.Syntax import Swarm.Language.Text.Markdown qualified as Markdown import Swarm.Log +import Swarm.Util (applyWhen) import System.Clock (TimeSpec) import Prelude hiding (Applicative (..), lookup) @@ -156,9 +157,8 @@ purgeFarAwayWatches = do let isNearby = isNearbyOrExempt privileged myLoc f loc = - if not $ isNearby loc - then IS.delete rid - else id + applyWhen (not $ isNearby loc) $ + IS.delete rid robotInfo . robotsWatching %= M.filter (not . IS.null) . M.mapWithKey f diff --git a/src/swarm-lang/Swarm/Language/LSP/Hover.hs b/src/swarm-lang/Swarm/Language/LSP/Hover.hs index 6a62b9bee..c2c1a9f29 100644 --- a/src/swarm-lang/Swarm/Language/LSP/Hover.hs +++ b/src/swarm-lang/Swarm/Language/LSP/Hover.hs @@ -242,7 +242,7 @@ explain trm = case trm ^. sTerm of internal description = literal $ description <> "\n**These should never show up in surface syntax.**" constGenSig c = let ity = inferConst c - in if ty `eq` ity then id else typeSignature (prettyText c) ity + in U.applyWhen (not $ ty `eq` ity) $ typeSignature (prettyText c) ity -- | Helper function to explain function application. -- diff --git a/src/swarm-lang/Swarm/Language/Requirements/Analysis.hs b/src/swarm-lang/Swarm/Language/Requirements/Analysis.hs index 89a03c455..b04d4e35b 100644 --- a/src/swarm-lang/Swarm/Language/Requirements/Analysis.hs +++ b/src/swarm-lang/Swarm/Language/Requirements/Analysis.hs @@ -26,6 +26,7 @@ import Swarm.Language.Requirements.Type import Swarm.Language.Syntax import Swarm.Language.Syntax.Direction (isCardinal) import Swarm.Language.Types +import Swarm.Util (applyWhen) -- | Infer the requirements to execute/evaluate a term in a given -- context. @@ -122,8 +123,8 @@ requirements tdCtx ctx = localReqCtx <- ask @ReqCtx localTDCtx <- ask @TDCtx let bodyReqs = - (if r then (singletonCap CRecursion <>) else id) - (requirements localTDCtx localReqCtx t1) + applyWhen r (singletonCap CRecursion <>) $ + requirements localTDCtx localReqCtx t1 local @ReqCtx (Ctx.addBinding x bodyReqs) $ go t2 -- Using tydef requires CEnv, plus whatever the requirements are -- for the type itself. diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Placement.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Placement.hs index d6f77a94b..0026a1ac6 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Placement.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Placement.hs @@ -16,6 +16,7 @@ import Swarm.Game.Location import Swarm.Game.Scenario.Topography.Area import Swarm.Game.Scenario.Topography.Grid import Swarm.Language.Syntax.Direction (AbsoluteDir (..)) +import Swarm.Util (applyWhen) newtype StructureName = StructureName Text deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON) @@ -49,7 +50,7 @@ reorientLandmark (Orientation upDir shouldFlip) (AreaDimensions width height) = transposeLoc (Location x y) = Location (-y) (-x) flipV (Location x y) = Location x $ -(height - 1) - y flipH (Location x y) = Location (width - 1 - x) y - flipping = if shouldFlip then flipV else id + flipping = applyWhen shouldFlip flipV rotational = case upDir of DNorth -> id DSouth -> flipH . flipV @@ -63,7 +64,7 @@ applyOrientationTransform (Orientation upDir shouldFlip) = where f = rotational . flipping flipV = NE.reverse - flipping = if shouldFlip then flipV else id + flipping = applyWhen shouldFlip flipV rotational = case upDir of DNorth -> id DSouth -> NE.transpose . flipV . NE.transpose . flipV diff --git a/src/swarm-tui/Swarm/TUI/Controller.hs b/src/swarm-tui/Swarm/TUI/Controller.hs index 3eb787ff8..665d00667 100644 --- a/src/swarm-tui/Swarm/TUI/Controller.hs +++ b/src/swarm-tui/Swarm/TUI/Controller.hs @@ -802,8 +802,8 @@ adjReplHistIndex d s = moveREPL :: REPLState -> REPLState moveREPL theRepl = newREPL - & (if replIndexIsAtInput (theRepl ^. replHistory) then saveLastEntry else id) - & (if oldEntry /= newEntry then showNewEntry else id) + & applyWhen (replIndexIsAtInput (theRepl ^. replHistory)) saveLastEntry + & applyWhen (oldEntry /= newEntry) showNewEntry where -- new AppState after moving the repl index newREPL :: REPLState diff --git a/src/swarm-tui/Swarm/TUI/Editor/View.hs b/src/swarm-tui/Swarm/TUI/Editor/View.hs index 057db5183..7e851ed18 100644 --- a/src/swarm-tui/Swarm/TUI/Editor/View.hs +++ b/src/swarm-tui/Swarm/TUI/Editor/View.hs @@ -25,6 +25,7 @@ import Swarm.TUI.Panel import Swarm.TUI.View.Attribute.Attr import Swarm.TUI.View.CellDisplay (renderDisplay) import Swarm.TUI.View.Util qualified as VU +import Swarm.Util (applyWhen) extractTerrainMap :: UIState -> TerrainMap extractTerrainMap uis = @@ -71,9 +72,8 @@ drawWorldEditor toplevelFocusRing uis = clickable n $ transformation w where transformation = - if Just n == maybeCurrentFocus - then withAttr BL.listSelectedFocusedAttr - else id + applyWhen (Just n == maybeCurrentFocus) $ + withAttr BL.listSelectedFocusedAttr swatchContent list drawFunc = maybe emptyWidget drawFunc selectedThing diff --git a/src/swarm-tui/Swarm/TUI/Launch/View.hs b/src/swarm-tui/Swarm/TUI/Launch/View.hs index 7f4b532c0..91b917a3b 100644 --- a/src/swarm-tui/Swarm/TUI/Launch/View.hs +++ b/src/swarm-tui/Swarm/TUI/Launch/View.hs @@ -27,7 +27,7 @@ import Swarm.TUI.Launch.Prep import Swarm.TUI.Model.Name import Swarm.TUI.View.Attribute.Attr import Swarm.TUI.View.Util (EllipsisSide (Beginning), withEllipsis) -import Swarm.Util (brackets, parens) +import Swarm.Util (applyWhen, brackets, parens) drawFileBrowser :: FB.FileBrowser Name -> Widget Name drawFileBrowser b = @@ -74,9 +74,7 @@ drawLaunchConfigPanel (LaunchOptions lc launchParams) = validatedOptions = toValidatedParams launchParams LaunchControls (FileBrowserControl fb _ isFbDisplayed) seedEditor ring displayedFor = lc addFileBrowser = - if isFbDisplayed - then (drawFileBrowser fb :) - else id + applyWhen isFbDisplayed (drawFileBrowser fb :) getFocusedConfigPanel :: Maybe ScenarioConfigPanelFocusable getFocusedConfigPanel = case focusGetCurrent ring of @@ -86,9 +84,8 @@ drawLaunchConfigPanel (LaunchOptions lc launchParams) = isFocused = (== getFocusedConfigPanel) . Just highlightIfFocused x = - if isFocused x - then withDefAttr highlightAttr - else id + applyWhen (isFocused x) $ + withDefAttr highlightAttr mkButton name label = clickable (ScenarioConfigControl $ ScenarioConfigPanelControl name) diff --git a/src/swarm-tui/Swarm/TUI/Model/Dialog/Goal.hs b/src/swarm-tui/Swarm/TUI/Model/Dialog/Goal.hs index c02ff33ea..e1acff4ff 100644 --- a/src/swarm-tui/Swarm/TUI/Model/Dialog/Goal.hs +++ b/src/swarm-tui/Swarm/TUI/Model/Dialog/Goal.hs @@ -23,6 +23,7 @@ import Servant.Docs qualified as SD import Swarm.Game.Scenario.Objective import Swarm.Game.Scenario.Objective.WinCheck import Swarm.TUI.Model.Name +import Swarm.Util (applyWhen) -- | These are intended to be used as keys in a map -- of lists of goals. @@ -118,8 +119,6 @@ constructGoalMap showHidden oc = filter (maybe False previewable . view objectivePrerequisite) inactiveGoals suppressHidden = - if showHidden - then id - else filter $ not . view objectiveHidden + applyWhen (not showHidden) $ filter $ not . view objectiveHidden (activeGoals, inactiveGoals) = partitionActiveObjectives oc diff --git a/src/swarm-tui/Swarm/TUI/Model/Repl.hs b/src/swarm-tui/Swarm/TUI/Model/Repl.hs index 1673194f0..f190829eb 100644 --- a/src/swarm-tui/Swarm/TUI/Model/Repl.hs +++ b/src/swarm-tui/Swarm/TUI/Model/Repl.hs @@ -73,6 +73,7 @@ import Servant.Docs qualified as SD import Swarm.Language.Syntax (SrcLoc (..)) import Swarm.Language.Types import Swarm.TUI.Model.Name +import Swarm.Util (applyWhen) import Swarm.Util.Lens (makeLensesNoSigs) import Prelude hiding (Applicative (..)) @@ -333,7 +334,7 @@ newREPLEditor t = applyEdit gotoEnd $ editorText REPLInput (Just 1) t where ls = T.lines t pos = (length ls - 1, T.length (last ls)) - gotoEnd = if null ls then id else TZ.moveCursor pos + gotoEnd = applyWhen (not $ null ls) $ TZ.moveCursor pos initREPLState :: REPLHistory -> REPLState initREPLState hist = diff --git a/src/swarm-tui/Swarm/TUI/Panel.hs b/src/swarm-tui/Swarm/TUI/Panel.hs index c4b44e6e2..8361aeecd 100644 --- a/src/swarm-tui/Swarm/TUI/Panel.hs +++ b/src/swarm-tui/Swarm/TUI/Panel.hs @@ -18,6 +18,7 @@ import Brick.Focus import Brick.Widgets.Border import Control.Lens import Swarm.TUI.Border +import Swarm.Util (applyWhen) data Panel n = Panel {_panelName :: n, _panelLabels :: BorderLabels n, _panelContent :: Widget n} @@ -32,7 +33,7 @@ drawPanel attr fr = withFocusRing fr drawPanel' where drawPanel' :: Bool -> Panel n -> Widget n drawPanel' focused p = - (if focused then overrideAttr borderAttr attr else id) $ + applyWhen focused (overrideAttr borderAttr attr) $ borderWithLabels (p ^. panelLabels) (p ^. panelContent) -- | Create a panel. diff --git a/src/swarm-tui/Swarm/TUI/View.hs b/src/swarm-tui/Swarm/TUI/View.hs index bb6372dc3..64f2a179e 100644 --- a/src/swarm-tui/Swarm/TUI/View.hs +++ b/src/swarm-tui/Swarm/TUI/View.hs @@ -407,9 +407,8 @@ drawMainMenuEntry s = \case Quit -> txt "Quit" where highlightMessages = - if s ^. runtimeState . eventLog . notificationsCount > 0 - then withAttr notifAttr - else id + applyWhen (s ^. runtimeState . eventLog . notificationsCount > 0) $ + withAttr notifAttr drawAboutMenuUI :: Maybe Text -> Widget Name drawAboutMenuUI Nothing = centerLayer $ txt "About swarm!" @@ -578,8 +577,8 @@ drawTPS s = hBox (tpsInfo : rateInfo) | s ^. uiState . uiGameplay . uiTiming . uiShowFPS = [ txt " (" , let tpf = s ^. uiState . uiGameplay . uiTiming . uiTPF - in (if tpf >= fromIntegral ticksPerFrameCap then withAttr redAttr else id) - (str (printf "%0.1f" tpf)) + in applyWhen (tpf >= fromIntegral ticksPerFrameCap) (withAttr redAttr) $ + str (printf "%0.1f" tpf) , txt " tpf, " , str (printf "%0.1f" (s ^. uiState . uiGameplay . uiTiming . uiFPS)) , txt " fps)" @@ -646,7 +645,13 @@ drawModal s = \case helpWidget :: Seed -> Maybe Port -> KeyEventHandlingState -> Widget Name helpWidget theSeed mport keyState = - padLeftRight 2 . vBox $ padTop (Pad 1) <$> [info, helpKeys, tips] + padLeftRight 2 . vBox $ + padTop (Pad 1) + <$> [ info + , colorizationLegend + , helpKeys + , tips + ] where tips = vBox @@ -660,6 +665,12 @@ helpWidget theSeed mport keyState = , txt ("Seed: " <> into @Text (show theSeed)) , txt ("Web server port: " <> maybe "none" (into @Text . show) mport) ] + colorizationLegend = + vBox + [ heading boldAttr "Colorization legend" + , drawMarkdown + "In text, snippets of code like `3 + 4` or `scan down` will be colorized. Types like `Cmd Text`{=type} have a dedicated color. The names of an `entity`{=entity}, a `structure`{=structure}, and a `tag`{=tag} also each have their own color." + ] helpKeys = vBox [ heading boldAttr "Keybindings" @@ -774,7 +785,7 @@ messagesWidget :: GameState -> [Widget Name] messagesWidget gs = widgetList where widgetList = focusNewest . map drawLogEntry' $ gs ^. messageNotifications . notificationsContent - focusNewest = if gs ^. temporal . paused then id else over _last visible + focusNewest = applyWhen (not $ gs ^. temporal . paused) $ over _last visible drawLogEntry' e = withAttr (colorLogs e) $ hBox @@ -1055,7 +1066,7 @@ drawItem sel i _ (Separator l) = -- element of the list, once it scrolls off the top of the viewport -- it will never become visible again. -- See https://github.com/jtdaugherty/brick/issues/336#issuecomment-921220025 - (if sel == Just (i + 1) then visible else id) $ hBorderWithLabel (txt l) + applyWhen (sel == Just (i + 1)) visible $ hBorderWithLabel (txt l) drawItem _ _ _ (InventoryEntry n e) = drawLabelledEntityName e <+> showCount n where showCount = padLeft Max . str . show @@ -1361,7 +1372,7 @@ drawRobotLog s = logEntriesToShow = getLogEntriesToShow s n = length logEntriesToShow drawEntry i e = - (if i == n - 1 && s ^. uiState . uiGameplay . uiScrollToEnd then visible else id) $ + applyWhen (i == n - 1 && s ^. uiState . uiGameplay . uiScrollToEnd) visible $ drawLogEntry (not allMe) e rid = s ^? gameState . to focusedRobot . _Just . robotID diff --git a/src/swarm-tui/Swarm/TUI/View/CellDisplay.hs b/src/swarm-tui/Swarm/TUI/View/CellDisplay.hs index 5b31df791..7f6a5f01c 100644 --- a/src/swarm-tui/Swarm/TUI/View/CellDisplay.hs +++ b/src/swarm-tui/Swarm/TUI/View/CellDisplay.hs @@ -161,7 +161,7 @@ displayEntityCell worldEditor ri coords = Coords xy = locToCoords $ P $ toHeading d displayForEntity :: EntityPaint -> Display - displayForEntity e = (if isKnownFunc ri e then id else hidden) $ getDisplay e + displayForEntity e = applyWhen (not $ isKnownFunc ri e) hidden $ getDisplay e -- | Get the 'Display' for a specific location, by combining the -- 'Display's for the terrain, entity, and robots at the location, and diff --git a/src/swarm-tui/Swarm/TUI/View/Robot.hs b/src/swarm-tui/Swarm/TUI/View/Robot.hs index 18e30c5cc..1d90778d9 100644 --- a/src/swarm-tui/Swarm/TUI/View/Robot.hs +++ b/src/swarm-tui/Swarm/TUI/View/Robot.hs @@ -133,10 +133,7 @@ rowHdr :: RowHdr Name RobotWidgetRow rowHdr = RowHdr { draw = \_ (WdthD wd) (RowHdrCtxt (Sel s)) rh -> - let attrFn = - if s - then id - else withAttr rowHdrAttr + let attrFn = applyWhen (not s) $ withAttr rowHdrAttr in attrFn $ padRight (Pad $ if wd > 0 then 0 else 1) $ padLeft Max (str $ show rh) , width = \_ rh -> RowHdrW . (+ 2) . maximum0 $ map (length . show) rh , toRH = \_ (Ix i) -> i + 1 @@ -331,7 +328,7 @@ mkLibraryEntries c = ] nameTxt = r ^. robotName - highlightSystem = if r ^. systemRobot then withAttr highlightAttr else id + highlightSystem = applyWhen (r ^. systemRobot) $ withAttr highlightAttr ageStr | age < 60 = show age <> "sec" diff --git a/src/swarm-tui/Swarm/TUI/View/Robot/Details.hs b/src/swarm-tui/Swarm/TUI/View/Robot/Details.hs index 7ac6eb3be..40bcb17c2 100644 --- a/src/swarm-tui/Swarm/TUI/View/Robot/Details.hs +++ b/src/swarm-tui/Swarm/TUI/View/Robot/Details.hs @@ -22,6 +22,7 @@ import Swarm.Pretty (prettyText) import Swarm.TUI.Model.Name import Swarm.TUI.View.Attribute.Attr (boldAttr, cyanAttr) import Swarm.TUI.View.Robot.Type +import Swarm.Util (applyWhen) renderRobotDetails :: FocusRing Name -> Robot -> RobotDetailsPaneState -> Widget Name renderRobotDetails ring r paneState = @@ -42,7 +43,7 @@ renderRobotDetails ring r paneState = ] where highlightBorderFor n = - if isFocused then overrideAttr borderAttr cyanAttr else id + applyWhen isFocused $ overrideAttr borderAttr cyanAttr where isFocused = focusGetCurrent ring == Just (RobotsListDialog $ SingleRobotDetails n) diff --git a/src/swarm-util/Data/BoolExpr/Simplify.hs b/src/swarm-util/Data/BoolExpr/Simplify.hs index b36566ce8..ac0507e22 100644 --- a/src/swarm-util/Data/BoolExpr/Simplify.hs +++ b/src/swarm-util/Data/BoolExpr/Simplify.hs @@ -52,9 +52,8 @@ replace _ BFalse = BFalse replace m c@(BConst x) = case M.lookup varname m of Nothing -> c Just val -> - if txform val + if isPositive == val then BTrue else BFalse where (varname, isPositive) = extractConstFromSigned x - txform = if isPositive then id else not diff --git a/test/integration/Main.hs b/test/integration/Main.hs index 714e53eb9..f5ac0cbd0 100644 --- a/test/integration/Main.hs +++ b/test/integration/Main.hs @@ -80,7 +80,7 @@ import Swarm.TUI.Model ( import Swarm.TUI.Model.DebugOption (DebugOption (LoadTestingScenarios)) import Swarm.TUI.Model.StateUpdate (constructAppState, initPersistentState) import Swarm.TUI.Model.UI (UIState) -import Swarm.Util (findAllWithExt) +import Swarm.Util (applyWhen, findAllWithExt) import Swarm.Util.RingBuffer qualified as RB import Swarm.Util.Yaml (decodeFileEitherE) import System.FilePath (splitDirectories) @@ -241,6 +241,7 @@ testScenarioSolutions rs ui key = , testSolution (Sec 10) "Challenges/gopher" , testSolution (Sec 5) "Challenges/hackman" , testSolution (Sec 5) "Challenges/blender" + , testSolution (Sec 10) "Challenges/dna" , testSolution (Sec 10) "Challenges/hanoi" , testSolution (Sec 3) "Challenges/lights-out" , testSolution (Sec 10) "Challenges/Sliding Puzzles/3x3" @@ -578,7 +579,7 @@ testEditorFiles = testTextInFile :: Bool -> String -> Text -> FilePath -> TestTree testTextInFile whitespace name t fp = testCase name $ do let removeLW' = T.unlines . map (T.dropWhile isSpace) . T.lines - removeLW = if whitespace then removeLW' else id + removeLW = applyWhen whitespace removeLW' f <- T.readFile fp assertBool ( "EDITOR FILE IS NOT UP TO DATE!\n" diff --git a/test/integration/TestRecipeCoverage.hs b/test/integration/TestRecipeCoverage.hs index 4bf3dcce1..a99dd32e0 100644 --- a/test/integration/TestRecipeCoverage.hs +++ b/test/integration/TestRecipeCoverage.hs @@ -12,7 +12,7 @@ import Data.Set qualified as Set import Data.Text qualified as T import Swarm.Doc.Gen import Swarm.Game.Entity (Entity, EntityName, entityName) -import Swarm.Util (quote) +import Swarm.Util (applyWhen, quote) import Test.Tasty import Test.Tasty.ExpectedFailure (expectFailBecause) import Test.Tasty.HUnit @@ -44,9 +44,8 @@ testRecipeCoverage = do expectNonCovered :: Entity -> TestTree -> TestTree expectNonCovered e = let name = T.toCaseFold (view entityName e) - in if name `elem` nonCoveredList - then expectFailBecause "More recipes needed (#1268)" - else id + in applyWhen (name `elem` nonCoveredList) $ + expectFailBecause "More recipes needed (#1268)" -- | Known non-covered entities that need a recipe. nonCoveredList :: [EntityName]