diff --git a/data/entities.yaml b/data/entities.yaml index a664f0c24..f82a277d2 100644 --- a/data/entities.yaml +++ b/data/entities.yaml @@ -547,8 +547,13 @@ attr: device char: '%' description: - - Installing treads on a robot allows it to move (via the 'move' command) and turn - (via the 'turn' command). + - Installing treads on a robot allows it to move and turn. + - The 'move' command simply moves the robot one space in its direction. + - 'Example:' + - ' move; move; // moves two spaces' + - The 'turn' command takes a direction as an argument, which + can be either absolute (north, west, east, south) or relative + (left, right, forward, back). - 'Example:' - ' move; turn left; move; turn right' capabilities: [move, turn] diff --git a/data/scenarios/02Tutorials/00-move.yaml b/data/scenarios/02Tutorials/00-move.yaml index 89bdae414..7869c1239 100644 --- a/data/scenarios/02Tutorials/00-move.yaml +++ b/data/scenarios/02Tutorials/00-move.yaml @@ -1,16 +1,15 @@ name: Moving, part 1 -entities: - - name: goal - display: - attr: device - char: 'X' - description: - - | - 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). - properties: [portable] +description: | + Learn how to move and chain commands. +# +# The players might not use ';' here, so it should be brought up again when building robots. +# +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). + - Note that you can chain commands with ';'. win: | try { loc <- as base {whereami}; @@ -25,8 +24,6 @@ robots: devices: - treads - logger - inventory: - - [1, goal] world: default: [blank, none] palette: diff --git a/data/scenarios/02Tutorials/01-turn.yaml b/data/scenarios/02Tutorials/01-turn.yaml index 082cc225c..30bcec0f8 100644 --- a/data/scenarios/02Tutorials/01-turn.yaml +++ b/data/scenarios/02Tutorials/01-turn.yaml @@ -1,16 +1,17 @@ name: Moving, part 2 +description: | + Learn how to turn in some direction and pass parameters to commands. +# +# Note that parameters are not in parentheses like in e.g. Python. +# +goal: + - In addition to 'move', you can use the 'turn' command + to turn your robot. For example 'turn right' or 'turn east'. + - Move over to the inventory view with Alt+E and select the treads + device to read about the details. You can come back with Alt+R. + - Afterwards move your robot to the flower in the northeast corner. + - Remember, you can chain commands with ';', for example 'move; move; move;'. entities: - - name: goal - display: - attr: device - char: 'X' - description: - - | - In addition to 'move', you can use the 'turn' command - to turn your robot. 'turn' takes a direction as an argument, - which can be either absolute (north, west, east, south) or relative - (left, right, forward, back). Move your robot to the flower in - the northeast corner. - name: knownflower display: attr: flower @@ -18,14 +19,32 @@ entities: description: - A flower. properties: [known] + - name: left T + display: + attr: entity + char: '├' + description: + - How did you get out of the maze? You should never see me. + properties: [unwalkable, known] + - name: right T + display: + attr: entity + char: '┤' + description: + - How did you get out of the maze? You should never see me. + properties: [unwalkable, known] win: | try { loc <- as base {whereami}; - return (loc == (3,1)) + return (loc == (3,4)) } { return false } solution: | - move; move; move; turn north; move + move; move; move; + turn north; move; move; turn west; + move; move; move; + turn north; move; move; turn east; + move; move; move; robots: - name: base @@ -35,12 +54,12 @@ robots: - treads - compass - logger - inventory: - - [1, goal] world: default: [blank, none] palette: '.': [grass, null] + '├': [stone, left T] + '┤': [stone, right T] '┌': [stone, upper left corner] '┐': [stone, upper right corner] '└': [stone, lower left corner] @@ -48,9 +67,12 @@ world: '─': [stone, horizontal wall] '│': [stone, vertical wall] '*': [grass, knownflower] - upperleft: [-1, 2] + upperleft: [-1, 5] map: | ┌────┐ │...*│ + │.───┤ + │....│ + ├───.│ │....│ └────┘ diff --git a/data/scenarios/02Tutorials/02-craft.yaml b/data/scenarios/02Tutorials/02-craft.yaml deleted file mode 100644 index d2dcb4d54..000000000 --- a/data/scenarios/02Tutorials/02-craft.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Crafting, part 1 -entities: - - name: goal - display: - attr: device - char: 'X' - description: - - | - Robots can use the 'make' command to make things. - Your base has a tree, check the inventory for the - available recipes. Your goal is to create a "logger". -win: | - try { - as base {has "logger"} - } { return false } -solution: | - make "log"; make "logger" -robots: - - name: base - loc: [0,0] - dir: [1,0] - devices: - - workbench - inventory: - - [1, goal] - - [1, tree] -world: - default: [blank, none] - palette: - '┌': [stone, upper left corner] - '┐': [stone, upper right corner] - '└': [stone, lower left corner] - '┘': [stone, lower right corner] - '─': [stone, horizontal wall] - '│': [stone, vertical wall] - ' ': [grass, null] - upperleft: [-1, 1] - map: | - ┌─┐ - │ │ - └─┘ diff --git a/data/scenarios/02Tutorials/02-types.yaml b/data/scenarios/02Tutorials/02-types.yaml new file mode 100644 index 000000000..653b5171c --- /dev/null +++ b/data/scenarios/02Tutorials/02-types.yaml @@ -0,0 +1,72 @@ +name: Typing +description: | + Learn about the types of commands and values. +goal: + - Before we get to solving more complex challenges, + it would be good to get used to how the commands + in Swarm language work and how they don't. + - Previosly, you have used the 'turn' command, + which takes one parameter, which must be a direction. + - What happens if you do not pass it a direction? + - 'Try these commands:' + - | + 'turn 1' + - | + 'turn move' + - | + 'turn north move' + - | + 'move move' + - The last two commands might give the most confusing error, + when they are obviously missing a ';' separating the commands. + - | + Finally try these commands, which will just show the value + (passing values will be useful later) and its type: + - | + 'turn' + - | + 'north' + - Once you are done, do 'place "Win"' to finish this challenge. +entities: + - name: Win + display: + attr: device + char: 'W' + description: + - Do 'place "Win"' once you are done with this challenge. + properties: [known, portable] +win: | + try { + w <- as base {has "Win"}; + return (not w); + } { return false } +solution: | + place "Win" + +robots: + - name: base + loc: [0,0] + dir: [1,0] + devices: + - treads + - compass + - logger + - grabber + inventory: + - [1, Win] +world: + default: [blank, none] + palette: + '.': [grass, null] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 1] + map: | + ┌───┐ + │...│ + │...│ + └───┘ \ No newline at end of file diff --git a/data/scenarios/02Tutorials/03-craft.yaml b/data/scenarios/02Tutorials/03-craft.yaml new file mode 100644 index 000000000..9b0edd2f9 --- /dev/null +++ b/data/scenarios/02Tutorials/03-craft.yaml @@ -0,0 +1,43 @@ +name: Crafting, part 1 +description: | + Learn how to make new things using recipes. +# +# The players should read the recipes and aquaint themselves with the UI. +# This also motivates the next tutorial about obtaining entites. +# +goal: + - Robots can use the 'make' command to make things. + - Your base has a few trees in its inventory. Select them to see the + available recipes. + - Your goal is to make a "branch predictor", so you will have to make + some "branches" first. +win: | + try { + as base {has "branch predictor"} + } { return false } +solution: | + make "branch"; make "branch predictor" +robots: + - name: base + loc: [0,0] + dir: [1,0] + devices: + - workbench + - logger + inventory: + - [10, tree] +world: + default: [blank, none] + palette: + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + ' ': [grass, null] + upperleft: [-1, 1] + map: | + ┌─┐ + │ │ + └─┘ diff --git a/data/scenarios/02Tutorials/04-grab.yaml b/data/scenarios/02Tutorials/04-grab.yaml new file mode 100644 index 000000000..7d4708b8a --- /dev/null +++ b/data/scenarios/02Tutorials/04-grab.yaml @@ -0,0 +1,50 @@ +name: Grab +description: | + Learn how to interact with the world by grabbing entities. +goal: + - Previously you learned how to make new things (like a branch predictor) from ingredients. + Now you will learn how to obtain the ingredients you need. + - There are trees ahead of you, 'move' to each one and 'grab' it. + - You can learn more by selecting the grabber device in your inventory. + - | + TIP: You can use the arrow key Up ('↑') to reuse your previous commands. +win: | + try { + t <- as base {count "tree"}; + return (t >= 6); + } { return false } +solution: + move; + move; grab; + move; grab; + move; grab; + move; grab; + move; grab; + move; grab; +robots: + - name: base + loc: [0,0] + dir: [1,0] + devices: + - treads + - grabber + - logger + - compass + inventory: + - [0, tree] +world: + default: [blank, none] + palette: + '.': [grass, null] + 'T': [grass, tree] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 1] + map: | + ┌────────┐ + │..TTTTTT│ + └────────┘ diff --git a/data/scenarios/02Tutorials/05-place.yaml b/data/scenarios/02Tutorials/05-place.yaml new file mode 100644 index 000000000..37a204488 --- /dev/null +++ b/data/scenarios/02Tutorials/05-place.yaml @@ -0,0 +1,77 @@ +name: Place +description: | + Learn how to interact with the world by grabbing entities and placing them. +# +# Placing the tree is motivated by speeding up tree collection as their growth +# is really slow. It can be cheesed by speeding up the game, but there is no need +# to tell the players. :) +# +goal: + - Previously you learned how to plunder a plentiful forest for wood. + Now you will learn how to plant trees to obtain as much wood as you need. + - There is a fast-growing tree (called "spruce") ahead of you and a new + one will grow there once you grab it. + - You can plant a tree seed by 'place "spruce"; grab' - that way you can + immediately place the next seed. + - You can speed this up by placing the tree in all available cells. + That way you can have eight times as many trees growing at once. + - Your goal is to collect 6 trees. + - | + TIP: You can get a sneak peak at a feature we will explain later and type + 'def t = move; place "spruce"; grab; end' after which you only need to type 't' + instead of retyping the whole command or searching in your command history. +entities: + # faster tree (the normal one grows 500-600 ticks) + - name: spruce + display: + attr: plant + char: 'T' + description: + - | + A tall, living entity made of a tough cellular material called "wood". + They regrow after being harvested and are an important raw ingredient used + in making many different devices. + - | + This one seems to grow a little faster. + properties: [portable, growable] + growth: [100, 120] +win: | + try { + t <- as base {count "spruce"}; + return (t >= 6); + } { return false } +solution: | + def t = move; place "spruce"; grab; end; + def g = grab; move end; + move; + move; grab; t; t; t; t; t; + wait 256; + turn back; g; g; g; g; g; g; +robots: + - name: base + loc: [0,0] + dir: [1,0] + devices: + - treads + - grabber + - logger + - compass + - dictionary + inventory: + - [0, spruce] +world: + default: [blank, none] + palette: + '.': [grass, null] + 'T': [grass, spruce] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 1] + map: | + ┌────────┐ + │..T.....│ + └────────┘ diff --git a/data/scenarios/02Tutorials/06-bind.yaml b/data/scenarios/02Tutorials/06-bind.yaml new file mode 100644 index 000000000..a07cd009e --- /dev/null +++ b/data/scenarios/02Tutorials/06-bind.yaml @@ -0,0 +1,70 @@ +name: Bind notation +description: | + Learn about command return types and how to bind the results. +goal: + - | + Some commands are simple, like 'move: cmd ()', because + they do not have anything meaningful to return. + - | + But there are commands that do, like 'grab' that has type + 'cmd string' which means that it returns the name of the + grabbed entity. Try it: + - | + 'move; grab' + - | + To use the result later you need to use the bind notation: + - | + 'move; t <- grab; place t' + - | + This can be useful if you for example do not care what you + grabbed and just want to move it to another cell. + - Once you are done, do 'place "Win"' to finish this challenge. + - You might need to 'grab' the entity you are standing on + or move to an empty cell first, since each cell can only + contain at most one entity. +entities: + - name: Win + display: + attr: device + char: 'W' + description: + - Do 'place "Win"' once you are done with this challenge. + - You might need to 'grab' the entity you are standing on + or move to an empty cell first. + properties: [known, portable] +win: | + try { + w <- as base {has "Win"}; + return (not w); + } { return false } +solution: | + place "Win" +robots: + - name: base + loc: [0,0] + dir: [1,0] + devices: + - treads + - grabber + - logger + - compass + - dictionary + inventory: + - [1, Win] + - [0, tree] +world: + default: [blank, none] + palette: + '.': [grass, null] + 'T': [grass, tree] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 1] + map: | + ┌───┐ + │.TT│ + └───┘ diff --git a/data/scenarios/02Tutorials/07-install.yaml b/data/scenarios/02Tutorials/07-install.yaml new file mode 100644 index 000000000..74621c7e4 --- /dev/null +++ b/data/scenarios/02Tutorials/07-install.yaml @@ -0,0 +1,71 @@ +name: Install +description: | + Learn how to install devices and gain new capabilities. +goal: + - You know how to 'grab' things lying around, so you are ready to get + an upgrade! + - By installing devices, you learn new capabilites which allow you to + perform more complex commands. + - Before you start building new robots in the later tutorials, you need + to gain the "build" capability. + - Try typing 'build {}' - you should get an error telling you that you + must install a device for it first. + - Fortunately, there is a "3D printer" lying nearby, that provides the + missing capability, go 'grab' it. + - When you have it, install the device with 'install base "3D printer"'. + - 'You win by building your first robot:' + - 'build {log "Hello World!"}' +entities: + - name: knownprinter + display: + attr: device + char: '3' + description: + - A 3D printer, that you should grab and install. + yields: 3D printer + properties: [known, portable] + - name: knownwater + display: + attr: water + char: '~' + description: + - A running stream of of water. + properties: [known, portable, growable, liquid] + growth: [0,0] +win: | + try { + _ <- robotNumbered 1; + return true; + } { return false } +solution: | + turn south; move; grab; install base "3D printer"; build {log "Hello World!"}; +robots: + - name: base + loc: [0,0] + dir: [1,0] + devices: + - logger + - treads + - compass + - grabber + inventory: + - [1, logger] +world: + default: [blank, none] + palette: + '.': [grass, null] + '~': [ice, knownwater] + '3': [grass, knownprinter] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 1] + map: | + ┌───┐ + │...│ + │3..│ + │~~~│ + └───┘ diff --git a/data/scenarios/02Tutorials/08-build.yaml b/data/scenarios/02Tutorials/08-build.yaml new file mode 100644 index 000000000..712c6ff96 --- /dev/null +++ b/data/scenarios/02Tutorials/08-build.yaml @@ -0,0 +1,77 @@ +name: Build +description: | + Learn how to build robots to carry out simple tasks. +# +# Has to be easy, so that you do not have to debug it too much yet. +# +goal: + - Now that you know basic commands, it is time to + build robots to do the work for you. + - You start in a base ('Ω') that does not move (at least not yet). + - Let's start by building a gardener robot to perform a simple task. + - You can build a robot with 'build {COMMANDS}', + where instead of 'COMMANDS' you write the sequence + of commands for the robot to execute (separated by ';'). + - Build a robot to grab the flower and place it next + to the water. + - | + TIP: You can use the name of the flower ("pickerelweed") + or the bind notation: 'f <- grab; ... ; place t;'. +entities: + - name: pickerelweed + display: + attr: flower + char: '*' + description: + - A small plant that grows near the water. + properties: [known, portable, growable] + # It would make sense for the plant to NOT grow away from water. + # But if the player sent a robot that grabbed it and then did not + # place it, another robot would need to salvage that robot. + - name: knownwater + display: + attr: water + char: '~' + description: + - An running stream of of water. + properties: [known, portable, growable, liquid] + growth: [0,0] +win: | + try { + teleport self (2,-1); + ishere "pickerelweed"; + } { return false } +solution: | + build {move; move; t <- grab; turn right; move; place t} +robots: + - name: base + loc: [0,0] + dir: [1,0] + display: + char: Ω + attr: robot + devices: + - logger + - 3D printer + inventory: + - [10, logger] + - [10, compass] +world: + default: [blank, none] + palette: + '.': [grass, null] + '~': [ice, knownwater] + '*': [grass, pickerelweed] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 1] + map: | + ┌───┐ + │..*│ + │...│ + │~~~│ + └───┘ diff --git a/data/scenarios/02Tutorials/09-crash.yaml b/data/scenarios/02Tutorials/09-crash.yaml new file mode 100644 index 000000000..8619eafe2 --- /dev/null +++ b/data/scenarios/02Tutorials/09-crash.yaml @@ -0,0 +1,111 @@ +name: Debug robots +description: | + Learn how to view built robots and debug them. +goal: + - Before you send your robots far away you need to learn how + to figure out what went wrong with them if they crash. + - Send a robot to walk four steps straight east into the mountain, + but use the bind syntax 'r <- build {COMMANDS}'. + - | + Since you expect the robot will crash into the mountain, + add a 'log' command so that we will see what command failed: + - | + 'r <- build {log "Hi!"; move;move;move; log "3"; move; log "OK"}' + - After that you can 'view r', to see how far it got. + - The instructions on how to win will appear in the crashed robots log. +entities: + - name: Win + display: + attr: device + char: 'W' + description: + - Do 'place "Win"' once you are done with this challenge. + - You might need to 'grab' the entity you are standing on + or move to an empty cell. + properties: [known, portable] + - name: knownwater + display: + attr: water + char: '~' + description: + - A running stream of of water. + properties: [known, portable, growable, liquid] + growth: [0,0] + - name: knowntree + display: + attr: plant + char: 'T' + description: + - A tree. + properties: [known, portable, growable] + growth: [0,0] + - name: knownmountain + display: + attr: snow + char: 'A' + description: + - A mountain. + properties: [known, unwalkable] + growth: [0,0] +win: | + try { + as base {has "Win"} + } { return false } +solution: | + crasher <- build {move; move; move; log "bye"; move}; + wait 32; + salvager <- build { + log "I will bring home the Win!"; + move; move; move; salvage; turn back; move; move; give base "Win" + }; +robots: + - name: base + loc: [0,0] + dir: [1,0] + display: + char: Ω + attr: robot + devices: + - logger + - 3D printer + inventory: + - [10, logger] + - [10, compass] + - [10, scanner] + - [10, plasma cutter] + - name: secret + loc: [3, 0] + dir: [0, 0] + devices: + - logger + - flash memory + - dictionary + - 3D printer + inventory: + - [100000, Win] + display: + char: '▒' + attr: grass + system: true + program: | + run "data/scenarios/02Tutorials/09-secret.sw" +world: + default: [blank, none] + palette: + '.': [grass, null] + '~': [ice, knownwater] + 'T': [grass, knowntree] + 'A': [stone, knownmountain] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 3] + map: | + ┌─────┐ + │AAAT~│ + │..A.~│ + │....A│ + └─────┘ diff --git a/data/scenarios/02Tutorials/09-secret.sw b/data/scenarios/02Tutorials/09-secret.sw new file mode 100644 index 000000000..af2578ad1 --- /dev/null +++ b/data/scenarios/02Tutorials/09-secret.sw @@ -0,0 +1,65 @@ +// A for cycle from start to end (excluded) that carries a state. +def foreachF = \s.\e.\com.\state. + if (s >= e) { + return state + } { + n <- com state s; + foreachF (s+1) e com n + } +end; + +// An infinite while cycle that carries a state. +def iterate = \state.\com. + n <- com state; + iterate n com; +end; + +// At the beginning all robots can be given Win. +def allOK: robot -> bool = \rob. + true +end; + +// Try to give a robot a Win, filtering out those that were already given a Win. +// The robot will also recieve instructions, so it **must have a logger!** +def tryGive: string -> (robot -> bool) -> int -> cmd (robot -> bool) = \msg.\f.\i. + r <- try { + robotNumbered i; + } { + log $ "could not find robot " ++ format i; + return self + }; + if (r != self && f r) { + log $ "found the robot " ++ format i; + l <- whereami; + rl <- as r {whereami}; + if (l != rl) { + log $ "the robot" ++ format i ++ "is not in my cell"; + return f; + } { + try { + reprogram r { log msg; }; + log $ "successfully reprogrammed robot " ++ format i; + give r "Win"; + log $ "successfully gave Win to robot " ++ format i; + } { + log $ "the robot " ++ format i ++ "is missing a logger!" + }; + return (\rob. (rob != r && f rob)); + } + } { + log $ "skipping the robot " ++ format i; + return f + } +end; + +// ------------------------------------------------------------------------- +// RUN +// ------------------------------------------------------------------------- + +log "Hi, I am secret"; +iterate allOK (foreachF 1 16 $ tryGive + $ "Send a robot to 'salvage' me and come back to 'give base \"Win\"'.\n" + ++ "When the rescue robot stands where I am and executes 'salvage'," + ++ "all my inventory and logs will go to it, namely the \"Win\".\n" + ++ "Once you have brought the \"Win\" to your base, you will win!" +) \ No newline at end of file diff --git a/data/scenarios/02Tutorials/10-scan.yaml b/data/scenarios/02Tutorials/10-scan.yaml new file mode 100644 index 000000000..0035216cd --- /dev/null +++ b/data/scenarios/02Tutorials/10-scan.yaml @@ -0,0 +1,62 @@ +name: Scan +description: | + Learn how to send robots to safely scan your surroundings. +goal: + - When you land on an alien planet, all the entites in the + world will be unfamiliar to you. + - But you can learn what they are using the scanner device. + - Send robots to move to the unknown entites (marked as '?'), + 'scan forward' and after that come back and 'upload base'. + - | + TIP: You can also 'scan left' or 'scan north', to save on a few + 'turn' commands. +win: | + try { + bm <- as base {knows "mountain"}; + bt <- as base {knows "tree"}; + bw <- as base {knows "wavy water"}; + return (bm || bt || bw) + } { return false } +solution: | + build { + turn east; move; move; move; + turn left; move; + scan left; scan forward; scan east; + turn back; move; + turn west; move; move; move; + upload base; + } +robots: + - name: base + loc: [0,0] + dir: [1,0] + display: + char: Ω + attr: robot + devices: + - logger + - 3D printer + inventory: + - [10, logger] + - [10, compass] + - [10, scanner] +world: + default: [blank, none] + palette: + '.': [grass, null] + '~': [ice, wavy water] + 'T': [grass, tree] + 'A': [stone, mountain] + '┌': [stone, upper left corner] + '┐': [stone, upper right corner] + '└': [stone, lower left corner] + '┘': [stone, lower right corner] + '─': [stone, horizontal wall] + '│': [stone, vertical wall] + upperleft: [-1, 3] + map: | + ┌─────┐ + │AAAT~│ + │..A.~│ + │....A│ + └─────┘ diff --git a/data/scenarios/02Tutorials/README.md b/data/scenarios/02Tutorials/README.md new file mode 100644 index 000000000..bc60e21ad --- /dev/null +++ b/data/scenarios/02Tutorials/README.md @@ -0,0 +1,26 @@ +# Tutorial design + +Here are some notes about how the tutorial is designed. + +## Outline + +In the first few challenges, the starting robot moves and does it all itself. + +After the basics are understood, start using the build command to send out robots instead. + +Finally, move into the open world and guide through obtaining ingredients and crafting. + + +## Tips and tricks + +### Logger + +It is important that at least the base robot has a `logger` device installed. +Without it, the players will not see the errors and will be very confused. + +### Goal + +Until goal is separate from entity, we name it "Goal" so that it is lexicographically +first, since all other entites use lowercase names. + +That way it is the first selected entity and its description is visible to the players. \ No newline at end of file diff --git a/test/integration/Main.hs b/test/integration/Main.hs index afc206c62..ae4087627 100644 --- a/test/integration/Main.hs +++ b/test/integration/Main.hs @@ -9,7 +9,8 @@ import Control.Lens (Ixed (ix), use, view, (&), (.~), (<&>), (^.)) import Control.Monad (filterM, forM_, void) import Control.Monad.State (StateT (runStateT)) import Control.Monad.Trans.Except (runExceptT) -import Data.Foldable (find) +import Data.Foldable (Foldable (toList), find) +import Data.Maybe (isJust) import Data.Text (Text) import qualified Data.Text as T import Data.Yaml (ParseException, prettyPrintParseException) @@ -23,18 +24,20 @@ import qualified Swarm.Language.Context as Ctx import Swarm.Language.Pipeline (processTerm) import Swarm.Util.Yaml (decodeFileEitherE) import System.Directory (doesDirectoryExist, doesFileExist, listDirectory) +import System.Environment (getEnvironment) import System.FilePath.Posix (takeExtension, ()) import System.Timeout (timeout) -import Test.Tasty (TestName, TestTree, defaultMain, testGroup) +import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.ExpectedFailure (expectFailBecause) -import Test.Tasty.HUnit (Assertion, assertFailure, testCase) +import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, testCase) import Witch (into) main :: IO () main = do examplePaths <- acquire "example" "sw" scenarioPaths <- acquire "data/scenarios" "yaml" - + scenarioPrograms <- acquire "data/scenarios" "sw" + ci <- any (("CI" ==) . fst) <$> getEnvironment entities <- loadEntities case entities of Left t -> fail $ "Couldn't load entities: " <> into @String t @@ -43,8 +46,9 @@ main = do testGroup "Tests" [ exampleTests examplePaths + , exampleTests scenarioPrograms , scenarioTests em scenarioPaths - , testScenarioSolution em + , testScenarioSolution ci em ] exampleTests :: [(FilePath, String)] -> TestTree @@ -100,35 +104,51 @@ time = \case sec :: Int sec = 10 ^ (6 :: Int) -testScenarioSolution :: EntityMap -> TestTree -testScenarioSolution _em = +testScenarioSolution :: Bool -> EntityMap -> TestTree +testScenarioSolution ci _em = testGroup "Test scenario solutions" [ testGroup "Tutorial" - [ testSolution "move" Default "02Tutorials/00-move" - , testSolution "turn" Default "02Tutorials/01-turn" - , testSolution "craft" Default "02Tutorials/02-craft" + [ testSolution Default "02Tutorials/00-move" + , testSolution Default "02Tutorials/01-turn" + , testSolution Default "02Tutorials/02-types" + , testSolution Default "02Tutorials/03-craft" + , testSolution Default "02Tutorials/04-grab" + , testSolution Default "02Tutorials/05-place" + , testSolution Default "02Tutorials/06-bind" + , testSolution Default "02Tutorials/07-install" + , testSolution Default "02Tutorials/08-build" + , expectFailIf ci "Fails in CI, can not reproduce locally" $ + testSolution' (Sec 60) "02Tutorials/09-crash" $ \g -> do + let rs = toList $ g ^. robotMap + let hints = any (T.isInfixOf "you will win" . view leText) . toList . view robotLog + let win = isJust $ find hints rs + assertBool "Could not find a robot with winning instructions!" win + , testSolution Default "02Tutorials/10-scan" ] , testGroup "Challenges" - [ testSolution "chess" Default "03Challenges/01-chess_horse" - , testSolution "test (grab)" Default "03Challenges/00-test" - , testSolution "portal room" Default "03Challenges/03-teleport" + [ testSolution Default "03Challenges/01-chess_horse" + , testSolution Default "03Challenges/00-test" + , testSolution Default "03Challenges/03-teleport" ] , testGroup "Regression tests" [ expectFailBecause "Awaiting fix (#394)" $ - testSolution "build with drill (#394)" Default "04Testing/394-build-drill" - , testSolution "drowning results in destruction" Default "04Testing/428-drowning-destroy" + testSolution Default "04Testing/394-build-drill" + , testSolution Default "04Testing/428-drowning-destroy" ] ] where - testSolution :: TestName -> Time -> FilePath -> TestTree - testSolution n s p = testSolution' n s p (const $ pure ()) + expectFailIf :: Bool -> String -> TestTree -> TestTree + expectFailIf b = if b then expectFailBecause else (\_ x -> x) + + testSolution :: Time -> FilePath -> TestTree + testSolution s p = testSolution' s p (const $ pure ()) - testSolution' :: TestName -> Time -> FilePath -> (GameState -> Assertion) -> TestTree - testSolution' n s p verify = testCase n $ do + testSolution' :: Time -> FilePath -> (GameState -> Assertion) -> TestTree + testSolution' s p verify = testCase p $ do Right gs <- runExceptT $ initGameStateForScenario p Nothing Nothing case gs ^. winSolution of Nothing -> assertFailure "No solution to test!" @@ -136,7 +156,7 @@ testScenarioSolution _em = let gs' = gs & robotMap . ix 0 . machine .~ initMachine sol Ctx.empty emptyStore m <- timeout (time s) (snd <$> runStateT playUntilWin gs') case m of - Nothing -> assertFailure "Timed out" + Nothing -> assertFailure "Timed out - this likely means that the solution did not work." Just g -> do noFatalErrors g verify g