Module: Coding for Non-Coders
Author: Markleford Friedman
Revised: 2020-06-30 09:30
Generated: 2020-06-30 09:30

Coding for Non-Coders

Here's a crazy idea... Let's learn to code!

Welcome

First off, we'd like to celebrate your curiosity, or perhaps even your bravery!

We're told a million reasons why we'll NEVER be able to code. They say you're "too old", or that it's "too late". Some people claim it's an intrinsic gift which you have to be born with. Others whisper that you have to be a math genius to even begin to comprehend coding.

So in the face of everyone telling you "no", we're glad that you're at least considering an optimistic "well, maybe!"

Learning objectives

Anyone can code! Those who continue onward will be challenged to solve simple graphical maze puzzles of gently increasing complexity. You'll write guided code based on a simplified subset of the Python language. Absolutely zero prior coding experience is required, and partners are welcome -- bring a friend to share the fun!

Have you been told that coding is too hard for "ordinary people"? That's really not true! Part of the problem is that most coding classes try to dump too much information on you in too short of time. This introductory tutorial will give you just a taste of coding (an appetizer, perhaps), so that you can determine for yourself whether you'd like to learn more.

Only basic computer and typing skills are required, and no math will be involved! We'll have you go to a website and download some files, to start. Either Windows or Mac OS computers can be used, so long as you have the capability to install a Python language environment on it. And again, we know the power of teamwork, so by all means invite a partner if you want a friendly face to share the learning experience!

What is coding really about?

When it comes down to it, coding is about solving problems. More specifically, it's about solving a big problem by breaking it into smaller tasks that are manageable with a simpler set of tools.

If you've ever planned a big project and had to figure out how to break it into smaller chunks in order to make progress, you already have a head-start on the skills needed. It's likely that before starting, you made a list of steps that needed to happen in a certain order. You broke down big jobs into smaller jobs.

Code is a lot like that project plan: you're creating a list of instructions for work to be done. In this case, though, the computer will be doing the work that you tell it to do. The trick is to make those instructions simple enough for a computer to understand.

Despite what you hear about Artificial Intelligence, computers are actually pretty dumb... but they're always super-consistent. They do exactly what you instruct, every single time, but they will do ONLY what you tell them to do.

Our approach

We're going to give you a virtual on-screen robot to control. It will follow your code instructions to navigate around a grid to find an exit.

At first, you will tell it explicitly what to do. Later, you will teach it how to use its sensors to act on conditions that you specify.

Eventually, you might be even be able to teach your robot to escape a maze all on its own! But just like programming your virtual robot, we'll teach you the simple stuff first before increasing the complexity of instructions.

And we promise: no math genius-level skills are required!

Getting started

It's a fact: getting a computer configured to start coding can be quite frustrating!

You might have a desire to learn how to code, but the fine details of software installation and systems administration can quickly dampen that enthusiasm. Fortunately, that process is getting a little easier, these days.

Step 1: Install a Python environment on your system

Since we're going to use Python as our first coding environment, you'll need to pick a variety to go with.

The Python language itself is currently in a "major revision" state called Python 3. To be specific, the latest version as of this writing is Python 3.8, but any version in the 3.x range could potentially be used for this tutorial.

The two Python environments which we'll suggest you pick between are IDLE and Mu. We like them both, but only one is needed to get started.

IDLE

IDLE is a development environment provided as part of the official Python distribution. The official web site has the files and instructions to help you get started.

Download and install latest Python 3.x

Mu

Mu is a simple Python editor for beginner programmers. These clever developers have figured out how to remove many of the annoying hurdles for you!

Download and install latest Mu

Other environments

If you've set up a different Python environment previously, there shouldn't be any obstacles to you using it with this tutorial, so long as it supports a recent version of Python 3.

Step 2: Prepare the robot files

We've already coded the basic "operating system" of the virtual robot that you'll be controlling, so all you have to worry about is giving it basic instructions.

You'll need to download these files and unzip them into a folder (or directory) where you'll also save your own code. This could be anywhere on your computer you want, but some sort of "home" directory or "My Documents" folder would probably be the easiest to remember.

Download and expand Zip archive file

Step 3: Run your first robot program

Find the file solution_1.py within your (Windows) File Explorer or (Mac) Finder window.

If you installed IDLE, you should be able to right-click on this file and pick the "Edit with IDLE" entry in the menu.

If you installed Mu, run the Mu application and click the "Load" button to find this file.

In either case, once inside your Python environment with the file loaded, you should be able to use the Run menu command (alternately: F5 key) to run the code.

If everything has been installed correctly, you should see a screen that looks like this:

(Activity "mazey1" not loaded: mismatched type or js?)

Once the animation completes, you can dismiss the window manually by clicking in the window. We'll tell you more about this window later.

Summary: Getting started

Here's what we've accomplished in your first few steps:

  • In this section, we hope that you've learned that coding isn't quite as hard as people say it is.
  • By this point, you've managed to install Python on your computer and run your first program.
  • So now, finally, we should be ready to actually start learning to code!

The basics

What does "coding" actually mean? What is code? How will we learn it?

Let's establish some fundamentals to build upon...

Programs, code, and functions

At its simplest, a program is a series of instructions to be done in a specific order. Unlike a to-do list, you can’t complete these items in a mixed-up order. It's more like a recipe that you need to follow step-by-step, rather than an unordered checklist.

This set of instructions, when sequenced together, is called code. Code is written as a cohesive work unit to solve a specific problem. This problem might be the whole task in mind (the whole program itself!), or just one small part of it, but it's all considered code.

One fundamental instruction type that we constantly rely on in coding is known as a function. It's one of those smaller work units that solves a smaller subtask in a self-contained, repeatable fashion. When we "call" a function by its name, it executes some sort of work that's defined in advance. This is what's known as a function call, and it's your bread and butter in coding.

Abstraction, modules, and sharing

The work that a function accomplishes can range from fairly simple to very complex, to the extent that a function can call other functions, which in turn call more functions.

Pretty much all of coding relies upon efforts previously created by others, to the extent that the Python language itself has basic functions that you will use to write your own code. The great thing about this, though, is you don't have to know how these functions do their work -- you just have to know what work they're supposed to do. Once you learn the function's name and what it does, you can call it in the right situation to accomplish your coding goals.

Coding is always an effort that builds upon other code. When using the Python language, you'll rely on modules, which are essentially collections of functions that were thought to be useful to the coding world at large. For example, when it comes to your virtual robot, we've already provided a module to draw it on the screen, so that all you have to do is learn how to control it by calling the appropriate functions.

Furthermore, if someone has solved a problem like yours in the past, they may have decided to write their functions and modules in a more generic or flexible way, such that other coders can use their code without needing to know how it's done. When such code is shared online, they're typically known as an open source project.

Introducing the grid

The following graphic depicts a space where your robot can run around. We'll call this a grid.

(Activity "mazey2" not loaded: mismatched type or js?)

The grid has a cursor (an arrow indicating the robot’s location and facing direction) and an exit (represented by the square with the big "X"). The grid can be of any dimensions.

Your goal is to get your robot to the exit.

We'll navigate by providing cardinal directions to your robot: north, south, east, west. North is always assumed to be the top of the screen.

Code samples, files, and comments

In the previous example, the exit is one step to the north of the robot's starting location. This tutorial will display code that you might write to solve an exercise by putting it in a retro black-and-green box, as follows:

Code block follows.

move("north")

End of code block.

If there's a grid depicted in this tutorial, there will be a corresponding "exercise" file in your CodingRobot folder. For example, here the grid labeled as "Exercise # 2" will be in a file named exercise_2.py.

Similarly, most of the grids depicted will have a corresponding "solution" file. So in this instance, "Exercise # 2" will have a solution file named solution_2.py.

Within both of these types of files will be some starter code that you shouldn't modify. It is called out by what's known as a comment, which is essentially code that's ignored as being simply a human-readable note for coders, not for the computer itself.

The most important usage of this is that exercise files will provide a comment that indicates where you need to place your code. It looks like this:

Code block follows.

# Write your code immediately below this comment line! (and without a #!)

End of code block.

As instructed, you will write your code immediately below that line (and without a #).

Summary: The basics

We've learned a little bit about coding in general, and more specifically about the environment that we'll be using. Here are a few reminders of the important parts:

  • The art of coding is to know how to use functions in the most efficient way to tell a computer how to accomplish a larger task.
  • A function is a self-contained work unit that performs subtask in a repeatable fashion.
  • The process of abstraction is to break big problems into smaller, simpler ones that we can solve with the functions we know.
  • The grid depicts your virtual robot with an arrow cursor, and your goal is to get it to the exit denoted by "X".
  • Each grid in this tutorial will have a corresponding "exercise" file, and most will also have a "solution" file.
  • A comment is a line starting with a #. They're for your human eyes to read for additional information about how the program works, but the computer itself will entire ignore anything on that line.

Function calls

We've already heard that a function is a self-contained code unit that performs subtask in a repeatable fashion. We don't necessarily have to know how it does its work, so long as we know what it does. A function provides a potentially complex set of operations in an easily-called interface.

At long last, let's get down to actually coding by making function calls!

Call one function

With this first grid, after configuring your system to run, the coder only has to provide one function call to get your robot to the exit.

(Activity "mazey3" not loaded: mismatched type or js?)

In the text editor of the Python environment that you installed previously, load the exercise_3.py file and add the move("north") function call to the code below the comment.

Here's an example of what your code should look like.

Code block follows.

# Write your code immediately below this comment line! (and without a #!)

move("north")

End of code block.

Once you've added this one line of code, run the program by using the Run menu, or press F5. A new window with the grid drawing will appear. You should see the cursor advance one square to the north.

Upon reaching the exit, a text message will be shown. Congratulations! You've coded your first program! Afterward, just click the grid to dismiss the window.

Function names and parameters

Our basic movement function is move(). This is all you'll need for the first few exercises.

The function name generally tells us something about what it does, so long as someone made the effort to name it meaningfully. If they named it bonkerzzz(), it wouldn’t make much sense to coders (like you) who have to use it later.

In the Python programming language, we typically see function names in lowercase, with words separated by underscores. You might eventually start to notice a pattern of functions named with a verb and a noun object, such as print_report() or inflate_tire(). This is more of a convention than a requirement, but the consistency makes it easier to remember which function you're after.

So by design, this function does pretty much what you'd think it will:

Code block follows.

move("north")

End of code block.

The parentheses after the function name allow us to specify extra information about how the function will do its work. Those little extra pieces of info are called parameters.

For now, our move() function only takes one parameter, which is the cardinal direction to move in. When called once each, and if there are no walls in your robot's path, your robot would wind up where it started.

Code block follows.

move("north")
move("east")
move("south")
move("west")

End of code block.

When a function is used in your code with parameters in place, that's a function call. So in the above code snippet, we see four function calls, but only one shared function.

Call a function twice

This new grid looks almost the same, but adds one more step between your robot and the exit.

(Activity "mazey4" not loaded: mismatched type or js?)

Now you're two steps away from your goal. Since you already know how to move one square to the north, all you have to do is call that function twice to reach the exit.

Similar to last time, within the text editor of the Python environment that you installed previously, load the exercise_4.py file and add the code below:

Code block follows.

# Write your code immediately below this comment line! (and without a #!)

move("north")
move("north")

End of code block.

Again, after adding your code use the Run menu (or F5) to run the program. Hopefully your robot found the exit!

So remember: You can call a function more than once to achieve your objective, one step at a time.

One more time

Just to be sure we've learned the lesson, how would you solve this grid?

(Activity "mazey5" not loaded: mismatched type or js?)

Remember, your robot listens to your instructions literally, taking one step for each call to move("north"). So when you are three steps away from the exit, how many function calls must you make?

All you have to do is call move("north") one additional time, for a total of three calls.

Code block follows.

move("north")
move("north")
move("north")

End of code block.

Mixing different function calls

(Activity "mazey6" not loaded: mismatched type or js?)

It's almost always the case that you'll have to use more than one unique function call to accomplish your goals. Here, the exit is to the north and east. We’ll now add the move("east") function call to allow a solution.

When you're doing more than one type of task as part of a whole, you may recall that a program is more like a recipe than a to-do list. Functions in a program are executed in the order that they're called, line by line, from top to bottom.

So, given the two function calls move("north") and move("east"), how might you instruct your robot to get to the exit now? Write your code and run the program to see if you can reach the exit.

You'll have to use both function calls twice to get your robot to the exit. This is but one possible solution:

Code block follows.

move("north")
move("north")
move("east")
move("east")

End of code block.

But in which order?

Notice that you could have called those functions in pretty much any order, so long as there were two of each.

For instance, this way:

Code block follows.

move("north")
move("north")
move("east")
move("east")

End of code block.

Or this way:

Code block follows.

move("east")
move("east")
move("north")
move("north")

End of code block.

Or a few other ways, in fact! This sort of simplicity is a very rare case, though, so don't rely on it.

Function order matters

It's important to consider the result of each function before understanding how the next function call will behave.

In our previous example, function order didn't matter because there are no other constraints on movement across the grid. But what if we add walls which you can't move through?

(Activity "mazey7" not loaded: mismatched type or js?)

If your robot runs into a wall, the program will immediately end, so we’ll have to think a little harder about instructing our robot, otherwise the path will be blocked and it’ll go nowhere.

Remember: Functions in a program are executed in the order that they're called, line by line, from top to bottom.

Now there is only one solution to the grid. What is it?

Code block follows.

move("north")
move("east")
move("east")
move("north")

End of code block.

Summary: Functions

Here's some things we've learned about functions:

  • A function provides a potentially complex set of operations in an easily-called interface.
  • Functions are named meaningfully, so that we can remember what they do.
  • A function is called by typing the name followed by a set of parentheses, which contain parameters.
  • When your program is run, the functions you specify will be executed in order, line by line, from top to bottom.
  • The order of your functions matters!

Iteration: for-loop

One concept that's used all the time in coding is iteration. It allows us to repeat a set of instructions a number of times without having to type them all out.

Long distance hike

This grid might test your patience a bit...

(Activity "mazey8" not loaded: mismatched type or js?)

Oh no! That exit is a long way away! Ten steps away, in fact, and to the west.

We can approach this as we did earlier, of course. How do we reach the exit using move("west")?

Hopefully you used cut-and-paste for all this repetition!

Code block follows.

move("west")
move("west")
move("west")
move("west")
move("west")
move("west")
move("west")
move("west")
move("west")
move("west")

End of code block.

Long distance simplified

But you’re probably thinking, "There’s got to be a better way..."

Let's use the same grid, with a ten-step journey to the west:

(Activity "mazey9" not loaded: mismatched type or js?)

By using iteration, we can avoid all of the function repetition from our last solution. One way to do iteration in the Python language is with the for statement.

Here's a solution using for: just copy this code in your editor and run it.

Code block follows.

for step in range(10):
    move("west")

End of code block.

That’s a lot more tidy, two lines of code instead of ten, while completing the same solution.

For-loop syntax

Okay, brace yourself: using the for statement is a little more complex than a simple function!

Let's take another look at the previous solution:

Code block follows.

for step in range(10):
    move("west")

End of code block.

Here, range() is a function that requires a number as a parameter, representing how many steps to repeat. Since we want to repeat ten times, we’ve put the number 10 in the parentheses following the function name.

The line starting with for will always end in a colon (":"), indicating that a code-block will follow. Code-blocks are smaller collections of functions and instructions, intended to be executed as a logical unit. Here, we find only one function in the code-block, move("west").

All of the lines in your code-block need to be indented. The Python language typically uses four spaces (or a tab) for indentation. When running your program, it will know that indented lines are inside the code-block, whereas the first unindented line will be considered outside the code-block, meaning that it won't be repeated in the iteration.

This whole syntax, namely a line with a for statement ending with a colon and followed by an indented code-block, is typically called a for-loop.

For-loops and "program flow"

One thing to point out here: though we've said that "functions in a program are executed in the order that they're called, line by line, from top to bottom", a for-loop changes this a bit.

When calling the indented functions in the for-loop's code-block, as soon as it gets to the end of the block the program will skip up, back to the for definition line, to determine whether the code-block should be repeated one more time. If it has already exceed the specified number of steps, it skips down past the end of the code-block, to the next unindented line outside the loop.

As an example:

Code block follows.

for step in range(4):
    func_one()
    func_two()
    
func_three()

End of code block.

Functions func_one() and func_two() are indented, so they're inside the code-block, sometimes known as "inside the loop". They will repeat four times each, in alternating order, before the loop ends. At this point, the unindented func_three() is called once, being outside the loop.

If we wrote out the order that the individual functions are called, it would look like this:

Code block follows.

func_one()
func_two()
func_one()
func_two()
func_one()
func_two()
func_one()
func_two()
func_three()

End of code block.

Whew, that's kind of a big change from what we told you before, right? But that's the nature of iterators: they loop a code-block until a condition is satisfied, then they continue at the first line outside the code-block.

In other words, when the maximum number of steps in a for-loop is done, we skip past it to new code.

There are some deeper details about the use of for-loops that you'll learn later, but for now you can think of it as a reusable "stock pattern".

And back the other way

Let's put this new knowledge to work. With this new grid, we'll go back east.

(Activity "mazey10" not loaded: mismatched type or js?)

How would you handle this 7-step journey, using a for-loop?

We put the number 7 in the parentheses of the range() function, and make sure the indented function underneath the for statement is move("east")

Code block follows.

for step in range(7):
    move("east")

End of code block.

Pre and post for-loop

You don't always use a for-loop by its lonesome. Sometimes you'll need to execute another function before or after a for-loop, sometimes both!

(Activity "mazey11" not loaded: mismatched type or js?)

This grid has the same robot and exit location, but there are walls in the way. You'll still use the same for-loop as before, but you'll need to move above or below the wall at the beginning and end of your travel.

Note that those move() calls will be outside the for-loop, thus not part of the code-block, so they will not be indented.

Here is one solution, traveling above the wall. You could just as well go south first, to go underneath the wall.

Code block follows.

move("north")

for step in range(7):
    move("east")

move("south")

End of code block.

Two hikes

Sometimes your journey might require more than one for-loop.

(Activity "mazey12" not loaded: mismatched type or js?)

To reach the exit of this grid, we would use two for-loops, one after the other, each with a different direction:

Code block follows.

for step in range(3):
    move("south")

for step in range(8):
    move("west")

End of code block.

Note that the second for statement is not indented, since we want each for-loop to be treated separately.

Here, the move("south") function will repeat three times, until the for-loop knows that it's gone past the limit. At that point, the program jumps over the code-block to the next unindented line, which is the next for-loop.

Try it out

Here's another two-part journey:

(Activity "mazey13" not loaded: mismatched type or js?)

Your turn: code two for-loops to get your robot to the exit.

Here is one solution: what would the other one be?

Code block follows.

for step in range(4):
    move("south")

for step in range(6):
    move("east")

End of code block.

Each for-loop happens in order

Notice in the two previous examples that the order of the for-loops didn't matter. You could have reversed the order of your solution to reach the same goal. This is because there were no other conditions in play to force you to solve it in a particular way.

As with the lesson on function order, most often you'll have to carefully consider the result of each for-loop before specifying the next one. If we add walls to the grid, that requires a specific order your solution.

Try this one:

(Activity "mazey14" not loaded: mismatched type or js?)

You're going to need to use three for-loops in your solution.

Code block follows.

for step in range(4):
    move("north")

for step in range(4):
    move("west")

for step in range(4):
    move("south")

End of code block.

Larger code-blocks

Thus far, we've only had one function within a code-block.

In order to demonstrate larger code-blocks, we're going to climb a staircase:

(Activity "mazey15" not loaded: mismatched type or js?)

While it looks more complex than our previous examples, where you used one or more for-loops to travel in straight lines, it's actually pretty easy.

The trick is to consider that each step requires that you call move("north") followed by move("east"). After doing this for five steps, your robot will have reached the exit.

So, whereas writing it all out long-form would look like this:

Code block follows.

move("north")
move("east")
move("north")
move("east")
move("north")
move("east")
move("north")
move("east")
move("north")
move("east")

End of code block.

Here's how it looks coded with a for-loop. Go on and run the program with just this code below to watch it in action.

Code block follows.

for step in range(5):
    move("north")
    move("east")

End of code block.

Since both functions are within the indented code-block, they'll be called once each (and in that order) before the for-loop repeats.

This sort of solution requires that you to get better at pattern recognition, which is also an essential skill for coding. Again, you're looking at a larger problem and figuring out how to break it down into smaller problems. Here, the smallest problem is "climb one step". After you accomplish that, you find the bigger problem isn't all that hard to solve.

Bonus exercise

You could solve this grid with a long series of move() calls, but if you look at it for a bit you'll see some repeated elements to help break down the problem.

(Activity "mazey16" not loaded: mismatched type or js?)

You might opt to think of it like you're leaping three hurdles on your way to the finish line. If you know how to leap one hurdle, you can use iteration to complete the set.

Hint: Use a for-loop that repeats three times, with six functions in the code-block.

Code block follows.

for step in count(3):
    move("west")
    move("north")
    move("west")
    move("west")
    move("south")
    move("west")

End of code block.

Summary: Iteration

It's a big step to introduce iteration into your coding! Though it looks a bit complex at first, you'll probably learn it pretty quickly because it's used all the time.

  • Iteration allows us to repeat a set of instructions a number of times without having to type them all out.
  • The first iterator that we've learned is the for statement. Combined with an indented code-block, it's more commonly called a for-loop.
  • You can use more than one for-loop to help solve the larger problem.
  • You may also need to use separate function calls before or after the loop itself.

Handling data: parameters, return values, and variables

Functions have a lot more potential than just accomplishing a simple or complex task, only to call another function immediately after. But before we can fully explore their usefulness, we'll need to look at them in a bit more depth than a "to-do list".

We can send data into functions and receive data back in return. You can even temporarily save the data output from one function and use it as the input for another.

Parameters: "Input data"

Some functions can take parameters to do their job in a specific way, or to approach a similar group of tasks in a more flexible manner. A parameter is a small bit of information which has a known "value".

We've already mentioned parameters before as an aside, and have been using them all along. Your move() function takes a "string" which is a cardinal direction. In your for-loop, you specify a number for range() as the parameter.

Parameters are placed within the parentheses after the function name. If a function takes more than one parameter, they are separated by commas. Here's the general format:

Code block follows.

func_name( param1, param2, param3, ...etc...)

End of code block.

So if you have a hypothetical function to check lottery numbers, you might call it with this:

Code block follows.

check_lotto( 1, 7, 18, 23, 41, 47 )

End of code block.

Using strings as parameters

As mentioned, you can send text values as parameters to some types of functions. To do this, you put the text within quotation marks to form what's called a string.

If there were a hypothetical function that painted a picture using the color names you provide as parameters, it could be called like this:

Code block follows.

paint_picture( "cyan", "magenta", "yellow", "black" )

End of code block.

So far as an actual robot function that uses a string parameter, you could use this function:

Code block follows.

pause("Here is your message")

End of code block.

Any text will do, so long as it's in quotation marks to make it a string.

Try it out on this grid:

(Activity "mazey17" not loaded: mismatched type or js?)

Use this code as your solution, then run the program:

Code block follows.

for step in range(4):
    pause("Let's go east!")
    move("east")

End of code block.

Note that for each iteration of the for-loop, the program will pause and wait for a click before moving the robot. This is because the pause() function call is within the code-block.

What would happen if you changed the code to this?

Code block follows.

pause("Click to start your journey!")

for step in range(4):
    move("east")

End of code block.

Here, the call to pause() is outside the for-loop, so it will only be called once before the robot starts moving.

The print() function

One essential built-in function for Python is print(). It can take several parameters, and even accept a mix of numbers and strings.

For each parameter, it will print out each value to your text window, separated by a space. So if you called it this way:

Code block follows.

print ("cherries", 777, "banana", 88)

End of code block.

You'd see in your text window:

Code block follows.

cherries 777 banana 88

End of code block.

print() is convenient for all kinds of information reporting in your code. Often times, the purpose of your program itself is to print out information. Sometimes, though, you might want to use it to figure out what your code is doing by printing out status messages along the way: this is sometimes called print debugging.

Return values: "Output data"

Some functions can produce a return value. This value might tell you something about the state of the system, as a string, or maybe it's the numeric result of a complex equation.

What you do with this return value is up to you. Maybe you'll send it to the print() function so that you can check the result on-screen. Or perhaps you'll immediately use this value as an input parameter to another function.

Your first sensor function: look()

To demonstrate, let's add a new capability to your robot, a "sensor" to determine the state of the grid around it. It will provide this information to us as a return value.

look() is a "sensor function" that we can start using immediately. It uses a direction string as the sole parameter:

Let's use it to look around this grid:

(Activity "mazey18" not loaded: mismatched type or js?)

We can call the print() function to report what we see, using another call to the look() function as the second parameter. Use the code below in your program:

Code block follows.

print( "North:", look("north") )
print( "East:", look("east") )
print( "South:", look("south") )
print( "West:", look("west") )
move("north")

End of code block.

In our text output window, we should see these results:

Code block follows.

North: exit
East: 3
South: wall
West: A

End of code block.

Try looping and looking

You can use look() constantly while using iteration:

(Activity "mazey19" not loaded: mismatched type or js?)

Use a for-loop to print and move multiple times to read the word backwards. What is printed to your output window?

The word BACKWARDS should be printed in a column in your text output window.

Code block follows.

for step in range(9):
    print(look("north"))
    move("west")

End of code block.

NOTE: The order of "look then move" is important here! If you ordered the functions as "move then look", like this...

Code block follows.

for step in range(9):
    move("west")
    print(look("north"))

End of code block.

...then your output would read as ACKWARDS, skipping the initial B! This is due to the fact that the program ends as soon as the robot finds the exit, meaning that the print() function in the code-block will never be called.

Variables: "Named storage"

There may be times when you don't need the return value from a function right away, though you'll still want to save it for later. Or perhaps you need to use a return value as a parameter for more than one function, such that calling the original function more than once might be wasteful.

In these cases, you can use a variable to store that value, and then recall it again as many times as you need. You can address a variable by an easily remembered, descriptive name which you declare.

As with function names in the Python programming language, we typically see variable names in lowercase, with words separated by underscores. This is more of a convention than a requirement, but the consistency makes it easier to remember which variable you're after. For example, dog_breed would make more sense than db, which is too abbreviated, or doggo, which is too generic.

After the variable is first declared, anywhere this variable name is seen in your code the computer will replace it with the value that it was last told to store.

Declaring and reassigning variables

We can declare a variable for later use by giving it a name and a value, separated by an equals sign ("="):

Code block follows.

var_name = var_value

End of code block.

(Parenthetically speaking, at a level of "jargon" you don't quite need yet, the equals sign is more traditionally called the assignment operator in coding texts.)

The left-hand side of the declaration is the name of the variable, while the right-hand side is the value. That value could also be the return value of a function, so any of these declarations would be legal:

Code block follows.

lucky_number = 7
first_name = "Terry"
n_result = look("north")

End of code block.

(The right-hand side could also be another variable, or a value computed by a math equation, but we don't need that level of complexity yet. Besides, didn't we already agree NO MATH from the beginning?)

After the initial declaration of a variable, using that same pattern is called an reassignment. When a variable is accessed later, the value provided will be the one last declared or assigned. So for this example, the value printed will be 13:

Code block follows.

lucky_number = 7
lucky_number = 13
print(lucky_number)

End of code block.

(And as a final aside, you might also see "declaration" and "reassignment" both be referred to as an "assignment", in general.)

Storing return values in variables

Let's reuse a grid from before:

(Activity "mazey20" not loaded: mismatched type or js?)

Instead of using look() as the second parameter of the print() function, we could instead save the return value in a newly declared variable to print out later.

Code block follows.

n_result = look("north")
e_result = look("east")
s_result = look("south")
w_result = look("west")

print( "North:", n_result )
print( "East:", e_result )
print( "South:", s_result )
print( "West:", w_result )

move("north")

End of code block.

When this code runs, you'll see the same values printed in our text window:

Code block follows.

North: exit
East: 3
South: wall
West: A

End of code block.

The persistence of variables

Here's an example that we've seen previously:

(Activity "mazey21" not loaded: mismatched type or js?)

To demonstrate how a variable can contain a different value at different times in the same program, due to reassignment, consider this solution:

Code block follows.

direction = "north"

for step in range(4):
    move(direction)

direction = "west"

for step in range(4):
    move(direction)

direction = "south"

for step in range(4):
    move(direction)

End of code block.

After the variable direction is first declared, it is reassigned twice more. Each for-loop looks like it's making the same function call, but by the time those for-loops are reached the value of direction is different.

Remember: After the variable is first declared, anywhere this variable name is seen in your code the computer will replace it with the value that it was last told to store, via later reassignment.

To help make sense of this, you could even use print debugging to print out the value of direction at any point. Try placing this function call following every reassignment, on the following line.

Code block follows.

print("direction is now", direction)

End of code block.

Summary: Handling data

This section represents a large jump in your theoretical coding knowledge! Don't worry if you haven't absorbed it all quite yet, as later exercises will hopefully allow it to sink in.

To summarize, perhaps in an oversimplified manner:

  • Functions can take parameters in parentheses as "input data" to do their job in a more flexible way
  • Functions can have a return value as a result of the work they do, allowing us to use that information later
  • A variable is "named storage" for a value

Indefinite iteration: while-loop

Using function calls and for-loops works just fine when you know all of the details of the problem before you start. In previous exercises, you'd look at the grid to determine which direction you needed to move() and how many steps to provide to your range() function.

But what do you do when you don't know how many times the iteration will take? Here's where we need to add some "smarts" to your code so that it can make some decisions for itself, based on its environment. Most coding problems are like this, in fact, where the program needs to be able to cope with indeterminate amounts of data without human interference.

To allow this flexibility, conditional code tests for a given situation before behaving appropriately. It most often does this by evaluating a function's return value for a True or False value.

The while-loop is a kind of conditional code. Unlike a for-loop, which repeats a limited number of times that we specify, a while-loop repeats a set of instructions over and over only while a condition is True.

Two new sensor functions: can_move() and is_wall()

To allow the use of conditional code, we need to add some more "sensors" to your robot so that it can detect conditions of the grid around it.

Your next two sensor functions are can_move() and is_wall(). Each of these functions takes a direction string as the sole parameter input and return a special value that is either True or False.

They determine movement possibilities for your robot, and can be considered opposites -- if can_move() returns True, then is_wall() will return False, and vice versa.

You can always print out the return value for a function by using it as a parameter to print().

Code block follows.

print( can_move("north"), "NEVER EQUALS", is_wall("north") )

End of code block.

The while-loop

A while-loop consists of the while statement and a condition, ending in a colon (":"), followed by an indented code-block. It is another iterator, like its cousin the for-loop, but we describe it as "indefinite" because we don't know how many times it will repeat.

Otherwise, it's constructed very similarly, and follows the same program flow as the for-loop: it repeats the function calls in the code-block in order until the loop knows that it's complete, then resumes running the non-indented code below the while-loop.

Here's an example of how to use a while-loop, while only relying on your can_move() sensor function.

(Activity "mazey22" not loaded: mismatched type or js?)

If you want to move in one direction until just before hitting a wall, use this while-loop with the appropriate direction as a parameter to can_move(). Try pasting this code into your solution.

Code block follows.

while can_move("east"):
    move("east")
    
move("north")

End of code block.

The conditional is checked (or "tested") before each and every time the while-loop's code-block is repeated. This means that your robot will check if it can move before it moves. Once can_move("east") has a return value of False, the while-loop is abandoned and program flow continues by calling the function move("north").

Note that this solution should work equally well if the grid is shorter or longer. Try pasting it into the following exercises.

(Activity "mazey23" not loaded: mismatched type or js?)

(Activity "mazey24" not loaded: mismatched type or js?)

This shows the versatility of indefinite iteration and the while-loop. If you were to do this with a for-loop, you'd need to count the number of moves required and write code for three separate solutions!

Two while-loops

Just as with for-loops, you can use more than one while-loop to get to your destination. The first one will be repeated until the condition is True, then the next while-loop will be run until a different condition is True (or the exit is reached).

How would you solve this grid?

(Activity "mazey25" not loaded: mismatched type or js?)

If you want to move east as far as possible before going south as far as possible, use two while-loops in a row. Note that the first loop must finish entirely before the second one begins.

Code block follows.

while can_move("east"):
    move("east")

while can_move("south"):
    move("south")

End of code block.

Again, this solution should still work in similar grids with different dimensions. Try the same solution here:

(Activity "mazey26" not loaded: mismatched type or js?)

In fact, it's possible for a while-loop to repeat zero times, if the condition is already True.

(Activity "mazey27" not loaded: mismatched type or js?)

Since the return value of can_move("east") starts out as False, the while-loop cannot run any functions in its code-block. As such, program flow jumps immediately to the second while-loop.

Even more while-loops!

But why stop at just two while-loops?

(Activity "mazey28" not loaded: mismatched type or js?)

How many while-loops would you need for this grid?

Four while-loops, in fact, one for each cardinal direction.

Code block follows.

while can_move("west"):
    move("west")

while can_move("north"):
    move("north")

while can_move("east"):
    move("east")

while can_move("south"):
    move("south")

End of code block.

Following the wall

Thus far, we've only used the can_move() sensor function as our conditional. But sometimes we can't rely on being able to move until we can't any longer! In this case we use is_wall() to test for a condition where a wall to the side of our path of travel is no longer there.

(Activity "mazey29" not loaded: mismatched type or js?)

Since the exit isn't all the way to an edge of the grid, we'll need to trace the length of a wall until it ends. Here, we'll check for a wall to the north while we travel west. Once is_wall("north") returns a value of False, we know that we can move north to the exit.

Code block follows.

while is_wall("north"):
    move("west")
    
move("north")

End of code block.

Again, this one solution will work for grids with different geometries.

(Activity "mazey30" not loaded: mismatched type or js?)

And as we've seen before, it will even work when is_wall("north") returns a value of False on its very first check.

(Activity "mazey31" not loaded: mismatched type or js?)

Mixing conditions

You can use two while-loops with different sensor checks to get your robot to the exit.

(Activity "mazey32" not loaded: mismatched type or js?)

This time, you'll need to use two while-loops in your solution. One will use can_move() as its condition, and the other will use is_wall().

Code block follows.

while is_wall("south"):
    move("east")
    
while can_move("south"):
    move("south")

End of code block.

Just to be sure your solution is correct, will it also work on this grid?

(Activity "mazey33" not loaded: mismatched type or js?)

Infinite loops

Sometimes we use the while-loop as an infinite loop by providing an explicit condition of True, meaning that it repeats a code-block forever... or at least until the robot finds the exit and the program immediately ends.

Here's what it would look like:

Code block follows.

while True:
    <...code-block goes here...>

End of code block.

As an example, let's look at a grid where the path to the exit is a straight shot in one direction.

(Activity "mazey34" not loaded: mismatched type or js?)

Previously, we would have had you go south, using your sensors to know when you reached the wall, like this:

Code block follows.

while can_move("south"):
    move("south")

End of code block.

But since your robot program ends as soon as it reaches the exit, we could have asked you to use an infinite loop in your solution, like so:

Code block follows.

while True:
    move("south")

End of code block.

But... why "infinite"?

It's a bit of a case of "splitting hairs" to use an infinite loop when you know you'll be reaching the exit, rather than testing for a condition. Here, the difference is pretty ephemeral, thanks to the fact that reaching the exit will immediately end your program.

In the real world, though, many computational forms rely on infinite loops. For example, games rely on a "render loop", which takes player input, adjusts how the objects move, and paints a new screen, and then repeats this code over and over until the user quits the game. Network servers have a "dispatch loop", which accepts an incoming session connection, processes its request, and generates data to send back to the client, and then repeats this code over and over until the server is shut down.

But for now, since you're only dealing with our tiny robot environment, it's pretty interchangeable. For instance, let's revisit this old grid:

(Activity "mazey35" not loaded: mismatched type or js?)

Previously, we'd have instructed you to solve the grid like this:

Code block follows.

while is_wall("south"):
    move("east")
    
while can_move("south"):
    move("south")

End of code block.

But you could've just as easily used an infinite loop in that last leg, like this:

Code block follows.

while is_wall("south"):
    move("east")
    
while True:
    move("south")

End of code block.

For the most part, an infinite loop will be more useful in the next section, when we introduce the concept of branching.

Mixing indefinite and infinite loops

So, knowing that we can usually finish off the last leg of our solution with an infinite loop, try out this grid.

(Activity "mazey36" not loaded: mismatched type or js?)

Use two while-loops in your solution, with the final one being an infinite loop.

Code block follows.

while can_move("north"):
    move("north")
    
while True:
    move("east")

End of code block.

Expanding the code-block

One thing we shouldn't forget is that a code-block can have more than one function call. Consider this staircase that we climbed previously with a for-loop.

(Activity "mazey37" not loaded: mismatched type or js?)

Instead, you could now use an infinite loop with two functions in the code-block.

Code block follows.

while True:
    move("north")
    move("east")

End of code block.

The advantage here is that once again the same solution could work for staircases of different size. Try pasting this solution into these exercises.

(Activity "mazey38" not loaded: mismatched type or js?)

(Activity "mazey39" not loaded: mismatched type or js?)

Even more function calls

To extend this notion even further, solve this grid:

(Activity "mazey40" not loaded: mismatched type or js?)

How many function calls are needed in the code-block?

Here, you'll need three function calls for each repeat.

Code block follows.

while True:
    move("east")
    move("east")
    move("south")

End of code block.

To make sure you have the right solution, does the same code work on these grids?

(Activity "mazey41" not loaded: mismatched type or js?)

(Activity "mazey42" not loaded: mismatched type or js?)

Summary: Indefinite iteration

You've now added a different kind of iteration to your toolbox.

  • While a for-loop requires that you know how many times your code needs to repeat, a while-loop can repeat as many times as it needs so long as a condition is found to still be True.
  • Your robot's newest sensor functions are can_move() and is_wall(). They take a direction string as a parameter, and have a return value of True or False.
  • A special kind of indefinite iteration is the infinite loop, which repeats until the exit is found. It is specified like this: while True:

Branching: if-block

Conditional code checks for a given situation before behaving appropriately. As we saw with the while-loop (and indefinite iteration), this is most often accomplished by evaluating a function's return value for a True or False value.

The while-loop is a bit limited, though, in that the central question it asks is this: "Should I run the indented code-block in the loop one more time? Or do I quit now, and continue on to the code below it?"

When solving coding problems, it's quite common to need to run some conditional code only once (not as part of multiple iterations), and the conditions that determine the results may in fact be more numerous than just "either this or nothing at all".

To solve this, programming languages support a type of conditional code known as branching. This represents a process for making actual decisions for your robot, making it effectively a little "smarter" about which path it travels.

We can chain if, elif, and else statements, together called an if-block, to behave appropriately for the conditions tested. We typically use these to test the value of a variable or function before executing the code-block.

Revisiting the look() sensor function

Since an if-block depends on a condition, and because our sensor functions use a return value to tell us about the grid conditions, let's enhance our robot's capabilities with a sensor function that we introduced earlier.

As a reminder, the look() function takes a direction string as a parameter and returns a string value that describes the item in the appropriate square. Typical return values might be: "wall", "exit", a single capital letter (as a string) or single digit number, or a special value called None.

look() can also use "here" as a parameter to look in the square where the robot currently sits, and the return value can be assigned to a variable for later use. We'll soon find this to be very convenient!

Testing equality: ==

Our other sensor functions return a True or False value, so how do we test the string result of look() for use as conditional code? Here, we introduce the equality operator, which is formed by two equals signs: ==

(In a coding language, an "operator" is a symbol or keyword that alters or evaluates the values of things around it. It's kind of like the symbols + or -, which are also operators, but we already promised: NO MATH)

When the == is placed between two values, the computer sees it as a result of True or False, making it valid as a test to run conditional code.

Let's make use of the equality operator and the special look() return value of None.

(Activity "mazey43" not loaded: mismatched type or js?)

You can use this solution:

Code block follows.

while look("here") == None:
    move("east")

move("north")

End of code block.

We've told the program to move east so long as the robot stands in an empty square. Once the square is not empty (with a "?" in this case), we know that the exit is immediately to the north, so one more move is all it takes.

Two things to note:

  • The equality operator, ==, is different than the assignment operator, which is a single =. If you confuse them, your Python environment will point this out as an error!
  • The None value is special. It is not a string, so there are no quotation marks around it, and it always has to be capitalized as: None

Now try to use the same test for equality on this grid:

(Activity "mazey44" not loaded: mismatched type or js?)

The solution is handled very much like the previous one.

Code block follows.

while look("here") == None:
    move("south")

move("east")

End of code block.

Using the "if" statement

So far, our examples with the equality operator are only determining whether our robot continues doing its assigned task, but it's not telling the robot what to do. To allow this ability, we'll start adding the if statement.

Check out this grid we've seen recently:

(Activity "mazey45" not loaded: mismatched type or js?)

Our previous solution assumes the exit is to the north:

Code block follows.

while look("here") == None:
    move("east")

move("north")

End of code block.

But what if we couldn't depend on the exit always being to the north? How would our robot test its environment to know which way to move?

With the if statement, we could look() around once we got to the "?".

Code block follows.

while look("here") == None:
    move("east")

if look("north") == "exit":
    move("north")

if look("south") == "exit":
    move("south")

if look("east") == "exit":
    move("east")

End of code block.

Here, the robot stops after finding a non-empty square, then looks in each of the remaining directions to determine if the exit is there. This is done with a series of if-statements.

This solution now works on any of the following grids, too. Load up these examples and use the same solution to watch their behavior.

(Activity "mazey46" not loaded: mismatched type or js?)

(Activity "mazey47" not loaded: mismatched type or js?)

Extending our reach

Since look() can only see to the robot's immediate sides, this solution only works if the exit is in a neighboring square. So how can we determine which way to go if the exit is further away, beyond the reach of our sensors?

To allow for this, we need to make the item in the target square tell us something about which way to go.

(Activity "mazey48" not loaded: mismatched type or js?)

Now we'll put the initial of a compass direction in the target square, and use if-statements to move us in the new direction until we reach the exit.

Code block follows.

while look("here") == None:
    move("east")

if look("here") == "N":
    while True:
        move("north")

if look("here") == "S":
    while True:
        move("south")

if look("here") == "E":
    while True:
        move("east")

End of code block.

Once we determine which way to turn for our final leg, we use an infinite loop to get there. Notice, though, that since the code-block of an if-statement needs to be indented and the code-block of a while-loop needs to be indented, that inner-most move() call needs to be indented twice. This is what one might call nested blocks.

So, if this solution is coded correctly, it should also work when copied into these exercises:

(Activity "mazey49" not loaded: mismatched type or js?)

(Activity "mazey50" not loaded: mismatched type or js?)

Constructing the if-block

You've started using if-statements to add some "smarts" to your robot, but we should take a little time to refine how we're writing them.

When we have a few if-statements in a row, and when only one of those conditions will inevitably be found true, we should write an if-block instead.

An if-statement can optionally be followed by any number of elif statements (aka "else if"), which are only tested if no previous other if or elif condition in the same block was found to be True. (Remember, code is evaluated from top to bottom, so each condition is also evaluated in that order.) In other words, once one of the conditions is found to be true, only that code-block is executed and program flow skips to after the entire if-block.

Here is the previous solution rewritten as an if-block:

Code block follows.

while look("here") == None:
    move("east")

if look("here") == "N":
    while True:
        move("north")

elif look("here") == "S":
    while True:
        move("south")

elif look("here") == "E":
    while True:
        move("east")

End of code block.

Functionally, it's no different, but experienced coders will know at a glance that only one of these code-blocks will be used.

Let's refine this if-block a little more. To serve a more general case for other grids, we might have to eventually move west, so let's add that as an elif condition.

Code block follows.

if look("here") == "N":
    while True:
        move("north")

elif look("here") == "S":
    while True:
        move("south")

elif look("here") == "E":
    while True:
        move("east")

elif look("here") == "W":
    while True:
        move("west")

End of code block.

Finally, what if the item in our robot's square isn't one of our compass initials? In such a case, you can end your if-block with an else clause. It has no condition, essentially saying: "if no previous if or elif condition was found to be True, run this following indented code-block".

For our purposes, perhaps we'll print out this unknown item to our output window. This is the resulting if-block:

Code block follows.

if look("here") == "N":
    while True:
        move("north")

elif look("here") == "S":
    while True:
        move("south")

elif look("here") == "E":
    while True:
        move("east")

elif look("here") == "W":
    while True:
        move("west")
        
else:
    print("Unknown item:", look("here"))

End of code block.

Using this generalized if-block, solve these exercises. Remember, you'll first need to get your robot to the letter!

(Activity "mazey51" not loaded: mismatched type or js?)

Code block follows.

while look("here") == None:
    move("north")
    
if look("here") == "N":
    while True:
        move("north")

elif look("here") == "S":
    while True:
        move("south")

elif look("here") == "E":
    while True:
        move("east")

elif look("here") == "W":
    while True:
        move("west")
        
else:
    print("Unknown item:", look("here"))

End of code block.

(Activity "mazey52" not loaded: mismatched type or js?)

Code block follows.

while look("here") == None:
    move("east")
    
if look("here") == "N":
    while True:
        move("north")

elif look("here") == "S":
    while True:
        move("south")

elif look("here") == "E":
    while True:
        move("east")

elif look("here") == "W":
    while True:
        move("west")
        
else:
    print("Unknown item:", look("here"))

End of code block.

Testing against a variable

Remember that the return value of a function can be assigned to a variable. This comes in handy for if-blocks, where you might be testing the result of a look() over and over without expecting the return value to change.

Here's a way to change the if-block we've been using.

Code block follows.

item = look("here")

if item == "N":
    while True:
        move("north")

elif item == "S":
    while True:
        move("south")

elif item == "E":
    while True:
        move("east")

elif item == "W":
    while True:
        move("west")
        
else:
    print("Unknown item:", item)

End of code block.

The first line is capturing which item is in the robot's square, and then remembering it under the variable named item.

This not only makes the code a little more "readable", but it also offers an efficiency boost when calling some functions, which can speed up a program. Our look() function isn't computationally complex, but what if you were calling a hypothetical function like observe_all_squares()? That would take Python a lot longer to compute several times over, slowing down your program, so it's best just to put the return value in a variable if you don't expect it to change before the next function call.

Further code refinement with variables

While we're working with variables, remember that you can pass a variable to a function as a parameter. In our current if-block, we're repeating a lot of code, namely the infinite while loops, with the only difference being the string direction value we're passing to move() as a parameter.

How about instead, we'll keep track of the direction we currently want to move in a variable named direction? A full solution might look like this.

Code block follows.

direction = "east"

while look("here") == None:
    move(direction)
    
item = look("here")

if item == "N":
    direction = "north"

elif item == "S":
    direction = "south"

elif item == "E":
    direction = "east"

elif item == "W":
    direction = "west"
        
else:
    print("Unknown item:", item)
    
while True:
    move(direction)

End of code block.

This solution should be able to handle most of the grids we've seen recently, with the only change being the first assignment of direction to get your robot to the square with the letter.

Try out these exercises: remember, each will need a different initial direction.

(Activity "mazey53" not loaded: mismatched type or js?)

Code block follows.

direction = "west"

while look("here") == None:
    move(direction)
    
item = look("here")

if item == "N":
    direction = "north"

elif item == "S":
    direction = "south"

elif item == "E":
    direction = "east"

elif item == "W":
    direction = "west"
        
else:
    print("Unknown item:", item)
    
while True:
    move(direction)

End of code block.

(Activity "mazey54" not loaded: mismatched type or js?)

Code block follows.

direction = "north"

while look("here") == None:
    move(direction)
    
item = look("here")

if item == "N":
    direction = "north"

elif item == "S":
    direction = "south"

elif item == "E":
    direction = "east"

elif item == "W":
    direction = "west"
        
else:
    print("Unknown item:", item)
    
while True:
    move(direction)

End of code block.

Deciding within a loop

The use of an if-block is often the basis for defining the "behavior" of a system. Combined with an infinite loop, it becomes the framework for a system that reacts to ever-changing conditions.

Here's a grid that seems quite complex, at first:

(Activity "mazey55" not loaded: mismatched type or js?)

The solution, though, becomes simpler when we tell the robot to follow this rule: "Keep going in the same direction until you find a compass initial, then change direction."

This will use another infinite loop, where we use look("here") for every step we take. As with our previous code refinements, we'll keep track of conditions for each step with the variables direction and item.

The only other hint your robot needs is to start out going north. Altogether, your solution would look something like this:

Code block follows.

direction = "north"

while True:
    item = look("here")

    if item == "N":
        direction = "north"
    elif item == "E":
        direction = "east"
    elif item == "S":
        direction = "south"
    elif item == "W":
        direction = "west"

    move(direction)

End of code block.

Recall that a variable always remembers the value last assigned to it. When we reach this line...

Code block follows.

move(direction)

End of code block.

...it is not necessary for the value of direction to have changed in the if-block above. If the item found in the robot's square is not one of our compass initial letters, or is perhaps even None, we just ignore that return value and keep the last value of direction that was assigned.

Triggered code

Here's a grid we've seen before, where we jumped over hurdles.

(Activity "mazey56" not loaded: mismatched type or js?)

Previously, we relied on every hurdle requiring the same set of move() calls in a code-block, but coding this with an if-block that check for conditions has an advantage. Without any letters to tell our robot how to behave, though, how do we know when to run a special code-block to jump the hurdle?

So far we've been using look() to test for conditions in an if-block, but would shouldn't neglect the sensor functions can_move() and is_wall(), which we've used in other conditional code.

For our solution, we'll need to tell our robot the rules it needs to follow. In plain "human" language, it would be something like this: Keep moving west: if your robot finds a wall in its path, jump over it, then continue west again.

Like our previous exercises, we're modeling behavior based on grid conditions, so we pretty much expect to code an infinite loop. Within the block for that while-loop, we'll have to accomplish a few tasks every time it repeats.

  • Keep moving west.
  • After you move, check if there is a wall in the square immediately to the west.
  • If there is a wall there, execute a code-block that tells the robot how to jump the hurdle.
  • Keep doing this all over again!

In this manner, we "trigger" a code-block upon the robot sensing a particular condition.

Here's what the solution could look like:

Code block follows.

while True:
    move("west")
    
    if is_wall("west"):
        move("north")
        move("west")
        move("west")
        move("south")

End of code block.

Remember that mention of an advantage to using an if-block to trigger a code-block? It's this: since we no longer have to rely upon the hurdles being evenly spaced, this same solution would work on this grid, where distance to the next hurdle varies.

(Activity "mazey57" not loaded: mismatched type or js?)

Multiple triggers

It is such a common coding task to use iteration to keep repeating a task until a condition is met, triggering the execution of a special code-block. You'll find the combination of a while-loop or a for-loop with an if-block time and time again!

It's also important to know you can even have more than one trigger in your loop. Take a look at this grid:

(Activity "mazey58" not loaded: mismatched type or js?)

Without an obvious context of what the letters "A" and "B" mean, we'll have to give your robot the new rules it needs to follow: Keep moving east: if your robot visits a square with the letter "A" it should follow the north path, while a "B" indicates the south path.

Again, since we're modeling behavior based on grid conditions, we pretty much expect to code an infinite loop. Within the block for that while-loop, we'll have to accomplish a few tasks every time it repeats.

  • Get the robot to the next letter. We'll do this by moving east until we find a letter.
  • Read the value of the letter and store it in a variable.
  • If the letter is "A", execute a code-block that tells the robot how to navigate the north path.
  • If the letter is "B", execute a code-block that tells the robot how to navigate the south path.

In this manner, we "trigger" a code-block upon the robot stepping on a certain letter, but otherwise just repeat the loop and move east.

So what would the solution look like? As a hint, here are some of the things you'll need:

  • An infinite loop
  • A call to move() that will be repeated each time, so that you eventually reach a letter.
  • An if-block to decide what to do with the letters "A" and "B"
  • A code block with multiple function calls for each separate path of "A" and "B"

We're getting into complex territory here! Give it your best effort before peeking at the solution: you'll probably be pretty close, even if you don't solve it the first time.

Code block follows.

while True:
    move("east")

    letter = look("here")

    if letter == "A":
        move("north")
        move("east")
        move("east")
        move("south")
    elif letter == "B":
        move("south")
        move("east")
        move("east")
        move("north")

End of code block.

Just to make sure you got the right solution, can you use the same code on this grid, where you'll have to ignore all items except "A" and "B"?

(Activity "mazey59" not loaded: mismatched type or js?)

This is the same solution as above, but it should be emphasized that your if-block should use an if and an elif. If you used an else without a condition instead of an elif, your code would break almost immediately!

Code block follows.

while True:
    move("east")

    letter = look("here")

    if letter == "A":
        move("north")
        move("east")
        move("east")
        move("south")
    elif letter == "B":
        move("south")
        move("east")
        move("east")
        move("north")

End of code block.

Summary: Branching

Sometimes you need to decide between two or more courses of action in your program, or maybe you need to iterate over a task while monitoring a trigger condition to execute special-purpose code.

For either purpose, the if-block allows you to run conditional code via branching.

  • An if-block is constructed with an if statement, followed by zero or more elif statements, and possibly (but not necessarily) ending with an else clause.
  • The condition in an if-statement must evaluate to True or False.
  • You can use the equality operator, ==, to test whether two values are equal, producing a True or False result.
  • The code-blocks within an if-block must be indented, as with other code-blocks. Keep in mind, if you have a code-block nested within another code-block, that innermost block must be indented twice.
  • You'll find the combination of a while-loop or a for-loop with an internal if-block time and time again!

Abstraction: def

Code makes more sense if we can break it down into related chunks, and then give those chunks a simplified label to identify them by. This is known as abstraction (or, sometimes, procedural abstraction).

Abstraction hides the unnecessary details from the coder. It makes code easier to understand: we just need to know what it does, not how it does it. It's an external simplification that shields the inner workings from the world at large.

As you've already seen, functions are a form of abstraction. They do potentially complex work for the low-low price of calling a descriptive function name.

Thus far you've been calling functions that have been coded by others, but now you're going to start defining your own!

And while abstraction works great when you don't need to know the hidden details of someone else's implementation, you'll find that using abstraction also helps organize and simplify the structure of your own code when you're doing the implementation yourself.

Defining your own functions

As with iteration, if you observe repeated sub-tasks in solving the larger problem, there's probably a good case to be made for using abstraction.

We'll do this by defining our own functions. Let's use this grid to help demonstrate:

(Activity "mazey60" not loaded: mismatched type or js?)

It seems that there's a lot of left and right stepping to do, with a few upward jumps in between. That's a lot of repeated elements! If we were to abstract these sub-tasks, without worrying about implementation yet, the code might look something like this:

Code block follows.

jog_right()
climb_up()
jog_left()
climb_up()
jog_right()
climb_up()
jog_left()

End of code block.

That makes a lot of sense, since we used descriptive function names. In fact, most non-coders could probably understand it as a simple task list.

But we can't run this code yet, until we actually define what these new functions are. In order to do this, we use the def keyword to define the functions before they're called, so you'll find function definitions above their use in code.

So to fill out the definitions of our proposed functions, the solution would look like this:

Code block follows.

def jog_right():
    for step in range(4):
        move("east")
 
def jog_left():
    for step in range(4):
        move("west")
        
def climb_up():
    move("north")
    move("north")
 
jog_right()
climb_up()
jog_left()
climb_up()
jog_right()
climb_up()
jog_left()

End of code block.

Now this program can be run, since your new functions are defined. Watch that robot go!

Function definition syntax and program flow

Here's the basic structure of a function definition:

Code block follows.

def function_name():
    code-line-1
    code-line-2
    ...etc...

End of code block.

Note that a function definition uses a colon at the end of the line, followed by an indented code-block, similar to how iteration and branching statements do it. In this manner, the program knows that the first unindented line afterward is not part of the function's code-block.

In fact, since some of our function definitions above contain a for-loop, you'll see two "levels" of indentation:

Code block follows.

def jog_right():
    for step in range(4):
        move("east")

End of code block.

So when the program is run, the code knows that the lines indented once belong to the code-block of the function definition, while lines indented twice belong to the code-block of the for-loop.

Also similar to our other code-block forms, when you call your own custom function we break the typical program flow of line by line, from top to bottom by "jumping" to the first line of the function's code-block. At that point we resume the "line by line" flow until we get to the end of the function's code-block, whereupon we jump back to where the function is called.

That's a lot of jumping, right? But you'll get used to it. When you call a custom function, you can pretty much just tell yourself: "Forget this current code I'm running, and just run the code in the function as if it's a completely different program. When that other program is done, just resume my old program exactly where I left it."

And now, back to some practical examples...

Abstraction allows quick, invisible code modification

Consider this grid:

(Activity "mazey61" not loaded: mismatched type or js?)

It looks almost the same as the previous grid, except that the right and left jogs are now 6 steps long (instead of 4).

Interestingly, the code outside of the function definitions themselves ("what we're doing") would be identical:

Code block follows.

jog_right()
climb_up()
jog_left()
climb_up()
jog_right()
climb_up()
jog_left()

End of code block.

What we're calling remains the same. The differences come in how we implement jog_right() and jog_left(). Changing the count for the for-loops is all you need to do to modify the old code to solve the new grid.

This is one major benefit of abstraction: you can change how the functions do their work in a way that doesn't require a change to the basic code that calls these functions. Such a change would be invisible to other coders who use your jog functions.

So, using cut-and-paste from the last solution and modifying the distance your robot has to jog, what would be the full solution for this new grid?

All you need to do is change the count of 4 to 6 for each function definition.

Code block follows.

def jog_right():
    for step in range(6):
        move("east")
 
def jog_left():
    for step in range(6):
        move("west")
        
def climb_up():
    move("north")
    move("north")
 
jog_right()
climb_up()
jog_left()
climb_up()
jog_right()
climb_up()
jog_left()

End of code block.

Parameters add flexibility to functions

Functions can take parameters to do their job in a specific way.

You've already used parameters in your for-loop, when providing a number of steps for range(), or when specifying a direction to a sensor function. Similarly, you can add parameters to your own function definitions to allow them to do more tasks than before.

These parameters are placed within the parentheses after the function name. If a function takes more than one parameter, they are separated by commas. Here's the general format:

Code block follows.

def func_name( param1, param2, param3, ...):
    code-line-1
    code-line-2
    ...etc...

End of code block.

When a named parameter is specified in a function definition, the value which was passed to it is available in the function's code block by using the parameter name as if it were a variable.

For example, we could modify our previous definition of jog_right() to use a parameter. So instead of the old way, like this:

Code block follows.

def jog_right():
    for step in range(6):
        move("east")

End of code block.

It would now look like this:

Code block follows.

def jog_right(repeats):
    for step in range(repeats):
        move("east")

End of code block.

At this point we could call jog_right() with different repeat values. Within that function, repeats acts as a variable with the initial value that was passed as a parameter.

If we were to solve a 8-step grid, we'd call:

Code block follows.

jog_right(8)

End of code block.

While for a 6-step grid, we'd call:

Code block follows.

jog_right(6)

End of code block.

Now your jogging is more flexible than before! Try to find a solution for this grid, using jogs of 5 steps.

(Activity "mazey62" not loaded: mismatched type or js?)

You'll also have to write a function for jog_left() which takes a parameter of repeats to set the range of a for-loop. Look at the above definition for jog_right() to base it on.

Code block follows.

def jog_right(repeats):
    for step in range(repeats):
        move("east")
 
def jog_left():
    for step in range(repeats):
        move("west")
        
def climb_up():
    move("north")
    move("north")
 
jog_right(5)
climb_up()
jog_left(5)
climb_up()
jog_right(5)
climb_up()
jog_left(5)

End of code block.

An example in flexibility

You might argue that it's probably just easier to change every 4 to a 6 in copy-pasted code than make a new function, but that's only true if all of the jogs are the same length.

Try this on for size:

(Activity "mazey63" not loaded: mismatched type or js?)

At this point, each jog is a different length. You wouldn't want to write a separate function for each jog, but adding a repeats parameter to your jog functions makes them flexible.

The length of the jogs will change each time, so make sure you count carefully!

Code block follows.

def jog_right(repeats):
    for step in range(repeats):
        move("east")

def jog_left(repeats):
    for step in range(repeats):
        move("west")
        
def climb_up():
    move("north")
    move("north")
 
jog_right(8)
climb_up()
jog_left(7)
climb_up()
jog_right(6)
climb_up()
jog_left(5)
climb_up()
jog_right(4)

End of code block.

More parameters, less functions

With the new move() function at our disposal, we can consider a simpler solution for our previous "jog grid".

(Activity "mazey64" not loaded: mismatched type or js?)

Recall our two jog functions:

Code block follows.

def jog_right(repeats):
    for step in range(repeats):
        move("east")

def jog_left(repeats):
    for step in range(repeats):
        move("west")

End of code block.

We're always looking for patterns, though, and it might stick out to you that these functions are really, really similar in how they do their work. The only difference is the direction that's passed to move().

So how about we replace both of these functions by defining a single function that takes two parameters?

Code block follows.

def jog(direction, repeats):
    for step in range(repeats):
        move(direction)

End of code block.

That's great! Now you can jog in any direction for any number of steps with the same jog() function.

Any since we can jog in any direction, including "north", we can actually replace this:

Code block follows.

climb_up():

End of code block.

By calling this:

Code block follows.

jog("north", 2)

End of code block.

This totally eliminates the need to define the climb_up() function. Suddenly, we've gone from three function definitions to only one.

The final code would look like this:

Code block follows.

def jog(direction, repeats):
    for step in range(repeats):
        move(direction)
        
jog("east", 8)
jog("north", 2)
jog("west", 7)
jog("north", 2)
jog("east", 6)
jog("north", 2)
jog("west", 5)
jog("north", 2)
jog("east", 4)

End of code block.

It's so much simpler than our original solution!

Adding some "smarts"

We've now created a single-function means to navigate a "snakey" grid of varying lengths by providing two parameters per jog.

However, isn't one of the points of coding to remove the need for humans to count and do math? We're still providing a lot of numbers to these jog functions! Can't we just have the computer count for itself?

Well, we've already used indefinite iteration, built on while-loops and sensor functions, to move in straight lines until to before hitting a wall. We could utilize this to eliminate all need for counting.

So let's try that same grid again:

(Activity "mazey65" not loaded: mismatched type or js?)

This time, though, you'll remove the repeats parameter from the function definition (and the later individual function calls, as well!). The only parameter to jog() will then be direction.

In place of the previous for-loop, you'll use a while-loop instead with the can_move() sensor function.

Code block follows.

def jog(direction):
    while can_move(direction):
        move(direction)
        
jog("east")
jog("north")
jog("west")
jog("north")
jog("east")
jog("north")
jog("west")
jog("north")
jog("east")

End of code block.

Jogging vertically

Being able to jog in any direction until just before a wall is very handy! We're no longer limited to a left-to-right motion, and we no longer have to count.

As an exercise, let's go up and down varying sized passageways:

(Activity "mazey66" not loaded: mismatched type or js?)

We can still use the same jog() function as before, but we're still left with a lot of typing! A full solution might look like this:

Code block follows.

def jog(direction):
    while can_move(direction):
        move(direction)
        
jog("south")
jog("east")
jog("north")
jog("east")
jog("south")
jog("east")
jog("north")
jog("east")
jog("south")
jog("east")
jog("north")
jog("east")
jog("south")
jog("east")
jog("north")
jog("east")
jog("south")

End of code block.

That's just a crazy amount of repeated code, right?! And from what we've seen before, any time we see a lot of repetition, we can try to recognize the pattern and use iteration instead.

So what we see here is that we jog south, east, north, and east one more, then we repeat the whole thing over and over again. And from what we learned previously, when we want to model a "behavior" for your robot in a system, it's a good bet that you'll see an infinite loop, implemented with while True:.

So what would your full solution look like? It's probably shorter than you might expect!

This solution is so much cleaner and understandable!

Code block follows.

def jog(direction):
    while can_move(direction):
        move(direction)

while True:
    jog("south")
    jog("east")
    jog("north")
    jog("east")

End of code block.

Summary: Abstraction

Abstraction hides unnecessary details from other coders, while making your own code easier to understand. The primary way we accomplish this is through function definitions.

By defining your own functions with def and descriptive names, your code becomes more understandable, even to non-coders! Better yet, coding functions with parameters makes them more flexible in a variety of situations.

Reasons to use abstraction:

  • You rely on this functionality over and over, and want to code it more generally to improve flexibility.
  • A conceptually separate task is more easily referred to by a recognizable name.
  • The implementation is complex and/or ugly, and you want to hide it from the world.
  • Simpler code is more easily understandable, maintainable, debuggable, and reuseable.

Functions, methods, subroutines, procedures, modules, libraries, packages are all abstractions.

Bonus! A smarter robot

This is just an example of what you might be able to do after learning more about coding...

One "classical" solution to any maze which has no loops is to always keep your hand on the wall to the right of you. As soon as there's a path to the right, you turn that corner and continue walking straight ahead. If you have walls to both your right and forward, you turn left (with your hand still on the right wall). Eventually you will find the exit of the maze.

Our robot could use these very same rules to escape any non-looping maze. The only additional concept that we'll have to teach our robot is relative directions.

Essentially, all of our move() and sensor functions can take the special directions "left", "right", and "forward". Also, we'll add the turn() function to allow us to face in a relative direction.

When you run this exercise (including the solution), the maze will randomize each time. (The maze depicted here is but one example.)

(Activity "mazey67" not loaded: mismatched type or js?)

Since we're modeling a behavior for the robot, you probably wouldn't be surprised that the solution involves an infinite loop. Each time the loop repeats, the robot makes a decision about what it should do based on the conditions around it, and checking conditions means we'll have an if-block.

In fact, the decisions are the very same ones we mentioned above:

  • If your robot can move to its right (NOT your right, as oriented on the screen), it should turn that way and start down that path.
  • Otherwise, if there's no path to the right, it should move straight ahead.
  • Finally, if the robot can neither move to the right nor straight ahead, it should turn left to keep the wall ahead to its right.

Turning these behaviors into an if-block, the solution would look like this. Remember: only one clause of the if-block can be executed for each repeat of the loop.

Code block follows.

set_speed(20)

while True:
    if can_move("right"):
        turn("right")
        move("forward")
    elif can_move("forward"):
        move("forward")
    else:
        turn("left")

End of code block.

(We also added a set_speed() function to move your robot along a little faster, since the randomized maze is fairly large.)

We wouldn't have expected you, as a beginning coder, to come up with this solution right away, especially so soon after dropping the new concept of relative directions on you! But it's helpful to look at examples which are just beyond your current reach as something to work towards.

Though you might not know how it's done, properly named functions can allow you to surmise what is being done. This provides a way to understand what the code is all about without necessarily having the skills to write it yourself at your current skill level.

Thanks to the practice of abstraction, we can rely on the efforts of others to help our work along. It's quite common to adopt open-source code to use in your projects as prescribed on the "surface level", and then later dig in "under the hood" when you have the confidence to change how it works.

At any rate, try running this solution a few times to observe how the robot explores the random maze. Does the path it chooses make sense for the rules that we told it? Can you predict the direction the robot will take when the path branches?

The Big-Big Summary

We've only scratched the surface of coding, but hopefully you now have a better idea of what it's all about. It's not really our goal to have you walk away from this experience as a coding wizard, but rather to have built some confidence that it's quite approachable, and that learning even more on your own might be achievable and (dare we say) fun!

A handy "cheat sheet"

What are the most important concepts we've covered in the Python language?

Here they are, all in one place!

  • The art of coding is to know how to use functions in the most efficient way to tell a computer how to accomplish a larger task. To do this, we break big tasks into smaller, approachable tasks.
  • We use functions to call potentially complex sequences of operations with an easy to remember name. Our main robot function is move().
  • Functions in a program are executed in the order that they're called, line by line, from top to bottom. We call this program flow.
  • A comment is a line starting with a hash: #. They're for your human eyes to read for additional information about how the program works, but the computer itself will entire ignore anything on that line.
  • Iteration allows us to sequence repetitive actions without having to type it all out. The for-loop is specified by for steps in range(count):, followed by an indented code-block.
  • Functions can take parameters as "input data" to do their job in a more flexible way.
  • Functions can have a return value as a result of the work they do, allowing us to use that information later. Our sensor functions, like look(), provide a return value.
  • A variable acts as "named storage" for values. You assign it by var_name = value.
  • Conditional code tests for a given situation before behaving appropriately. It most often does this by evaluating a function's return value for a True or False value.
  • Indefinite iteration is a kind of conditional code. A while-loop can repeat as many times as it needs so long as a condition is found to still be True. We used our sensor functions can_move() and is_wall() as such conditions.
  • An infinite loop is specified by while True:. It repeats a set of rules forever, or at least until our robot finds the exit. This is typically how we model behavior in a system.
  • Branching is another kind of conditional code. We can chain if, elif, and else to behave appropriately as an if-block, running only one choice of code-block within that chain.
  • The equality operator, ==, can be used to compare two values to return a True or False value, usable as a condition. We often find these in if-blocks.
  • Abstraction helps us simplify the design of our code, as well as adding flexibility to our functions. We define our own functions with def and can specify parameters for them.

There is a multitude of additional topics within coding (in general) and Python (specifically), but these are the most essential for you to start out with and build upon.

Next steps

If you found this tutorial useful and want to learn more about Python, or would like to see a different approach to coding topics which might better fit your own learning style, then keep on going!

You'll find that with each new tutorial you try, the repeated material will become easier to understand, while they'll give you more features of Python which we (intentionally) did not cover here.

Here are a few resources which we found worth a look:

  • Code the Blocks (CTB)
    A no-experience-needed tutorial that allows you to run Python code in the browser to manipulate 3-D building blocks. There's a little bit of math toward the later sections, but it's not too difficult, and they'll guide you through it.
  • Computer Science Circles
    From the University of Waterloo, a nicely presented tutorial for beginners. The in-the-browser nature of such courses make for easy entry to see if you like it, since there's nothing additional for you to install in order to give it a test drive!
  • Automate the Boring Stuff with Python
    Described as "practical programming for total beginners", it's a good first look at some of the most useful coding tasks, including how to read and write popular file formats.
  • NCLab Karel (Hour of Code)
    If our tutorial moved a bit fast for you, this one was developed for K-12 students participating in the "Hour of Code" event. Don't let the playful graphics turn you off, though: it can still teach some solid fundamentals of coding, but isn't Python specific.
  • How to Think Like a Computer Scientist (Interactive Edition)
    Perhaps the most formalized tutorial that we might suggest for serious learners, such as those who might want a taste of what a real Computer Science class might be like.
  • Python for Non-Programmers
    Not a tutorial in itself, but rather a list of other learning resources, maintained by the developers of the Python language itself. Find the tutorial that's right for you!

Lastly, this tutorial takes inspiration from the classic "Karel the Robot", a simple platform designed for learning, written by Richard E. Pattis in 1981. Mad props, yo.

Final words

We wish you luck on your coding journey! Even if you don't have to code for a living full-time, you might still find it useful, especially if you manage data as part of your work. And if you don't have need to code yourself, you might find it valuable to know for dealing with coders in your organization, such as employees, contractors, and collaborators.

Credits

This web tutorial (and accompanying robot coding framework) was written by Markleford Friedman at the Digital Scholarship Center of UMass Amherst Libraries. The DSC promotes and supports research and instruction efforts of the campus community through the use of digital technologies, methodologies, and media.

This Online Educational Resource is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

For redistributed and derivative works, please acknowledge and attribute the work of "Markleford Friedman at the Digital Scholarship Center of UMass Amherst Libraries".

Contact

In the short term, due to the Covid pandemic, the Digital Scholarship Center has lost its funding to employ specialists to help answer coding questions (including the author).

Making things more difficult, the UMass Amherst Libraries web site is currently undergoing massive restructuring, and pages might disappear momentarily! If your luck holds out, you might still be able to get assistance by checking one of these sources:

  • Digital Scholarship Services landing page: This was the "landing page" for Digital Scholarship units across the libraries. One of the referenced departments might be able to help, or otherwise forward you to a knowledgable person sometime in the future, as the contents of this "hub" page will inevitably change.
  • Digital Scholarship Services Request form: You might also try filling out this form, which will hopefully be forwarded to a qualified specialist in due time (when hiring resumes).