Controlling Karel

Jed Rembold & Fred Agbo

January 15, 2025

Announcements

  • Problem Set 1 is posted and due on Monday night, 27 January
  • Sections start hopefully next week. If you have not yet filled out your availability, please do that now here!
  • Polling today! Use this link
    • I don’t need your full official name, but include enough that I can identify you!
    • The multiple choice options will not show until I start the poll.

Review Question

Suppose you had Karel starting in the world shown to the right. If Karel then executed the commands shown to the far right, what intersection would they end up at?

move()
turn_left()
turn_left()
move()
move()
turn_left()
turn_left()
turn_left()
move()
  1. 2nd avenue and 1st street
  2. 4th avenue and 2nd street
  3. 3rd avenue and 4th street
  4. Karel would hit a wall and error

Watching Karel Work

  • In Python, we can run Karel programs by including import karel near the top of the program
    • The file karel.py also needs to be in the same directory as your program
image/svg+xml Load newworld Save newworld Run entireprogram Run nextcommand Animationspeed Reset tobeginning Programtracer
Karel Interface

Functional Programs

  • Recall that we had to execute 3 left turns whenever we wanted to turn right
  • Functions give us a way to “bundle-up” this code in a form that is easier to use!
    • This is largely a convenience
    • Especially useful when the same sequence of commands is used repeatedly
  • Using functions takes place in two parts:
  • Where you define what is in the bundle

  • Syntactically, it looks like:

    def your_function_name():
        any_commands_to_be_bundled
  • Note that bundled commands need to be tabbed in!

  • Where you use the earlier defined bundle (the function)

  • Commonly referred to as calling the function

  • Use matched parentheses after the function name:

    your_function_name()

Turn Right Round

  • We can define a convenience function to turn right!

    def turn_right():
        turn_left()
        turn_left()
        turn_left()
  • Then we can just type turn_right() whenever we want to turn right

  • Internally, this is no different from what we had before (Karel still turns left 3 times), it is just easier to read and write

def main():
    turn_left()
    turn_left()
    turn_left()
    move()
    turn_left()
    move()
    move()
    turn_left()
    turn_left()
    turn_left()
    move()
    pick_beeper()
    turn_left()
    turn_left()
    turn_left()
    move()
    move()
    put_beeper()

Turn Right Round

  • We can define a convenience function to turn right!
def turn_right():
    turn_left()
    turn_left()
    turn_left()
  • Then we can just type turn_right() whenever we want to turn right
  • Internally, this is no different from what we had before (Karel still turns left 3 times), it is just easier to read and write
def main():
    turn_right()
    move()
    turn_left()
    move()
    move()
    turn_right()
    move()
    pick_beeper()
    turn_right()
    move()
    move()
    put_beeper()

def turn_right():
    turn_left()
    turn_left()
    turn_left()

Pothole Decomposition

  • Functions can also be a very effective way to break a larger problem into smaller, more managable pieces
  • Suppose we wanted Karel to fill all the “holes” in the below world with a beeper

Breaking down Potholes

  • From a big picture perspective, we might break this down something like:
    • Moving between potholes
    • Filling a pothole
  • Wherever one of those actions is simple and routine enough to write a function, we probably should!
    • Moving between potholes: this is not currently routine as the amount we need to move changes
    • Filling a pothole: this would be the same every time!
def main():
    move()
    fill_pothole()
    move()
    move()
    fill_pothole()
    move()

def fill_pothole():
    turn_right()
    move()
    put_beeper()
    turn_around()
    move()
    turn_right()

def turn_right():
    turn_left()
    turn_left()
    turn_left()

def turn_around():
    turn_left()
    turn_left()

A Quick Comment

  • It is always a good idea to document and leave notes to yourself (or future readers of your code)
  • Comments are pieces of text in your program that are ignored when the program is run
  • Python has two methods to make a comment:
    • Hashtag method: Everything following a hashtag (#) on the same line is ignored

      # This is a short comment!
      • Commonly used to make quick explanatory or labeling comments
    • Triple quote method: Every inside triple quotes (“““) is ignored

      """ This is also a comment! """
      • Commonly used to describe what functions do
      • Can span multiple lines

Commenting Example

def main():
    """
    Main function to fill 2 potholes 
    at known locations.
    """
    move()
    fill_pothole()
    move()
    move()
    fill_pothole()
    move()

def fill_pothole():
    """
    Fills a single pothole and returns 
    to where it started. 
    """
    turn_right()
    move()
    put_beeper() #assuming infinite beepers available
    turn_around()
    move()
    turn_right()

def turn_right():
    """ Turns Karel 90 to the right. """
    turn_left()
    turn_left()
    turn_left()

def turn_around():
    """ Convenience function to turn Karel 180 around. """
    turn_left()
    turn_left()

Making Choices

  • To solve more interesting tasks, we need to be able to write programs that can make choices about what they should be doing
  • Commands that alter the order that a program will run its commands are called control statements, which come in two flavors:
    • Conditional statements: Only run portions of the program if a condition is true
    • Iterative statements: Repeat portions of the program if certain conditions are met
  • Conditions are answers to yes or no (or true/false) type questions
    • Am I facing a wall?
    • Do I have any beepers in my bag?
  • You can ask these questions of Karel using what are called predicate functions, which are the programming equivalent of yes-or-no questions

Interrogating Karel: Predicate Functions

Potential questions you can ask Karel include:

front_is_clear() front_is_blocked()
left_is_clear() left_is_blocked()
right_is_clear() right_is_blocked()
beepers_present() no_beepers_present()
beepers_in_bag() no_beepers_in_bag()
facing_north() not_facing_north()
facing_south() not_facing_south()
facing_east() not_facing_east()
facing_west() not_facing_west()

Kinda Iffy

  • Predicate functions can be used to control a kind of “switch”: running one piece of code if the answer is yes and a different piece of code if the answer is no.

  • Commonly called if or if-else statements, they take on the syntax of:

    if conditional_test:
        # Code to run if test answer is yes
    else:
        # Code to run if test answer is no
  • If you don’t want the code to do anything special if the answer is no, you can ignore the “else” part of the statement:

    if conditional_test:
        # Code to run if test is true
    # Carrying on with code that will always run

Karel’s Decisions Example

  • Suppose we want Karel to move across the bottom of the world and fill in any gaps in the beepers
    • We want an even layer of beepers, no stacks
    • What questions should we have Karel ask?

Its been a while

  • Another common use of predicate functions is in controlling a type of iterative function called a while loop

  • The structure of a while loop looks like:

    while some_conditional_test:
        # Code to repeat as long as the answer
        # to the conditional_test is yes (true)
    # Code to run once the answer is no
  • All of our predicate functions give yes-or-no answers though! So we can do something like

    while front_is_clear():
        move()

    which will continually move Karel forward as long as there is not a wall in front of them!

Smarty Karel

  • Combining conditional statements with loops lets us write a program for Karel in which it can react to different situations in different ways, all using the same code
  • Our pothole code from earlier could only handle two potholes, and they had to be perfectly spaced
  • With one loop and one if statement, we can make the program fill any number of potholes with any manner of spacing!
  • Key questions:
    • How do we know when we are done?
    • How do we know when we reach a pothole?

Smart Potholes

def main():
    """
    Main function to fill any number of
    potholes at any location!
    """
    while front_is_clear():
        if right_is_clear():
            fill_pothole()
        move()

def fill_pothole():
    """
    Fills a single pothole and returns 
    to where it started. 
    """
    turn_right()
    move()
    put_beeper() #assuming infinite beepers available
    turn_around()
    move()
    turn_right()

def turn_right():
    """ Turns Karel 90 deg to the right. """
    turn_left()
    turn_left()
    turn_left()

def turn_around():
    """ Turns Karel 180 deg around. """
    turn_left()
    turn_left()

Class activity?

// reveal.js plugins