**CS 111 f19 - Homework 6 (pair programming)** *Due: Monday, November 11, at 9pm* In this homework you will practice creating classes that operate as components within a larger program. The larger program in question is the *Critter World* simulation that pits different *critters* against each other in a free-for-all struggle. You will create classes for a set of critters each with different behavior. To begin, download the starter files in [hw6.zip](hw6.zip). **You will need to put homework 6 files in their own folder for Critter World to work correctly.** These files (which you will not need to modify at all) are - `color.py`: sets up variables for different colors. You will access these by including `import color` at the top of your file and then writing `color.BLACK` (for example) - `attack.py`: sets up variables for the different attacks critters can use. You will access these by including `import attack` at the top of your file and then writing `attack.POUNCE` (for example) - `direction.py`: sets up variables for the different directions in the Critter World grid. You will access these by including `import direction` at the top of your file and then writing `direction.NORTH` (for example) - `critter_gui.py`: defines a class that creates and manages the user interface for Critter World - `critter_model.py`: defines a class that manages the behavior of critters in Critter World - `critter_main.py`: defines functions that actually run Critter World ![Critter World populated with the five standard critters](critter_world.png) Take a look at the current state of Critter world by running
  python3 critter_main.py
At the beginning, you will see an empty grid with no critters. The Go button starts the simulation; initially there is nothing to simulate so the move count will just count up with nothing happening. The real simulation gets going once you have some critters on your board. On each round of the simulation, each critter is asked which direction it wants to move. On each round, each critter can move one square to the north, south, east, west, or stay at its current location. Critters move around in a world of finite size, but the world is toroidal (going off the end to the right brings you back to the left and vice versa; going off the end to the top brings you back to the bottom and vice versa). The critter world is divided into cells that have integer coordinates. There are 60 cells across and 50 cells up and down. The upper-left cell has coordinates (0,0), increasing x values moves you right and increasing y values move you down (similar to images in homework 5). This program may be confusing at first because you are not writing the main component (i.e., `critter_main.py` and the other files that will use your critter objects), therefore your code will not be in control of the overall program's execution. Instead, you are defining a series of objects that become part of a larger system. For example, you might find that you want to have one of your critters make several moves all at once--you won't be able to do that. The only way a critter can move is to wait for the simulator to ask it for a move. Although this experience can be frustrating, it is a good introduction to the kind of programming we do with objects. As the simulation runs, critters may collide by moving onto the same location. When two critters collide, they fight to the death. The winning animal survives and the losing animal is removed from the simulation. The following table summarizes the possible fighting choices each animal can make and which animal will win in each case. To help you remember which beats which, notice that the starting letters and win/loss ratings of "roar, pounce, scratch" correspond to those of "rock, paper, scissors." If the critters make the same choice, the winner is chosen at random.
Critter #2
ROAR POUNCE SCRATCH
Critter #1 ROAR  random winner  #2 wins #1 wins
POUNCE #1 wins  random winner  #2 wins
  SCRATCH   #2 wins #1 wins  random winner 
Please post your questions on the [homework forum](https://moodle.carleton.edu/mod/forum/view.php?id=494276) or bring them to class. For this homework, each successfully completed OPTIONAL item will earn two bonus points (~3% of the homework grade). Timeline (guidelines to help you stay on track for this homework): - Implement the **Stone** and **Mouse** critters by the end of Tuesday, Nov. 5 - Implement the **Tiger** and **Elephant** critters by the end of Friday, Nov. 8 - This leaves you the weekend and Monday for the **Chameleon** critter and any OPTIONAL extensions --- # Getting Started Each critter class in your program must have the following methods: - `fight(self, oppInfo)`: returns this critter's attack when it collides with another critter - `getColor(self)`: returns this critter's color - `getMove(self, info)`: returns this critter's move for the round - `getChar(self)`: returns the character (one-letter string) that represents this critter - `fightOver(self, won, oppAttack)`: method called after each fight, returns nothing To start out, let's add the stationary, yet mighty **Stone** critter to our world. Read through the code below and the comments on each method. **Stones** don't move (`getMove` always returns `direction.CENTER`), and always use the `ROAR` attack. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python import color, attack, direction class Stone(): # we can omit a __init__ method if there's nothing we need to initialize # @param oppInfo The critter info of the current opponent. # @returns Your attack: attack.ROAR, attack.POUNCE, or attack.SCRATCH def fight(self, oppInfo): return attack.ROAR # Give your color. # @returns Your current color. def getColor(self): return color.GRAY # Give your move this round. # @param info your critter info # @returns A cardinal direction or staying put: # (direction.NORTH, direction.SOUTH, direction.EAST, direction.WEST, direction.CENTER) def getMove(self, info): return direction.CENTER # Give the letter that represents you. # @returns Whichever character represents this critter. def getChar(self): return 'S' # End of fight shenanigans. # @param won Boolean; true if won fight, false otherwise. # @param oppAttack Opponent's choice of fight strategy (ROAR, etc) # @returns Nothing. def fightOver(self, won, oppAttack): # we don't do anything in fightOver method here, because a # Stone doesn't do anything with this information # we have to have the method because the simulation expects every critter class to have one # and we'll get a syntax error if we have the def line with nothing indented after it # so we use the pass instruction since it literally means "do nothing" # leave this method as just pass unless the critter needs to change its behavior based on these parameters pass def __str__(self): return self.__class__.__qualname__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Add this code to a file called `stone.py`, then run `critter_main`. Now you should see some stones in your world. Of course, **Stones** don't move, so even when you press the *Go* button, nothing happens, but we'll do more interesting critters in due time. The `direction` module defines nine constants for the various directions (`NORTH` , `NORTHEAST`, `NORTHWEST`, `SOUTH`, `SOUTHEAST`, `SOUTHWEST`, `EAST`, `WEST` `CENTER`), and the `attack` module defines three constants for the three types of fighting (`ROAR`, `POUNCE`, `SCRATCH`). You make these available to your code with `import direction` and `import attack`. # Critters to Implement The following are the five critter classes you will implement. Each class must have only one constructor and that constructor must accept exactly the parameter(s) described in the table. For random moves, each possible choice must be equally likely (use `random.choice(choices)` to return a random element from a list `choices`). Put each definition in its own file with the specified name. ## Mouse (in `mouse.py`) | | | |-----------|-----------------| | constructor | `def __init__(self, color)` | | fighting behavior | always `attack.SCRATCH` | color | the color passed to the constructor | | movement behavior | alternates between `direction.EAST` and `direction.SOUTH` in a zigzag pattern (first `direction.EAST`, then `direction.SOUTH`, then `direction.EAST`, then `direction.SOUTH`, ...) | character | `'M'` | The **Mouse** constructor accepts a parameter representing the color in which the **Mouse** should be drawn. This color should be returned each time the `getColor` method is called on the **Mouse** (so, maybe you should store it in an instance variable). For example, a **Mouse** constructed with a parameter value of `color.RED` will return `color.RED` from its `getColor` method and will therefore appear red on the screen. Of course, it is the main program that will construct these mice, so you will have no control over what color each Mouse is displayed as. Remember, the `getMove` method is called by the main program each time the critter needs to move; you can use this information (and perhaps another instance variable) to keep track of whether it is time to move East or to move South. ## Tiger (in `tiger.py`) | | | |-----------|-----------------| | constructor | `def __init__(self)` | | fighting behavior | always `attack.ROAR` | | color | alternates between `color.ORANGE` and `color.BLACK` (first `color.ORANGE`, then `color.BLACK`, then ...) | | movement behavior | moves 3 turns in a row in one random direction (`direction.NORTH`, `direction.SOUTH`, `direction.EAST`, or `direction.WEST`), then chooses a new random direction and repeats | | character | `'T'` | **Note that `getColor` may be called multiple times per step, and is thus not a good method in which to keep track of the number of steps that have passed.** ## Elephant (in `elephant.py`) | | | |-----------|-----------------| | constructor | `def __init__(self, steps)` | | fighting behavior | if opponent displays as a **Tiger** (with the character `'T'`), then `attack.ROAR`; otherwise `attack.POUNCE` | | color | `color.GRAY` | | movement behavior | first go `direction.SOUTH` steps times, then go `direction.WEST` steps times, then go `direction.NORTH` steps times, then go `direction.EAST` steps times (a clockwise square pattern), then repeats | | character | `'E'` | The **Elephant** constructor accepts a parameter representing the distance the **Elephant** will walk in each direction before changing direction. For example, an **Elephant** constructed with a parameter value of 8 will walk 8 steps south, 8 steps west, 8 steps north, 8 steps east, and repeat. You can assume that the value passed for steps is at least 1. For the `fight` method, you need to determine your opponent's display character. Every time a critter's `fight` method is called, it is passed a parameter `oppInfo` of type `CritterInfo` that provides useful information about the current opponent, including its display character. In particular, a critter's `CritterInfo` contains the following instance variables and the method `getNeighbor`:
    x -- the current critter's x coordinate
    y -- the current critter's y coordinate
    width -- the width of the simulation world
    height -- the height of the simulation world
    char -- the current critter's display character
    color -- the current critter's display color
    getNeighbor(direction) -- a method that, when called with a parameter
       representing one of the direction constants, returns the name of the class
       (NOT the display character) of the critter in that location
       (i.e. the location that is one space in the given direction of the current critter.)
Thus, in the `fight` method, the code `oppInfo.char` represents the opponent critter's display character, and `oppInfo.color` its color. Slightly less useful now, the code `oppInfo.getNeighbor(direction.NORTH)` would return the string `'Stone'` if there is a stone to the north of your opponent, and the string `'.'` if there is no neighbor to the north. Note that every time a critter's `getMove` method is called, it is passed a parameter `info` also of type `CritterInfo`; this time, the information being passed is about the current (`self`) critter, not of an opponent. Once again, some of the information (for example, knowing what neighbors you have in your immediate vicinity) may help you make movement choices. ## Chameleon (in `chameleon.py`) | | | |-----------|-----------------| | constructor | `def __init__(self)` | fighting behavior | the attack used by this **Chameleon's** last opponent (in its very first fight, the **Chameleon** uses a random attack) | color | the color used by this **Chameleon's** last opponent (all **Chameleons** start as color.GREEN) | movement behavior | the **Chameleon** waits for its prey--stays still unless it sees a critter it can move to | character | `'C'` The **Chameleon** will try to emulate the appearance of its most recent (losing) opponent by displaying the opponent's color. (You can get this information from the `fight` method's `oppInfo` parameter.) As its fighting behavior, a **Chameleon** will use the attack of its most recent opponent. You can obtain information about the outcome of each fight from the `fightOver` method, which is called at the end of each and every fight, and tells each critter whether it won or lost the fight (`won` is `True` if the former, `False` if the latter) and what its opponent's attack was (`oppAttack` is one of `attack.ROAR`, `attack.POUNCE`, or `attack.SCRATCH`). Finally, a **Chameleon's** movement strategy is based on its surroundings. It moves to fight any neighbor it sees and otherwise stays still. You can find out which critters (if any) are to your North, South, East, and West through the `info` parameter (of type `CritterInfo`, described above) that is passed to the getMove method. For example, `info.getNeighbor(direction.NORTH)` will return the class name (as a string) of the critter to the North, and `'.'` if there is none. # OPTIONAL extensions ## Hive Mind Chameleons Enhance the **Chameleon** critter to have the following behavior: | | | |-----------|-----------------| | constructor | `def __init__(self)` | fighting behavior | the attack that beats the most used attack against the **Chameleons** so far (not just this particular **Chameleon**) | color | the color used by this **Chameleon's** last opponent (all **Chameleons** start as color.GREEN) | movement behavior | if the **Chameleons** have lost the majority of their fights so far, then avoid enemy critters. Otherwise, move towards opponents. | character | `'C'` For both fighting and movement, this version of the **Chameleon** relies on a *hive mind* where information is shared among all **Chameleons**. For fighting, a **Chameleon** will use the attack that beats (and is thus different from) the most past opponents of **Chameleons**; that is, the **Chameleon** *species* needs to keep counts on how often each attack is used against a **Chameleon** (think: static (i.e., non-instance) variables) and play the counter-attack to the most-used one. A hive mind **Chameleon's** movement strategy is based on the **Chameleon** species' success rate: if they are not faring well (if **Chameleons** have lost at least half their fights) then they will try to avoid other critters, that is, move away from adjacent critters if possible, or stay in the same spot if surrounded. If **Chameleons** are winning over half their fights then they become aggressive and move towards other critters. ## No Friendly-Fire Tigers The version of the **Tiger** critter you implemented above is just as willing to fight other **Tigers** as it is to fight any critter. Change the `getMove` method of the **Tiger** class so that **Tigers** will never fight each other. To test your code, run
  python3 critter_main.py --fight Tiger Tiger --nostandard
and make sure no **Tigers** ever fight (this command runs a Critter World with 50 **Tigers** and no other critters). ## Critter Tournament Submit a critter of your own design to compete in a Critter World tournament. Credit will only be given for critters with creative and non-trivial behavior. A very simple critter or one that too closely resembles one of the standard critters (or another extension you submitted) will receive partial or zero bonus points for this extension. Your critter's fighting behavior may want to utilize the parameter sent to the `fight` method, `oppInfo`, which tells you how your opponent displays themselves (`oppInfo.char` is their display character, `oppInfo.color` is their display color, and `oppInfo.getNeighbor(critter.CENTER)` is their class name; for a **Mouse** these might be `'M'`, `Color(r=133, g=132, b=118)`, and `"Mouse"`, respectively.) You can make your critter return any character you like from its `getChar` method and any color you like from the `getColor` method. In fact, critters are asked what display color and character to use on each round of the simulation, so you can have a critter that displays itself differently over time. Keep in mind that the `getChar` character is also passed to other critters when they fight your critter; you may wish to strategize to try to fool opponents. We will host the Critter World tournament in class consisting of battles in the following format: two critter classes will be placed into the simulator world along with the other standard critters (**Stone**, **Elephant**, **Tiger**, and **Mouse**), with 25 of each critter type. The simulator will be started and run until no significant activity is occurring or until 1000 moves have passed, whichever comes first. The student whose critter class has the higher score (other critters defeated + your critters alive) wins the battle. The winner of the tournament will gain fame and fortune! # What to Turn In Submit the following files via the [Moodle Homework 6 page](https://moodle.carleton.edu/mod/assign/view.php?id=494277). You **do not** need to submit any of the starter files. - Four Python files, one for each of the critters you implemented: `mouse.py`, `tiger.py`, `elephant.py`, and `chameleon.py` - OPTIONAL `extensions.txt` describing which extensions you did, and a file called `tournament.py` with your tournament Critter if you did that extension - A plain text file called `feedback.txt` with the following information (you get points for answering these questions!): - How many hours you spent outside of class on this homework. - The difficulty of this homework for this point in a 100-level course as: too easy, easy, moderate, challenging, or too hard. - What did you learn on this homework (very briefly)? Rate the educational value relative to the time invested from 1 (low) to 5 (high). --- Acknowledgments: This assignment description is modified from [Adam Eck's Critter Tournament lab](http://www.cs.oberlin.edu/~aeck/Fall2018/CSCI150/Labs/Lab10/index.html).