Data Science Challenge
This week we are consolidating the skills we have learned over the past four weeks in order to tackle a practical challenge. In this notebook, you will find the data science challenge, in which you will have to write code that will navigate a small virtual robot around a maze.
Please Note: These challenges are designed to difficult! Do not worry if you are unable to complete a challenge in the time given. The important thing is that you are building resilience and practicing problem-solving skills - everything else is secondary!
You’re currently viewing the Quarto version of the Data Science Challenge notebook. Please note that, due to differences in how Quarto and Jupyter render outputs, some functions may behave slightly differently or run more slowly. You’re welcome to use this version for today’s material, but we strongly recommend using the version on Jupyter if possible.
Table of Contents
Preliminaries
This notebook draws on the knowledge you’ve built over the last four weeks. This challenge assumes knowledge of only the material from the beginner notebooks - however, you are welcome to draw upon material from the intermediate and advanced notebooks as well! The tasks are designed to be accessible to everyone.
Before getting started, you’ll need to know how to generate random numbers. Here’s a quick primer.
To load in a package (collection of functions) that will let you generate random numbers, you must run the below code:
You only need to run the above line of code once (unless you restart the notebook, in which case you will have to run it again after restarting). Once you have run the import code you will be able to generate random numbers using the random.random() and random.randint() functions like so:
Try running the above box a few times and observe how a new value is produced each time you call random or randint. For today’s class, this is all the knowledge we will need concerning random numbers in Python. We’ll explore random number generation in greater depth later on in the course.
Tutorial: The Robot Maze
Today, you will be programming a robot to navigate a small maze. To get started, run the code box below. This will load in the RobotMaze function which we will be working with today.
Creating a Robot Maze
The RobotMaze function will create a new randomly generated maze with a robot inside it. Try running the below code:
We can view the robot in the maze using robot.render().
Let’s break down what we can see here. The maze contains:
- White Squares: Open spaces, which the robot has not yet visited.
- Black squares: Walls. The robot cannot walk into these.
- Grey squares: These are open spaces that the robot has visited previously.
- A red circle with a white arrow: This is the robot. The arrow tells us which way it is facing.
- A green circle with a T: This is the target. This is where the robot must move to.
At the top of the maze, we see the title Robot Maze - Run #1. Here, the number #1 refers to the number of times we have attempted to solve the maze.
At the bottom of the maze, we see the Robot Console. This is a handy message box, which we shall use to display messages from the robot throughout this task.
Moving the Robot
Here are some instructions you can give the robot:
robot.move(): Running this will cause the robot to take one step forward in the direction it is currently facing.robot.turn(): This function takes a string as input. It will turn the robot to the left if the string is"LEFT", right if the string is"RIGHT", behind if the string is"BEHIND"and ahead if the string is"AHEAD". The robot always turns relative to the direction it is currently facing (that is, where the white arrow is pointing).
Running the below code will give the robot the instructions.
The robot now knows what it is going to do. To execute the instructions, you must now run robot.render().
Note: If the robot bumps into a wall, it will not move forward. Instead, it will print a message to the
Robot Consoletelling you it has bumped into a wall.
If the robot is moving too fast or slow, you can change the speed at which it moves using the robot.delay parameter. For instance:
Note: Each time you run
robot.render(), the robot will begin moving from it’s last location on the previous render. Try re-running the above box a few times (maybe with a smaller delay!) to make sure you understand how this works.
Resetting and Saving the Maze
If you want to reset the maze, and send the robot back to the start, you can use the robot.reset() command like so. Notice that the title will now say Robot Maze - Run #2, but the Robot Console will keep the robot’s message history.
Every time you run the robot = RobotMaze(), a new random maze is generated. Sometimes you might want to save a particular maze to come back to later. You can do this by running robot.get_maze_id(). For instance, to save the maze you have above, you can run the following:
You can then load the same maze again later on using my_maze_id. Try commenting out and uncommenting the below lines of code to test your understanding.
Recall: You can interrupt a cell that is running in Quarto by pressing the
Start Overbutton.
Aims and Limits
Your aim in this challenge to write code that moves the robot to the target. However, there is a catch - the robot only has a finite amount of fuel!
The robot begins with 100 units of fuel. Each call to robot.move() and robot.turn() costs one unit of fuel. Once the robot has no fuel left, it will not be able to move.
Here are some functions you can use to check on the robot’s status:
robot.at_target(): This will returnTrueif the robot is on the target square andFalseotherwise.robot.check_fuel(): This will tell you how much fuel the robot has left. The amount will be returned as an integer between0and100.robot.print(): This will print a message to the robot console.
The below code uses the above commands. See if you can understand what it is doing.
Finally, the robot is equipped with a sensor that can detect what type of square is in a given direction.
To use the sensor, call the robot.sense() function with one of the following inputs: "AHEAD", "LEFT", "RIGHT", or "BEHIND". The function returns a string describing the square in that direction, returning either "WALL", "EMPTY", or "BEEN_THERE" if the robot has already visited it.
Have a look at the below code for an example:
Note: Using the sensor does not require fuel! That is, it may be better to sense before you move…
Challenges
We’re now ready to get to grips with the robot environment!
Task 1: Getting Started
The below code will load a simple four by four RobotMaze.
In the below box, use the robot.move() and robot.turn() functions to navigate the robot to the target.
Try to write your code as concisely as possible.
Hint: You may want to use for loops like we did in the example in the Aims and Limits section.
Task 2: Sensing Danger
We’re now going to look at a pre-written robot maze program. In the below code box, you will see there is a function named my_controller. See if you can determine what this controller does before reading ahead.
The nice thing about writing our commands for the robot inside a function is that we can now run the entire set of instructions simply by calling the function, like so:
Try updating the my_controller function so that the robot first checks for a wall in front of it before attempting to move forward. If a wall is detected, the robot should stay in place and not move.
Task 3: End of the Road
Continuing with the my_controller function, modify your code to identify if the robot is:
- At a dead-end (a square surrounded by three walls),
- At a corner (a square bordering two walls whose corners touch),
- In a corridor (a square with two walls bordering it on opposing sides),
- Next to a single wall (a square with only one wall bordering it),
- On an unbordered square (a square sharing no edges with a wall).
Use the robot.print() function to print a message to the console stating which type of square the robot is on.
Task 4: Turning Back
Write your own controller function which does the following: - Senses the squares around the robot. - If there at least one of the surrounding squares is "EMPTY", the robot should choose an "EMPTY" square at random and move to it. - If there are no "EMPTY" squares, the robot should choose at random from the "BEEN_THERE" squares surrounding it and move to the chosen square.
Task 5: Final Challenge
It is now up to you!
By doing your own research on maze solving algorithms, choose a method to solve the maze and try writing it up as a controller function. Can you solve the maze faster than the my_controller robot, or your solution to Task 4?
Good luck!