diff --git a/app/css/bootcamp/exercises/maze.css b/app/css/bootcamp/exercises/maze.css index e6610addb4..86abf4767c 100644 --- a/app/css/bootcamp/exercises/maze.css +++ b/app/css/bootcamp/exercises/maze.css @@ -71,22 +71,22 @@ background: radial-gradient( circle, transparent 20%, - #f9dcdc 20%, - #f9dcdc 80%, + #dcdef9 20%, + #dcdef9 80%, transparent 80%, transparent ), radial-gradient( circle, transparent 20%, - #f9dcdc 20%, - #f9dcdc 80%, + #dcdef9 20%, + #dcdef9 80%, transparent 80%, transparent ) 10px 10px, - linear-gradient(#f74545 0.8px, transparent 0.8px) 0 -0.4px, - linear-gradient(90deg, #f74545 0.8px, #f9dcdc 0.8px) -0.4px 0; + linear-gradient(#6645f7 0.8px, transparent 0.8px) 0 -0.4px, + linear-gradient(90deg, #6645f7 0.8px, #dcdef9 0.8px) -0.4px 0; background-size: 10px 10px, 10px 10px, 5px 5px, 5px 5px; } &.start { diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx index 0ba1bbb15a..4f99e5e82e 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx @@ -520,6 +520,9 @@ export default class MazeExercise extends Exercise { this.character.style.left = `${this.characterPosition.x * this.squareSize}%` this.character.style.top = `${this.characterPosition.y * this.squareSize}%` } + public getLayout(_: ExecutionContext): SquareInstance[][] { + return Jiki.wrapJSToJikiObject(this.mazeLayout) + } public announceEmojis(_: ExecutionContext, emojis: Jiki.Dictionary) { this.collectedEmojis = Jiki.unwrapJikiObject(emojis) } @@ -575,5 +578,10 @@ export default class MazeExercise extends Exercise { func: this.removeEmoji.bind(this), description: 'removed the emoji from the current square', }, + { + name: 'get_layout', + func: this.getLayout.bind(this), + description: 'got the current layout of the maze', + }, ] } diff --git a/bootcamp_content/projects/maze/config.json b/bootcamp_content/projects/maze/config.json index 0105059ba6..db02f18276 100644 --- a/bootcamp_content/projects/maze/config.json +++ b/bootcamp_content/projects/maze/config.json @@ -9,6 +9,7 @@ "look-around", "emoji-collector", "squares-not-strings", - "omniscience" + "omniscience", + "classy-maze" ] } diff --git a/bootcamp_content/projects/maze/exercises/omniscience/config.json b/bootcamp_content/projects/maze/exercises/omniscience/config.json index 843a5ed31a..0dda6a0958 100644 --- a/bootcamp_content/projects/maze/exercises/omniscience/config.json +++ b/bootcamp_content/projects/maze/exercises/omniscience/config.json @@ -49,7 +49,7 @@ ], "checks": [ { - "property": "position", + "function": "new Maze(get_layout())", "value": [4, 6], "error_html": "You didn't reach the end of the maze." } diff --git a/bootcamp_content/projects/maze/exercises/pacman/config.json b/bootcamp_content/projects/maze/exercises/pacman/config.json new file mode 100644 index 0000000000..92cd8030c2 --- /dev/null +++ b/bootcamp_content/projects/maze/exercises/pacman/config.json @@ -0,0 +1,342 @@ +{ + "title": "Omniscience", + "description": "Navigate the maze without any help!", + "level": 9, + "idx": 4, + "project_type": "maze", + "tests_type": "state", + "blocks_level_progression": false, + "exercise_functions": [ + "move", + "turn_left", + "turn_right", + "announce_emojis", + "get_initial_maze" + ], + "interpreter_options": { + "max_repeat_until_game_over_iterations": 50, + "max_total_loop_iterations": 20000, + "max_total_execution_time": 30000, + "time_per_frame": 0.01 + }, + "tasks": [ + { + "name": "A straight path", + "tests": [ + { + "slug": "diamonds", + "name": "Collect the diamonds", + "function": "build_maze", + "args": ["get_layout()"], + "codeRun": "new Maze(...)", + "description_html": "Collect and announce the diamonds.", + "setup_functions": [ + ["enableEmojiMode"], + ["enableOOP"], + [ + "setupGrid", + [ + [ + [1, 1, 1, 1, 2, 1, 1], + [1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 3, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [4, 0]] + ], + "checks": [ + { + "property": "position", + "value": [4, 6], + "error_html": "You didn't reach the end of the maze." + } + ] + }, + { + "slug": "left-turn", + "name": "A single left turn", + "setup_functions": [ + [ + "setupGrid", + [ + [ + [2, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [0, 0]] + ], + "checks": [ + { + "property": "position", + "value": [8, 5], + "error_html": "You didn't reach the end of the maze." + }, + { + "property": "direction", + "value": "right", + "error_html": "You seem to have done an extra unnecessary turn at the end." + } + ] + }, + { + "slug": "right-turn", + "name": "A single right turn", + "setup_functions": [ + [ + "setupGrid", + [ + [ + [1, 1, 1, 1, 1, 1, 1, 1, 2], + [1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 0], + [3, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [8, 0]] + ], + "checks": [ + { + "property": "position", + "value": [0, 5], + "error_html": "You didn't reach the end of the maze." + } + ] + }, + { + "slug": "forks", + "name": "Choose left if you can, otherwise choose right", + "setup_functions": [ + [ + "setupGrid", + [ + [ + [2, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 0, 3], + [0, 1, 1, 1, 0, 1, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1, 1], + [1, 4, 1, 1, 4, 1, 1, 1, 1], + [1, 4, 4, 4, 4, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [0, 0]] + ], + "checks": [ + { + "property": "position", + "value": [8, 2], + "error_html": "You didn't reach the end of the maze." + } + ] + }, + + { + "slug": "turn-around", + "name": "Turn around if you can't move straight, left, or right", + "setup_functions": [ + [ + "setupGrid", + [ + [ + [1, 1, 1, 2, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 4, 4, 0, 1, 1, 0, 1, 1], + [1, 4, 1, 0, 1, 1, 0, 1, 1], + [1, 4, 4, 0, 0, 0, 0, 0, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1], + [3, 0, 0, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [3, 0]] + ], + "checks": [ + { + "property": "position", + "value": [0, 7], + "error_html": "You didn't reach the end of the maze." + } + ] + }, + { + "slug": "forks-2", + "name": "Choose right if you can, otherwise choose left", + "setup_functions": [ + [ + "setupGrid", + [ + [ + [2, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 0, 0, 1, 1, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 0, 1], + [0, 1, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 0, 1, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 0, 0, 1], + [1, 4, 1, 1, 0, 1, 1, 0, 1], + [1, 4, 4, 4, 0, 1, 0, 0, 1], + [1, 1, 1, 1, 3, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [0, 0]] + ], + "checks": [ + { + "property": "position", + "value": [4, 8], + "error_html": "You didn't reach the end of the maze." + } + ] + }, + { + "slug": "poo", + "name": "Watch out!", + "description_html": "Collect the animals - not their waste!", + "setup_functions": [ + ["enableEmojiMode"], + ["enableOOP"], + [ + "setupGrid", + [ + [ + [2, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 0, 0, 1, 1, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 0, 1], + ["🐶", 1, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 0, 1, 0, 1, 1], + [0, 0, 0, "🐈", "🐶", 1, 0, 0, 1], + [1, 4, 1, 1, 0, 1, 1, 5, 1], + [1, 4, 4, 4, 0, 1, 0, "🐶", 1], + [1, 1, 1, 1, 3, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [0, 0]] + ], + "checks": [ + { + "property": "position", + "value": [4, 8], + "error_html": "You didn't reach the end of the maze." + }, + { + "property": "collectedEmojis", + "value": { "🐈": 1, "🐶": 2 }, + "error_html": "You didn't report the collected emojis correctly. We expected you to report:
{\"🐈\": 1, \"🐶\": 2 }" + } + ] + }, + { + "slug": "only-once", + "name": "Only once!", + "description_html": "Make sure you only collect things once!", + "setup_functions": [ + ["enableEmojiMode"], + ["enableOOP"], + [ + "setupGrid", + [ + [ + [3, 1, 0, 0, 2, 1, 1, 1, 1], + [0, 1, "🎀", 1, "🩷", 1, 1, 1, 1], + ["👑", 1, 0, 1, 0, 0, 0, "🩷", 1], + ["👑", 0, 0, 1, 0, 1, 4, 1, 1], + [0, 1, 4, 1, 0, 1, 4, 1, 1], + [5, 0, 0, 1, 0, 1, 4, 1, 1], + [1, 4, 1, 1, "🩷", 4, 4, 1, 1], + [1, 4, 4, 4, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["down"]], + ["setupPosition", [4, 0]] + ], + "checks": [ + { + "property": "position", + "value": [0, 0], + "error_html": "You didn't reach the end of the maze." + }, + { + "property": "collectedEmojis", + "value": { "🩷": 3, "🎀": 1, "👑": 2 }, + "error_html": "You didn't report the collected emojis correctly. We expected you to report:
{ \"🩷\": 3, \"🎀\": 1, \"👑\": 2 }" + } + ] + }, + { + "slug": "random-emojis", + "name": "Random Emojis!", + "description_html": "Different emojis every time!", + "setup_functions": [ + ["enableEmojiMode"], + ["enableOOP"], + [ + "setupGrid", + [ + [ + [1, 1, 0, 0, 0, 1, 1, 1, 1], + [3, 1, 6, 1, 6, 1, 1, 1, 1], + [6, 1, 0, 1, 0, 0, 0, 6, 1], + [6, 0, 0, 1, 1, 1, 4, 0, 1], + [0, 1, 0, 1, 1, 0, 4, 0, 1], + [0, 0, 6, 1, 1, 1, 4, 0, 1], + [6, 5, 6, 0, 6, 0, 4, 6, 1], + [1, 5, 5, 5, 0, 1, 1, 0, 2], + [1, 1, 1, 1, 1, 1, 1, 1, 1] + ] + ] + ], + ["setupDirection", ["left"]], + ["setupPosition", [8, 7]] + ], + "checks": [ + { + "property": "position", + "value": [0, 1], + "error_html": "You didn't reach the end of the maze." + }, + { + "function": "randomEmojisAllCollected", + "matcher": "toBeTrue", + "error_html": "You didn't report the collected emojis correctly." + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/maze/exercises/pacman/example.jiki b/bootcamp_content/projects/maze/exercises/pacman/example.jiki new file mode 100644 index 0000000000..622b21b55b --- /dev/null +++ b/bootcamp_content/projects/maze/exercises/pacman/example.jiki @@ -0,0 +1,222 @@ +function has_key with dict, key do + for each k,v in dict do + if k == key do + return true + end + end + return false +end + +function calculate_starting_position with maze do + for each row in maze indexed by row_idx do + for each cell in row indexed by col_idx do + if cell.is_start do + return {"row": row_idx, "col": col_idx} + end + end + end +end + +function calculate_starting_direction with maze, pos do + if space_free_below(maze, pos) do + return "down" + else if space_free_left(maze, pos) do + return "left" + else if space_free_right(maze, pos) do + return "right" + else if space_free_above(maze, pos) do + return "up" + end +end + +function check_boundaries with maze, row, col do + if row < 1 or row > 9 or col < 1 or col > 9 do + return false + end + return true +end + +function space_free with maze, row, col do + if not check_boundaries(maze, row, col) do + return false + end + + set square to maze[row][col] + + return !square.is_wall and square.in_maze and square.contents != "🔥" and square.contents != "💩" +end + +function can_move with maze, pos do + if pos["direction"] == "down" do + return space_free_below(maze, pos) + else if pos["direction"] == "up" do + return space_free_above(maze, pos) + else if pos["direction"] == "left" do + return space_free_left(maze, pos) + else if pos["direction"] == "right" do + return space_free_right(maze, pos) + end +end + +function can_turn_left with maze, pos do + if pos["direction"] == "up" do + return space_free_left(maze, pos) + else if pos["direction"] == "right" do + return space_free_above(maze, pos) + else if pos["direction"] == "down" do + return space_free_right(maze, pos) + else if pos["direction"] == "left" do + return space_free_below(maze, pos) + end +end + +function can_turn_right with maze, pos do + if pos["direction"] == "up" do + return space_free_right(maze, pos) + else if pos["direction"] == "right" do + return space_free_below(maze, pos) + else if pos["direction"] == "down" do + return space_free_left(maze, pos) + else if pos["direction"] == "left" do + return space_free_above(maze, pos) + end +end + +function space_free_above with maze, pos do + return space_free(maze, pos["row"] - 1, pos["col"]) +end + +function space_free_below with maze, pos do + return space_free(maze, pos["row"] + 1, pos["col"]) +end + +function space_free_left with maze, pos do + return space_free(maze, pos["row"], pos["col"] - 1) +end + +function space_free_right with maze, pos do + return space_free(maze, pos["row"], pos["col"] + 1) +end + + +//---- + + + + + + + +function check_direction with direction do + set space to look(direction) + return !space.is_wall and space.in_maze and space.contents != "🔥" and space.contents != "💩" +end + +function current_square with maze, position do + return maze[pos["row"]][pos["col"]] +end + +function handle_move with maze, pos do + move() + + if(pos["direction"] == "up") do + change pos["row"] to pos["row"] - 1 + else if(pos["direction"] == "down") do + change pos["row"] to pos["row"] + 1 + else if(pos["direction"] == "left") do + change pos["col"] to pos["col"] - 1 + else if(pos["direction"] == "right") do + change pos["col"] to pos["col"] + 1 + end + change pos["square"] to maze[pos["row"]][pos["col"]] + + return pos +end + +function collect with current_square, emojis do + set emoji to current_square.contents + if emoji == "" do + return emojis + end + + current_square.remove_emoji() + + if has_key(emojis, emoji) do + change emojis[emoji] to emojis[emoji] + 1 + else do + change emojis[emoji] to 1 + end + + return emojis +end + +function turn_around do + turn_right() + turn_right() +end + +function direction_string_to_num with direction do + if direction == "up" do + return 0 + else if direction == "right" do + return 1 + else if direction == "down" do + return 2 + else if direction == "left" do + return 3 + end +end + +function direction_num_to_string with direction do + if direction == 0 do + return "up" + else if direction == 1 do + return "right" + else if direction == 2 do + return "down" + else if direction == 3 do + return "left" + end +end + +function change_direction with direction, changer do + set num to direction_string_to_num(direction) + change num to (num + changer) % 4 + return direction_num_to_string(num) +end + +function turn_if_required with maze, position do + if can_turn_left(maze, position) do + turn_left() + change position["direction"] to change_direction(position["direction"], 3) + return position + else if can_move(maze, position) do + return position + else if can_turn_right(maze, position) do + turn_right() + change position["direction"] to change_direction(position["direction"], 1) + return position + else do + turn_around() + change position["direction"] to change_direction(position["direction"], 2) + return position + end +end + +set maze to get_initial_maze() + +set pos to calculate_starting_position(maze) +change pos["square"] to maze[pos["row"]][pos["col"]] +change pos["direction"] to calculate_starting_direction(maze, pos) + +set emojis to {} + +repeat_until_game_over do + change pos to turn_if_required(maze, pos) + change pos to handle_move(maze, pos) + change emojis to collect(pos["square"], emojis) + + if pos["square"].is_finish do + announce_emojis(emojis) + end +end \ No newline at end of file diff --git a/bootcamp_content/projects/maze/exercises/pacman/introduction.md b/bootcamp_content/projects/maze/exercises/pacman/introduction.md new file mode 100644 index 0000000000..48519237ae --- /dev/null +++ b/bootcamp_content/projects/maze/exercises/pacman/introduction.md @@ -0,0 +1,74 @@ +# Omniscience + +So far in the maze exercises, you've gone from using a high-level `can_turn_right()` function, to using a slightly lower-level `look(direction)` function. +In this exercise we take this one step further and leave navigation entirely in your hands! + +This is one of the biggest and most complex exercises you've done so far. +It's **really** important to take it step by step. + +### How it works + +When you start the exercise you can use the new `get_initial_maze()` function to get all of the squares arranged in a list of lists. +Each list is a row (so `squares[1]` is the first row, and `squares[1][1]` is the top-left square). + +Each square is an Instance of the Square, exactly the same as in the "Squares Not Strings" exercise. + +There is one change, and that's the addition of a new method `remove_emoji()`. Previously, `remove_emoji()` was a function you called, passing in a square as an input. +Under the hood, that used the square's method. +Now you should use the method directly. + +## Instructions + +Your job is to upgrade your code to deal without having the built-in `look` function. + +You should navigate around, keeping track of where you are, and working out where you can go next. +Collect the emojis and announce them at the end. + +We've given you your "Squares not Strings" code as your starting point, and give you a variety of step-by-step scenarios to work through. +I highly recommend working through them one-by-one! + +Some notes: + +- Remember to stick to the original algorithm you were given. +- The easiest starting point is probably to implement your own `look` function. +- You always start on the starting square +- You always start looking into the maze (i.e. into a square that's not a wall or off the board). If there are multiple options, prioritise by downward, leftward, rightward, upward. +- Take this one step at a time. Start by positioning your character correctly, then get their initial direction right, then solve each scenario one-by-one! + +## Recap + +### The Square class + +As a reminder, instances of the square class have a few properties you can use: + +- `is_start`: A boolean representing whether this is the start square. +- `is_finish`: A boolean representing whether this is the finishing square. +- `is_wall`: A boolean representing whether this is the all square. +- `contents`: A string containing an emoji, or an empty string. + +It also now has the method: + +- `remove_emoji()`: Removes the emoji from the square. + +### Emojis + +Special emojis. Avoid these: + +- `"🔥"` (Careful!) +- `"💩"` (Ewww) + +Pick up the rest! + +### Library Functions + +This exercise will probably need your [`my#has_key`](/bootcamp/custom_functions/has_key/edit) library function! + +### Functions + +You have following game functions: + +- `move()` +- `turn_left()` +- `turn_right()` +- `announce_emojis(dict)` +- `get_initial_maze()` diff --git a/bootcamp_content/projects/maze/exercises/pacman/stub.jiki b/bootcamp_content/projects/maze/exercises/pacman/stub.jiki new file mode 100644 index 0000000000..ab32a3cb84 --- /dev/null +++ b/bootcamp_content/projects/maze/exercises/pacman/stub.jiki @@ -0,0 +1 @@ +{{EXERCISE:maze/squares-not-strings}} \ No newline at end of file diff --git a/bootcamp_content/projects/maze/exercises/pacman/task-1.md b/bootcamp_content/projects/maze/exercises/pacman/task-1.md new file mode 100644 index 0000000000..0aa2d70289 --- /dev/null +++ b/bootcamp_content/projects/maze/exercises/pacman/task-1.md @@ -0,0 +1,3 @@ +# Task 1 + +Use the new objects to solve the mazes.