**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.pyAt 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 |
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 --nostandardand 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).