From 0cd92785e121b2dba6663767f54c253305c66d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0ebek?= <44544735+xsebek@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:49:07 +0200 Subject: [PATCH] Use the new wall in move tutorial (#2160) * add the new boundary wall to `entities.yaml` * use the new boundary wall to simplify the scenario code * the door visual is achieved by using an invisible walkable boundary entity called "door" * use instant wait * directly check the base position in objective conditions * use one-line code snippets in the markdown description so that they are rendered on a single line --- data/entities.yaml | 11 + data/scenarios/Tutorials/move.yaml | 354 +++++++---------------- data/scenarios/Tutorials/move_check.sw | 72 ++--- data/scenarios/Tutorials/move_surveil.sw | 71 ++--- src/swarm-doc/Swarm/Doc/Gen.hs | 3 +- 5 files changed, 169 insertions(+), 342 deletions(-) diff --git a/data/entities.yaml b/data/entities.yaml index c492aa884..23615f0ef 100644 --- a/data/entities.yaml +++ b/data/entities.yaml @@ -635,6 +635,17 @@ ############################################################ ### Utility ################################################ ############################################################ +- name: wall + display: + char: '+' + attr: entity + description: + - A wall + properties: [unwalkable, known, boundary] +# following are "legacy" walls that do not connect automatically, +# but require explicitly placing each one - this can be used for +# precise control, but in most cases the simple 'wall' is better +# because the 'boundary' updates it to look nice - name: upper left corner display: attr: entity diff --git a/data/scenarios/Tutorials/move.yaml b/data/scenarios/Tutorials/move.yaml index 9df927214..dcaa2769d 100644 --- a/data/scenarios/Tutorials/move.yaml +++ b/data/scenarios/Tutorials/move.yaml @@ -6,49 +6,60 @@ objectives: - id: move_to_first_flower teaser: Get the flower goal: - - Robots can use the `move` command to move forward one unit in the direction they are currently facing. - - To complete this challenge, move your robot two spaces to the right, to the coordinates `(2,0)` marked with the purple `flower`{=entity}. - - Note that you can chain commands with semicolon, `;`{=snippet}. - - You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. + - | + Robots can use the `move` command to move forward one unit in the direction they are currently facing. + + To complete this challenge, move your robot two spaces to the right, to the coordinates `(2,0)` + marked with the purple `flower`{=entity}. + + Note that you can chain commands with semicolon, `;`{=snippet}. + + You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. condition: | - r <- robotNamed "check"; - w <- as r {count "Win"}; - return (w >= 1) + as base {l <- whereami; return (l == (2,0))} - id: move_along_corridor teaser: Down the corridor goal: - - Good! Now you need to learn how to effectively repeat actions. - | + Good! Now you need to learn how to effectively repeat actions. + Previously you could move twice by chaining the move command: - - | - ``` - move; move - ``` - - To reuse that command without having to retype it press the upward arrow on your keyboard. This will allow you to select previous commands. - - Ahead of you is a six steps long corridor. Move to its end, i.e. the coordinates `(8,0)` marked with the second purple `flower`{=entity}. - - You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. + + `move; move` + + To reuse that command without having to retype it press the upward arrow on your keyboard. + This will allow you to select previous commands. + + Ahead of you is a six steps long corridor. Move to its end, i.e. the coordinates `(8,0)` marked + with the second purple `flower`{=entity}. + + You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. condition: | - r <- robotNamed "check"; - w <- as r {count "Win"}; - return (w >= 2) + as base {l <- whereami; return (l == (8,0))} prerequisite: move_to_first_flower - id: move_northeast_corner teaser: To northeast corner goal: - - Well done! In addition to `move`, you can use the `turn` command to turn your robot, for example, `turn right` or `turn east`. - - Switch to the inventory view in the upper left (by clicking on it or typing **Alt+E**) and select the `treads`{=entity} device to read about the details. If the bottom-left info panel is not big enough to read the whole thing, you can hit **Enter** on the `treads`{=entity} device to pop out the description, or you can focus the info panel (with **Alt+T** or by clicking) and scroll it with arrow keys or **PgUp**/**PgDown**. When you're done reading, you can come back to the REPL prompt by clicking on it or typing **Alt+R**. - - Afterwards, move your robot to the coordinates `(8,4)` in the northeast corner marked with two flowers. - | + Well done! In addition to `move`, you can use the `turn` command to turn your robot, for example, + `turn right` or `turn east`. + + Switch to the inventory view in the upper left (by clicking on it or typing **Alt+E**) and select + the `treads`{=entity} device to read about the details. + If the bottom-left info panel is not big enough to read the whole thing, you can hit **Enter** + on the `treads`{=entity} device to pop out the description, or you can focus the info panel + (with **Alt+T** or by clicking) and scroll it with arrow keys or **PgUp**/**PgDown**. + When you're done reading, you can come back to the REPL prompt by clicking on it or typing **Alt+R**. + + Afterwards, move your robot to the coordinates `(8,4)` in the northeast corner marked with another flower. + Remember, you can chain commands with `;`{=snippet}, for example: - - | - ``` - move;move;move;move - ``` - - You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. + + `move; move; move; move` + + You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. condition: | - r <- robotNamed "check"; - w <- as r {count "Win"}; - return (w >= 3) + as base {l <- whereami; return (l == (8,4))} prerequisite: move_along_corridor - goal: - Good job! You are now ready to move and turn on your own. @@ -56,9 +67,7 @@ objectives: - Remember you can press the upward arrow on your keyboard to select previous commands. - You can open this popup window at any time to remind yourself of the goal using **Ctrl+G**. condition: | - r <- robotNamed "check"; - w <- as r {count "Win"}; - return (w >= 4) + as base {l <- whereami; return (l == (8,8))} prerequisite: move_northeast_corner solution: | // 0 @@ -83,52 +92,31 @@ known: world: palette: '.': [blank] - '*': [blank, flower, check] - 'X': [blank, null, 1P flower] - 'Y': [blank, null, 2P flower] - 'Z': [blank, null, 3P flower] # FIRST ROOM - '┌': [blank, upper left corner] - '┐': [blank, upper right corner, 1S down and horizontal] - '└': [blank, lower left corner] - '┘': [blank, lower right corner, 1S up and horizontal] - '─': [blank, horizontal wall] - '│': [blank, vertical wall] + '0': [blank, wall] + 'A': [blank, flower, check] # SECOND ROOM - '1': [blank, vertical wall] - '-': [blank, null, 1P horizontal] - '|': [blank, null, 1P vertical] - 'c': [blank, null, 1P upper right, 1S left and vertical] - 'b': [blank, null, 1P lower right] - 'd': [blank, null, 1P horizontal, 2S up and horizontal] + '1': [blank, null, 1P wall] + 'B': [blank, null, 1P flower] # THIRD ROOM - '2': [blank, null, 1P horizontal] - '~': [blank, null, 2P horizontal] - '/': [blank, null, 2P vertical] - 'R': [blank, null, 2P upper right] - 'L': [blank, null, 2P upper left, 3S down and horizontal] - 'K': [blank, null, 2P vertical, 3S left and vertical] + '2': [blank, null, 2P wall] + 'C': [blank, null, 2P flower] # FOURTH ROOM - '3': [blank, null, 2P vertical] - '_': [blank, null, 3P horizontal] - '\': [blank, null, 3P vertical] - 'A': [blank, null, 3P lower left corner] - 'B': [blank, null, 3P lower right] - 'C': [blank, null, 3P upper right] - 'D': [blank, null, 3P upper left] + '3': [blank, null, 3P wall] + 'D': [blank, null, 3P flower] upperleft: [-1, 9] map: | - D_________C - \........Z\ - \..D______B - \..\....... - \..A___L~~R - \......3.Y/ - A______K../ - ......./../ - ┌───┐--d22c - │..*1....X| - └───┘-----b + 33333333333 + 3........D3 + 3..33333333 + 3..3....... + 3..33332222 + 3......2.C2 + 33333332..2 + .......2..2 + 00000111111 + 0..A0....B1 + 00000111111 # Font inspiration and a nicely visible separator: # # ███████ ██ ██ █████ ██████ ███ ███ @@ -150,209 +138,67 @@ robots: ################# - name: check system: true - program: run "scenarios/Tutorials/move_check.sw" - ################# - ## HORIZONTAL ## - ################# - - name: 1P horizontal - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="horizontal wall", room=1]); - m - - name: 2P horizontal - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="horizontal wall", room=2]); - m - - name: 3P horizontal - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="horizontal wall", room=3]); - m - ################# - ## VERTICAL ## - ################# - - name: 1P vertical - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="vertical wall", room=1]); - m - - name: 2P vertical - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="vertical wall", room=2]); - m - - name: 3P vertical - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="vertical wall", room=3]); - m - ################# - ## CORNERS ## - ################# - # the order is: - # up left up right - # D+----+C - # | | - # | | - # A+----+B - # low left low right - ######### - ## A ## - ######### - - name: 3P lower left corner - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="lower left corner", room=3]); - m - ######### - ## B ## - ######### - - name: 1P lower right - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="lower right corner", room=1]); - m - - name: 3P lower right - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="lower right corner", room=3]); - m - ######### - ## C ## - ######### - - name: 1P upper right - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="upper right corner", room=1]); - m - - name: 2P upper right - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="upper right corner", room=2]); - m - - name: 3P upper right - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="upper right corner", room=3]); - m - ######### - ## D ## - ######### - - name: 2P upper left - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="upper left corner", room=2]); - m - - name: 3P upper left - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="upper left corner", room=3]); - m + program: instant (run "scenarios/Tutorials/move_check.sw") ################# - ## SEPARATORS ## + ## WALLS ## ################# - # 1 - - name: 1S down and horizontal + - name: 1P wall system: true program: | def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="S", entity="down and horizontal wall", room=1]); - m - - name: 1S up and horizontal + instant ( + run "scenarios/Tutorials/move_surveil.sw"; + main [entity="wall", room=1] + ) + - name: 2P wall system: true program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="S", entity="up and horizontal wall", room=1]); - m - # 2 - - name: 1S left and vertical + def main = \a. noop end + instant ( + run "scenarios/Tutorials/move_surveil.sw"; + main [entity="wall", room=2] + ) + - name: 3P wall system: true program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="S", entity="left and vertical wall", room=2]); - m - - name: 2S up and horizontal - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="S", entity="up and horizontal wall", room=2]); - m - # 3 - - name: 3S left and vertical - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="S", entity="left and vertical wall", room=3]); - m - - name: 3S down and horizontal - system: true - program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="S", entity="down and horizontal wall", room=3]); - m + def main = \a. noop end + instant ( + run "scenarios/Tutorials/move_surveil.sw"; + main [entity="wall", room=3] + ) ################# ## GARDENERS ## ################# - name: 1P flower system: true program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="flower", room=1]); - m + def main = \a. noop end + instant ( + run "scenarios/Tutorials/move_surveil.sw"; + main [entity="flower", room=1] + ) - name: 2P flower system: true program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="flower", room=2]); - m + def main = \a. noop end + instant ( + run "scenarios/Tutorials/move_surveil.sw"; + main [entity="flower", room=2] + ) - name: 3P flower system: true program: | - def main = \a. return noop end - m <- instant (run "scenarios/Tutorials/move_surveil.sw"; - main [action="P", entity="flower", room=3]); - m + def main = \a. noop end + instant ( + run "scenarios/Tutorials/move_surveil.sw"; + main [entity="flower", room=3] + ) entities: - - name: Win + - name: door display: - char: W - attr: gold + char: 'D' + attr: entity + invisible: true description: - - This entity signals that the objective has been met. + - A wall + properties: [boundary] diff --git a/data/scenarios/Tutorials/move_check.sw b/data/scenarios/Tutorials/move_check.sw index b734dea54..a00ec1c27 100644 --- a/data/scenarios/Tutorials/move_check.sw +++ b/data/scenarios/Tutorials/move_check.sw @@ -1,67 +1,57 @@ -def until = \c. b <- c; if b {} {until c} end -def untilSafe = \c. until ( try { c } { return false } ) end - -def orM = \p1.\p2. b1 <- p1; if b1 {return true} {p2} end +def until = \cond. \c. b <- cond; if b {} {c; until cond c} end def abs = \x. if (x >= 0) {x} {-x} end -def baseIsAt = \l. +def open_door = create "door"; swap "door" end + +// TRICK: +// we only check for base at this location +// so we can sleep for as long as base will take to get here +def get_dist = \l. \bl. + abs (fst l - fst bl) + abs (snd l - snd bl) +end + +def waitForBaseAt = \l. \get_timeout. loc <- as base {whereami}; - // TRICK: - // we only check for base at this or neighbor location - // so we can sleep for as long as base will take to get there - let dist = abs (fst l - fst loc) + abs (snd l - snd loc) in - if (dist > 2) { - wait $ dist - 1 - } {}; - return (loc == l) + if (loc == l) { + return true + } { + wait (get_timeout l loc); + waitForBaseAt l get_timeout + } end def room1 = // l <- whereami; let l = (2, 0) in - untilSafe (baseIsAt l); - instant ( - create "Win"; - // open door - turn east; move; grab; - ) + waitForBaseAt l (\_. \_. 1); + log "room 1 done"; + // open door + turn east; move; open_door; end def room2 = let l = (8, 0) in teleport self l; - untilSafe (baseIsAt l); - instant ( - create "Win"; - // open doors - turn north; move; grab; - turn west; move; grab; - ) + waitForBaseAt l get_dist; + log "room 2 done"; + // open doors + turn north; move; open_door; + turn west; move; open_door; end def room3 = - let l = (7, 4) in - teleport self l; - untilSafe $ orM (baseIsAt l) (baseIsAt (8, 4)); - instant ( - create "Win"; - // open door - turn west; move; grab; - ) -end - -def room4 = - let l = (8, 8) in + let l = (8, 4) in teleport self l; - untilSafe (baseIsAt l); - create "Win"; + waitForBaseAt l get_dist; + log "room 3 done"; + // open door + turn west; move; move; open_door; end def main = room1; room2; room3; - room4; end main \ No newline at end of file diff --git a/data/scenarios/Tutorials/move_surveil.sw b/data/scenarios/Tutorials/move_surveil.sw index 0ef13e609..cd476ac22 100644 --- a/data/scenarios/Tutorials/move_surveil.sw +++ b/data/scenarios/Tutorials/move_surveil.sw @@ -1,24 +1,6 @@ -def repeat : Int -> (Int -> Cmd Unit) -> Cmd Unit = - \n. \c. if (n == 0) {} {c n; repeat (n-1) c} -end - def elif = \b.\t.\e. {if b t e} end def else = \e. e end -def act_lazy: Text -> Text -> Cmd (Cmd Unit) = \a.\e. instant $ - if (a == "S") { - if (e != "") { create e } {}; - return (swap e; log $ a ++ ": " ++ e) - } $elif (a == "G") { - return (grab; log a) - } $elif (a == "P") { - if (e != "") { create e } {}; - return (place e; log $ a ++ ": " ++ e) - } $else { - return (fail $ "Finished waiting for check but I don't know what to do: '" ++ a ++ "'") - } -end - def position: Int -> (Int * Int) = \room. if (room == 1) { (3,0) @@ -31,36 +13,33 @@ def position: Int -> (Int * Int) = \room. } end -def room_changes = \room. if (room == 1) {1} {2} end +def wait_until_change = \pos. \original. \cont. + surveil pos; + timeout <- random 10000; + wait timeout; + current <- as self {teleport self pos; scan down}; + if (current != original) { + cont current; + } { + log $ "no change after " ++ format timeout ++ " ticks - continue"; + wait_until_change pos original cont + } +end -def main: [action: Text, entity: Text, room: Int] -> Cmd (Cmd Unit) = \args. +def main: [entity: Text, room: Int] -> Cmd Unit = \args. let pos = position args.room in log $ format args; - target <- as self {teleport self pos; scan down}; - log $ "at position" ++ format pos ++ ": " ++ format target; - act <- act_lazy args.action args.entity; - return ( - // first room optimization - actively wait for one tick to see if the change occured - target2 <- as self {teleport self pos; scan down}; - if (args.room == 1 && target == target2) { - turn forward - } {}; - let changes = room_changes args.room in - has_changed <- - if (target != target2) { - log "changed in first tick - skipping one wait"; - return true - } $else { - target3 <- as self {teleport self pos; scan down}; - if (target != target3) { log "changed in second tick - skipping one wait" }{}; - return $ target != target3 - }; - let changed = if has_changed {1} {0} in - repeat (changes - changed) (\i. - log $ "sleeping until " ++ format pos ++ " changes (countdown: " ++ format i ++ ")"; - surveil pos; - wait 1000000; - ); - act + original <- as self {teleport self pos; scan down}; + log $ "surveil position" ++ format pos ++ ": " ++ format original; + create args.entity; + log $ "sleeping until " ++ format pos ++ " changes"; + wait_until_change pos original ( \changed. + if (args.room == 1) { + place args.entity + } { + // for later rooms a wall appears and then a door is opened + log $ "sleeping until " ++ format pos ++ " changes again"; + wait_until_change pos changed (\_. place args.entity) + } ) end \ No newline at end of file diff --git a/src/swarm-doc/Swarm/Doc/Gen.hs b/src/swarm-doc/Swarm/Doc/Gen.hs index e4362b2c9..ba03c9845 100644 --- a/src/swarm-doc/Swarm/Doc/Gen.hs +++ b/src/swarm-doc/Swarm/Doc/Gen.hs @@ -328,7 +328,8 @@ startingInventory = Map.fromList . map swap . E.elems . view robotInventory ignoredEntities :: Set Text ignoredEntities = Set.fromList - [ "upper left corner" + [ "wall" + , "upper left corner" , "upper right corner" , "lower left corner" , "lower right corner"