Problem Set 4 - Due Tue, Oct 1 at 23:59

Reading

  1. Slides and notebooks from Lec Functions and Lec 06 and 07: Booleans, Logical Expressions, Predicates, and Conditionals You do not need (in fact, should not use material from) Lec 08 Sequences and Simple Loops.
  2. Problems and solutions from Lab 03 Functions and Lab 04 Conditionals
  3. Think Python, Chapter 5: Conditionals (Secs 5.1 -- 5.7)

About this Problem Set

This problem set is intended to give you practice with conditionals and functions.


Unlike the previous two problem sets, this one has three tasks, and two of them are partner-optional. In Tasks 2 and 3, having a partner is optional, but strongly recommended. You can choose to work with a partner on both of the tasks, or just one of the tasks if you prefer. However, if you choose both of the tasks, you should work with the same partner on both tasks.

If you want to have a partner, use this shared Google Doc.

Remember, you can talk with other individuals and teams about high-level problem-solving strategies, but you cannot share any code with them.

Other notes:

 


Task 0: Scrambled Solutions

This is an individual problem which you must complete on your own, although you can ask for help from the CS111 staff.

As in problem set 3 Task 0 we have two puzzles for you to solve to review the problem set 3 solutions. One uses code from diamonds.py and the other uses code from butterflies.py. Go to:

CS 111 Puzzles

and select each of the options under Problem Set 3. These puzzles will only be made available after ps03 is due.

As before, please download and submit your solutions. The files should download as ps03-diamonds-solution.json and ps03-butterflies-solution.json. Also as before, please email pmawhort@wellesley.edu if you run into trouble.


Task 1: Rock, Paper, Scissors

This is an individual problem which you must complete on your own, though you may ask for help from the CS111 staff.

In this problem, you will define functions to play the game rock-paper-scissors. This is a game in which both you and your opponent each choose one of the three hand gestures rock, paper, or scissors, and the winner is chosen according to the following diagram:

Your task is to define the following five functions in the provided file named rockPaperScissors.py:

def isValidGesture(gesture):
    """
    Returns True if gesture is one of the strings 'rock', 'paper', or
    'scissors', and False otherwise.
    """
    # Flesh out the body of this function

def randomGesture():
    """
    Randomly returns one of 'rock', 'paper', or 'scissors', with equal
    probability.
    """
    # Flesh out the body of this function

def beats(gesture1, gesture2):
    """
    Returns True if the first gesture beats the second gesture, i.e., if
    the first and second gesture are rock/scissors, scissors/paper, or
    paper/rock, respectively. Returns False otherwise. The output is
    unspecified if one or both gestures is invalid.
    """
    # Flesh out the body of this function

def play(yourGesture,opponentGesture):
    """
    Plays rock/paper/scissors game with your gesture vs. opponent's
    gesture. If both gestures are valid, displays one of 'Game is a
    tie!', 'You win!', or 'Opponent wins!'. Otherwise, indicates the
    first gesture that is invalid.
    """
    # Flesh out the rest of the body of this function

def playComputer(yourGesture):
    """
    Plays rock/paper/scissors with your gesture against a computer
    opponent that randomly chooses a gesture. First displays the choice
    of the computer, and then displays the result of the game.
    """
    # Flesh out the rest of the body of this function

Interactive Testing in Canopy

One way to test your functions is to first run your program in the Canopy editor window (to load the function definitions) and then enter sample calls of your functions in the interactive console to test that they work correctly. Here are some examples:

Sample Output

In []: isValidGesture('paper')
Out[]: True

In []: isValidGesture('spock')
Out[]: False

In []: randomGesture()
Out[]: 'scissors' # You might get a different result because of randomness

In []: randomGesture()
Out[]: 'rock' # You might get a different result because of randomness

In []: randomGesture()
Out[]: 'scissors' # You might get a different result because of randomness

In []: beats('paper', 'rock')
Out[]: True

In []: beats('scissors', 'rock')
Out[]: False

In []: beats('rock', 'rock')
Out[]: False

In []: play('rock', 'paper')
Opponent wins!

In []: play('rock', 'scissors')
You win!

In []: play('rock', 'rock')
Game is a tie!

In []: play('rook', 'scissors')
Your gesture (rook) is invalid

In []: play('rock', 'sissors')
Opponent's gesture (sissors) is invalid

In []: play('spock', 'sissors')
Your gesture (spock) is invalid

In []: playComputer('paper')
Computer chooses rock
You win!
# You might get different printout because of randomness

In []: playComputer('paper')
Computer chooses paper
Game is a tie!
# You might get different printout because of randomness

In []: playComputer('paper')
Computer chooses paper
Game is a tie!
# You might get different printout because of randomness

In []: playComputer('paper')
Computer chooses scissors
Opponent wins!
# You might get different printout because of randomness

In []: playComputer('rock')
Computer chooses paper
Opponent wins!
# You might get different printout because of randomness

Automatic Testing in Canopy

Interactive testing can be tedious. As an alternative, we have provided a special main conditional block at the bottom of rockPaperScissors.py that includes test cases that will be automatically run every time you run your program in Canopy. This section begins as follows

if __name__ == "__main__":
    """All code that tests the above functions should be nested                       
    within this special "main" conditional.                                        
    """
    print("isValidGesture('paper') -->", isValidGesture('paper'))
    print("isValidGesture('spock') -->", isValidGesture('spock'))
    print("\nrandomGesture() -->", randomGesture())
    print("randomGesture() -->", randomGesture())
    ... many more tests below ...

Feel free to add or comment out tests in this block as desired.

Notes

 


Task 2: Exploring Mazes

In this problem, having a partner is optional, but is strongly recommended. If you want to have a partner, use this shared Google Doc.

This task involves a simple simulation of a robot in a maze. The maze is just a string that uses hash signs for walls, and the 'robot' is an arrow character (<, ^, >, or v depending which direction it is facing). The robot's goal is to navigate through the maze to reach the goal (G) before it runs out of fuel. A trivial maze looks like this:

######
#>  G#
######

A more complicated maze:

#########
# #     #
#   # # #
# ### ###
# ^#   G#
#########

Note that the robot leaves a trail of dots behind when it moves, so after some exploration, the same maze might look like this:

#########
#.#...  #
#...#.# #
#.###v###
#..#   G#
#########

Background

We have given you a file maze.py which implements this maze world, and your task is to write the rules that the robot will use to decide how to move. A set of rules for moving the robot is called an agent, and is implemented using a function. These agent functions take four parameters which indicate the tile in the maze on each side of the robot (ahead, to the right, behind, and to the left in that order). Agent functions must return one of the special variables from maze.py that indicate a direction to move: one of maze.FORWARD, maze.RIGHT, maze.BACKWARD, or maze.LEFT. The maze.py file defines a function animateAgent which takes four parameters: a maze, an agent, a fuel budget, and an animation speed, and it prints out an animation that shows how the robot moves through the maze.

Here's an example of a very simple agent which goes forward and turns around if it cannot go forward:

def turnAroundAgent(ahead, right, behind, left):
    """
    The turnAroundAgent always moves forward unless there's a wall ahead,
    in which case it turns around. It is very bad at solving mazes.
    """
    if isNotWall(ahead): # there's no wall in front of us: go forward
        return maze.FORWARD # keep going forward
    else: # otherwise go back
        return maze.BACKWARD # tell the robot to go backwards

Your Job

You must create the following agents (in the mazeAgent.py file):

  1. A bounceAgent that always moves forward if it can, and when it hits a wall, it turns left, or if it can't turn left (because there's a wall there too) it turns right. If it is blocked on all sides and ahead, it will go backwards. This agent can solve labyrinths (mazes without branches) but it gets stuck in most true mazes.

    Your bounceAgent must use the isNotWall function to determine where walls are, and it must not use the isOpen function.

    Here's what the bounceAgent looks like solving a maze:

    An animation of the bounce agent moving through a maze.

  2. A leftWallAgent that implements the left-hand rule for solving mazes: always keep your hand on the left wall, and you will eventually reach the goal. Specifically, the leftWallAgent should do the following:

    • If there is no wall to the left, move left.
    • If there is a wall to the left and there is no wall in front, move forward.
    • If there are walls on the left and ahead, but there is no wall to the right, move to the right.
    • If there are walls to the left, ahead, and to the right, give up and move backwards.

    This agent, although it isn't that different from the bounce agent, can actually solve any maze that doesn't include an open area.

    Your leftWallAgent must use the isNotWall function to determine where walls are, and it must not use the isOpen function.

    Here's what the leftWallAgent looks like solving a maze:

    An animation of the left wall agent moving through a maze.

  3. A cleverBounceAgent, which works like the bounceAgent, except that it first checks whether there is an "open" spot nearby, and moves there if it can. Use the supplied predicate isOpen to detect open spots, which are either empty or the goal. There is a subtle distinction between the isNotWall and isOpen predicates: the robot's trail of dots are not walls (and the robot can move through them) but they aren't "open" either. So this clever agent will try to explore new areas that it hasn't been to whenever it can, which dramatically improves the performance of the bounceAgent.

    If there are multiple adjacent open spots, your cleverBounceAgent must prioritize open spots in the following order: ahead, left, right, behind. For full credit, your cleverBounceAgent function must call your bounceAgent function if there are no adjacent open spots.

    Here's what the cleverBounceAgent looks like solving a maze:

    An animation of the clever bounce agent moving through a maze.

  4. A cleverLeftWallAgent, which works like the leftWallAgent, except that just like the cleverBounceAgent it prioritizes open spaces. If there are multiple open spaces, it should prioritize them in the following order: left, ahead, right, behind.

    For full credit, your cleverLeftWallAgent must call your leftWallAgent function if there are no adjacent open spaces.

    Here's what the cleverLeftWallAgent looks like solving a maze:

    An animation of the clever left wall agent moving through a maze.

Automatic Testing in Canopy

There are automatic testing functions at the bottom of the file. You can uncomment one or a few of these at a time to test your code every time you run it. This is usually better than interactive testing, because there's no risk that you forget to re-run the code after making a change.

Interactive Testing in Canopy

Another way to test your functions is to first run your program in the Canopy editor window (to load the function definitions) and then call the maze.animateAgent function to test them. The output will be long (because it prints the maze over and over again as the robot moves), but here's an example (note you can use higher numbers as the last argument to make it go faster, or use 0 to print the whole thing as fast as possible):

In [1]: maze.animateAgent(maze.MAZE0, goForwardAgent, 10, 2)
--------------------------------------------------------------------------------
Step 0
######
#>  G#
######
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Step 1
######
#.> G#
######
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Step 2
######
#..>G#
######
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Step 3
######
#...S#
######
--------------------------------------------------------------------------------

Note in particular how we use goForwardAgent as a parameter to the animateAgent function without any parentheses afterwards. We aren't using the return value of the function, we are using the function itself as an argument (and that's what the animateAgent function is expecting).

Notes

 


Task 3: Animal Quiz

In this problem, having a partner is optional, but is strongly recommended. If you want to have a partner, use this shared Google Doc.

What animal are you? In this problem, you will write a quiz program that determines your animal based on answers to three questions:

The answers to these three questions determine the animal shown in this table:

like meat? like cold? prefer furry? animal
True True True polar bear
True True False orca
True False True tiger
True False False komodo dragon
False True True yak
False True False clam
False False True bunny
False False False tortoise

Your task is to flesh out the following four function definitions in the provided file named animalQuiz.py:

def boolFromResponse(response): 
    """Return True if the `response` string begins with a 'Y' or 'y'; 
    return False in all other cases. 
    The .lower() and .startswith() methods on strings are 
    helpful in this function.
    """
    # Flesh out the body of this function

def boolFromUser(prompt): 
    """Prompt the user with the yes/no question in the `prompt` string.
    Return True if the user response begins with a 'Y' or 'y'; 
    return False for all other user responses.
    You *must* use `boolFromResponse` in this function."""
    # Flesh out the body of this function

def chooseAnimal(likesMeat, likesCold, prefersFurry):
    """Based on the three boolean values `likesMeat`, `likesCold`,
    and `prefersFurry`, return the animal string determined by the table 
    in the description for PS04 Task 3."""
    # Flesh out the body of this function

def animalQuiz():
    """Prompt the user with three yes/no questions, and then display
    the animal determined by their answers. See the 
    PS04 Task 3 description for exact wording.
    You *must* use `boolFromUser` and `chooseAnimal` in this function."""
    # Flesh out the body of this function

Interactive Testing in Canopy

One way to test your functions is to first run your program in the Canopy editor window (to load the function definitions) and then enter sample calls of your functions in the interactive console to test that they work correctly. Here are some examples:

Sample Output for boolFromResponse

In []: boolFromResponse('Yes')
Out[]: True

In []: boolFromResponse('yes')
Out[]: True

In []: boolFromResponse('yup')
Out[]: True

In []: boolFromResponse('Yellow')
Out[]: True

In []: boolFromResponse('y')
Out[]: True

In []: boolFromResponse('No')
Out[]: False

In []: boolFromResponse('nope')
Out[]: False

In []: boolFromResponse('maybe')
Out[]: False

In []: boolFromResponse('affirmative')
Out[]: False

Sample Output for boolFromUser

In these examples, the text in magenta represents input typed by the user.


In []: boolFromUser('Are you happy? ')

Are you happy? yup
Out[]: True

In []: boolFromUser('Are you happy? ')

Are you happy? NO
Out[]: False

In []: boolFromUser('Are you happy? ')

Are you happy? absolutely!
Out[]: False

Sample Output for chooseAnimal

In []: chooseAnimal(False, False, True)
Out[]: 'bunny'

In []: chooseAnimal(True, False, False)
Out[]: 'komodo dragon'

In []: chooseAnimal(True, False, True)
Out[]: 'tiger'

Sample Output for animalQuiz

In these examples, the text in magenta represents input typed by the user.


In []: animalQuiz()
What animal are you? Let's find out!

Do you like to eat meat? Nope

Do you like cold weather? yup!

Do you like furry things? Y

Your animal is the yak

In []: animalQuiz()
What animal are you? Let's find out!

Do you like to eat meat? unsure

Do you like cold weather? maybe

Do you like furry things? absolutely

Your animal is the tortoise

Automatic Testing in Canopy

Interactive testing can be tedious. As an alternative, we have provided a special main conditional block at the bottom of animalQuiz.py that includes calls to testing functions that will be automatically run every time you run your program in Canopy. This section looks like:

if __name__ == '__main__':
    '''Uncomment calls to these testing functions when ready'''
    # testBoolFromResponse()
    # testChooseAnimal()
    # print(boolFromUser('Are you happy? '))
    # animalQuiz()

If you uncomment the line # testBoolFromResponse(), then running animalQuiz.py in Canopy should produce the following output:

PASSED: boolFromResponse('Yes') returned expected value True
PASSED: boolFromResponse('yes') returned expected value True
PASSED: boolFromResponse('Yes!') returned expected value True
PASSED: boolFromResponse('yes!') returned expected value True
PASSED: boolFromResponse('Y') returned expected value True
PASSED: boolFromResponse('y') returned expected value True
PASSED: boolFromResponse('Yup') returned expected value True
PASSED: boolFromResponse('Yow!') returned expected value True
PASSED: boolFromResponse('yellow') returned expected value True
PASSED: boolFromResponse('yzz') returned expected value True
PASSED: boolFromResponse('No') returned expected value False
PASSED: boolFromResponse('no') returned expected value False
PASSED: boolFromResponse('Nope') returned expected value False
PASSED: boolFromResponse('nosiree') returned expected value False
PASSED: boolFromResponse('N') returned expected value False
PASSED: boolFromResponse('n') returned expected value False
PASSED: boolFromResponse('maybe') returned expected value False
PASSED: boolFromResponse('probably') returned expected value False
PASSED: boolFromResponse('affirmative') returned expected value False
PASSED: boolFromResponse('ABC') returned expected value False
PASSED: boolFromResponse('xyz') returned expected value False
PASSED: boolFromResponse('123') returned expected value False
PASSED: boolFromResponse('CS111') returned expected value False

If any of the lines begins ***FAILED, this indicates a bug in your function that you need to fix.

If you only uncomment the line # testChooseAnimal(), then running animalQuiz.py in Canopy should produce the following output:

PASSED: chooseAnimal(True, True, True) returned expected value 'polar bear'
PASSED: chooseAnimal(True, True, False) returned expected value 'orca'
PASSED: chooseAnimal(True, False, True) returned expected value 'tiger'
PASSED: chooseAnimal(True, False, False) returned expected value 'komodo dragon'
PASSED: chooseAnimal(False, True, True) returned expected value 'yak'
PASSED: chooseAnimal(False, True, False) returned expected value 'clam'
PASSED: chooseAnimal(False, False, True) returned expected value 'bunny'
PASSED: chooseAnimal(False, False, False) returned expected value 'tortoise'

If you only uncomment one of the other two lines, then running animalQuiz.py will test the boolFromUser or animalQuiz function, which will involve prompting you for input and printing a response.

Notes

In []: 'Wendy Wellesley'.lower()
Out[]: 'wendy wellesley'

In []: 'CS111'.lower()
Out[]: 'cs111'

In []: 'SHOUT!'.lower()
Out[]: 'shout!'

In []: 'the quick brown fox'.lower()
In []: 'the quick brown fox'
In []: 'Wendy Wellesley'.startswith('Wend')
Out[]: True

In []: 'Wendy Wellesley'.startswith('Well')
Out[]: False

In []: 'Wendy Wellesley'.startswith('W')
Out[]: True

In []: 'Wendy Wellesley'.startswith('w') 
Out[]: False # Case matters!

 


Task 4: Honor Code File

As usual, fill out the time you spent on each task and how you collaborated with others in your `honorcode.py file.


How to turn in this Problem Set