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

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)

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

• Task 0 is the usual individual problem involving puzzles based on the problem set 3 solutions.
• In Task 1 (individual task), you will write functions using conditionals to create a rock-paper-scissors game.
• In Task 2 (partner-optional task), you will write some functions involving conditionals to guide a robot through a maze.
• In Task 3 (partner-optional task), you will create a quiz that uses functions with conditionals and user input to determine which animal you are.

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:

• Based on data from Spring 2019, we expect that this assignment will take most students 5.5–7.5 hours (2–3 hours per task), plus about an hour of reading time. When you've been working on the pset for about 5 hours, you should evaluate whether you are making efficient progress, and make use of some of the class resources available to you, like help-room hours or office hours. If you've been working on it for 7 or 8 hours and still have a ways to go, you should definitely get some help with it.
• Based on the above times, this assignment is about 30% longer than `ps03`, so expect to spend a bit more time on it (it also has 3 main tasks instead of 2).

• All code for this assignment is available in the `ps04` folder in the `cs111/download` directory within your `cs` server account. This assignment also uses the <a href="Codder">https://cs.wellesley.edu/codder">Codder program to help you do a final check of the first and third tasks before you submit.

• There are no Codder tests for task 2, to give you a chance to practice testing your code on your own. The starter code includes some tests you can run in Canopy, but it is your responsibility to make sure that your implementation does what the problem says it must (you should read the problem description carefully).

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')

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

In []: play('spock', 'sissors')

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

• The Python function call `random.randint(a, b)` (where `a` and `b` are integers and `a``b`) returns a randomly chosen integer in the range [`a`, `b`] (inclusive). For example, `random.randint(1, 10)` returns one of the integers between 1 and 10 with equal probability. The file `rockPaperScissors.py` begins with an `import random` declaration so that the `random.randint` from the `random` module can be used in this program.

• An important computer science principle is Don't Repeat Yourself (DRY). In this problem, you should avoid repeating yourself by using some of the functions you define as helper functions in other functions. For example, you should call the `beats` function within the `play` function rather than repeating the logic of which gesture beats which other gestures within the `play` function. For full credit, your code should obey this principle.

• Once you're satisifed with testing all your functions within Canopy, use Codder to test `rockPaperScissors.py`. This is a good way to check for any unexpected errors before you submit your code.

• For fun, check out the variant game rock-paper-scissors-lizard-Spock. Consider implementing this as an ungraded challenge problem. (But don't change your exisiting `rockPaperScissors.py` file; make a new file with a different name if you decide to pursue this.)

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``````

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:

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:

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:

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:

### 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

• Your agent function will be called once for every step the robot takes to decide what to do, getting fresh arguments each time based on the robot's current surroundings. Each time it returns a value, that value is used to move the robot, and 1 fuel unit is used up. If there is a wall blocking the selected direction, the robot will turn to face that direction but it will not move, and a unit of fuel will still be expended. If the robot runs out of fuel (based on the fuel budget given to the `animateAgent` function) then it has failed to solve the maze.

• The four parameters to the agent function should be passed into the `isNotWall` and `isOpen` functions. Don't test their values directly. The `turnAroundAgent` has an example of how to do this.

• If your agent function returns `None`, the simulation will crash. You need to make sure that this cannot possibly happen, no matter which branch(es) of which conditional(s) end up being used.

• If you'd like a challenge (not worth any points), try to construct a maze which cannot be solved by either the `cleverBounceAgent` or the `cleverLeftWallAgent`. Then, create a new agent that can solve that maze.

• There is no Codder for this task. Use the provided testing code instead.

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:

• Do you like meat?
• Do you like cold weather?
• Do you prefer furry things over smooth ones?

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
"""
# 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

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

```
```

### 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

• The Python string method `.lower()` returns a lower-case version of the string when called with zero arguments. For example:
``````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'``````
• The Python string method `.startswith(s)` takes a single string argument s and determines whether the string begins with s. For example:
``````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!``````
• You must use the `boolFromResponse` function in `boolFromUser`

• You must use the `boolFromUser` and `chooseAnimal` functions in `animalQuiz`.

• In `animalQuiz`, you must ask exactly the same questions in the same order. Your printout should match the examples above.

• You can use Codder to test your solution to this task.

## 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

• For Task 0, save your downloaded `ps03-diamonds-solution.json` and `ps03-butterflies-solution.json` files in your `ps04` folder.
• Save your final `rockPaperScissors.py` file in the `ps04` folder.
• Each team member should save their final `mazeAgent.py` and `animalQuiz.py` files in their `ps04` folders. The header comment at the top of these files should list the names of both partners (if you worked with a parter).
• Save your filled-out `honorcode.py` file in the `ps04` folder as well.
• Note: It is critical that the name of the folder you submit is `ps04`, and your submitted files are named `ps03-diamonds-solution.json`, `ps03-butterflies-solution.json`, `rockPaperScissors.py`, `mazeAgent.py`, `animalQuiz.py`, and `honorcode.py`. In other words, do not rename the folder that you downloaded, do not create new files or folders, and do not delete or re-name any of the existing files in this folder. We have automated scripts to check your electronic submission: an improperly named folder or a folder with improperly named files will not count as a valid submission.
• You may wish to run Codder one last time on `rockPaperScissors.py` and `animalQuiz.py` before you submit your `ps04` folder.
• Before submitting, double check that running your programs does not cause any errors. Such errors may lead to substantial deductions in your score.
• Drop your entire `ps04` folder in your `drop` folder on the `cs` server using Cyberduck by 11:59pm on the DUE DATE = Tuesday, Oct 1, 2019.
• Failure to submit a code file before the deadline will result in zero credit for that code on PS04.