Details
Due date
This assignment is due on Monday, June 3rd 11:30PM
Assignment Overview
This last Mini Project covers everything up through Week 9, and is the third (and final!) OOP
    assignment, implemented in Java. You will find some similarities to your work in MP7,
    though you will also practice working with Random and arrays
    in Java. Other than that, you will be reviewing a cumulation of fundamental concepts we've learned throughout the 
    term, including loops, string processing, and OOP (which we choose to implement in Java).
    You will not need to write any documentation for this assignment, unless you implement 
    optional Part C features.
This assignment is an apt CS1-variant of the classic "Game of Life". The Game of Life is a simulation where cells follow some heuristics to overcome their neighbors. In this version, cells will represent "PokePixels", of which we will start with 5 types corresponding to a specific Pokemon type (e.g. "Fire"), each represented by a color.
    The Game of Life is represented as a 2D grid of pixels (also referred to as cells); we've seen 2D lists in Python (and perhaps numpy!)
    and you will get just a basic introduction to the analog in Java, which is covered on Monday's lecture 
    and which will be walked through whenever you use them in this assignment. Lab 08 will also
    be another motivating application of 2D arrays in Java!  
    How does the Game of Life work? The game first initializes a random board of pixels,
    each representing a cell type (different variants will define cell types differently; if you really wanted to,
    you could create a Game of Life variant using Caltech houses for cells). Then, a game loop
    is started (this is managed in our Game.java, which you won't modify in Parts A/B).
    For each lifetime "cycle", a the 2D grid is looped through, row by row, cell by cell,
    and applying an "update rule" for each cell to determine whether its type changes. 
Some Game of Life simulations define their heuristics by looking at each pixels neighbors and changing the neighbors depending on the placement of types, but ours will focus on looking at the neighbors to determine whether the pixel of interest is changed.
Example screenshots of the first 3 cycles of the game of life variant, followed by the 35th, are given below:
 
     
     
     
Example results printed to console after a game finishes:
Winning type: Fire
Since this game is initialized by randomly-assigning each pixel a type, we have built in support
    to test your game with a fixed board allocation. You will see a private boolean isRandom;
     field declaration in PokeLife.java which initializeBoard
    will use to assign each pixel a pre-determined type (the PokeLife constructor 
    you will implement will take a isRandom boolean flag to toggle the test mode state 
    our tests to modify the flag for randomness/non-randomness). 
    Game.java has a testMode field which can have 1 of three values
    (we have started it with the mode of 1 for you to test the small board first):
    
- 0 - random 60x60 board with 35 lifetime cycles each with a .5s delay
- 1 - non-random 3x4 (small) board with 4 lifetime cycles each with a 5s delay
- 2 - non-random 60x60 (standard) board with 35 lifetime cycles each with a 2.5s cycle delay
Game.java's testMode set to 2 
    and constructing your PokeLife with the isRandom argument set to false are provided below: 
 
     
     
     
Example results printed to console after the game finishes 35 lifetime cycles:
Winning type: Grass
    The results of running the game 
    with Game.java's testMode set to 1 (small non-random board test)
    and constructing your PokeLife with the isRandom argument set to false are provided below: 
 
 
 
 
Example results printed to console after the game finishes 4 lifetime cycles:
Cycle: 0 Fir Ele Ele Gro Gra Fir Gro Ele Fir Gra Wat Wat Gro Gro Ele Gra Gro Gra Gra Ele Cycle: 1 Fir Ele Gro Gra Gra Fir Gro Gro Gro Gra Wat Wat Gro Gra Ele Gra Gro Gra Gra Ele Cycle: 2 Fir Gro Gra Gra Gra Fir Gro Gra Gra Gra Wat Wat Gra Gra Ele Gra Gro Gra Gra Ele Cycle: 3 Gro Gra Gra Gra Gra Gro Gra Gra Gra Gra Wat Gra Gra Gra Ele Gra Gra Gra Gra Ele Winning type: Grass
As you can see, some interesting patterns can occur depending on your board initialization heuristics
    and updateType/lifeCycle methods you'll be seeing!
    In Part C, you'll have an option to extend the game with your own heuristics if you'd like.
    You should update the testMode in Game.java to 2 and 0 to test the
    large non-random board results (which should match the second set of screenshots above) and 
    random board results (which will be random, similar to the first set of example screenshots).
    Note: In 23fa, we updated the board representation in PokeLife to be
    easier to visualize with the graphic output; in particular, we will be using (x, y) coordinates 
    to refer to pixels on the board as shown in the toString representation which
    corresponds to the graphical output (using 0-based indexing). This is a common convention
    in representing coordinates in graphics (but not the only one) where x and y
    coordinates will start at (0, 0) on the top-left and increase right/down, respectively. Make sure to
    double-check with the examples!
What to submit
You will be submitting 2 files for this assignment, each of which we have provided starting templates for you
    in mp8_starter.zip:
- PokePixel.java(Part A)
- PokeLife.java(Part B)
- (Optional extensions from Part C)
You will also see the following files included in the starter code, which you do not need to modify:
- DrawingPanel.java- helper class for graphics
- Game.java- main game program
Testing Your Code
We encourage you to test your code iteratively, using a client program (with main to test calls to your Part A and B code, using the examples given.
We have provided two testing files for Part A and B in mp8_tests.zip which also include a studentTestArea function if you'd like to use the debugger for your own testing calls. See the file documentation
for each test for instructions to run as well as comments for test cases if you are failing something (if you are failing a test, make sure to re-read the spec instructions carefully!).
No Collaboration Policy
This assignment is strictly no collaboration, similar to MP1-MP6. Students who violate the collaboration policy will be subject to 0 and escalated to the BoC. We are making a clear reminder here given recent violations that our grading software has flagged, and want to remind you that the Honor Code is taken very seriously at Caltech. It's not worth the risk, and we would much rather you utilize course resources than disadvantage yourself and your peers.
Part A: PokePixel.java
        The first class you will finish implementing is a simple class representing one of 
        the pixels on the board, called a PokePixel. When you play the game,
        each pixel has one of 5 colors, which are defined in the provided Game.java.
        Each color represents one of the 5 types managed by PokePixel and PokeLife.
    
        You will finish implementing the following methods to create PokePixels 
        of random types which can be accessed and updated using getters and setters.
    
1. PokePixel Constructor
[10]
 The PokePixel constructor takes no arguments, however it must set a
    type for that PokePixel instance (which is the only field in the PokePixel). The PokePixel class is provided with
    a types array. This constructor should randomly select a type, and store
    it in the type field of the PokePixel. To get a random integer in Java, use the Random object
and its nextInt method, which takes an int n as an argument and returns a random
int between 0 (inclusive) and n (exclusive). Use the
randomly-generated int to access a random index in the TYPES array (a class constant). For review of using Random in Java, you may find the RandomDemo.java
program posted with lecture materials useful (along with the combined Week 8 Slide Deck)
    Note that the PokePixel has two class constants: a TYPES array of Strings
    defining all of the valid Pokemon types, and a Random object rand
    which is constructed as a class constant. Both of these are class constants since they
    should never be changed, and do not represent unique state between different PokePixel
    instances. Use the rand object (don't re-assign it) in your constructor. Do not hard-code any numbers
    in this method (use the length of the TYPES array to get the random int bounds).
In this application, a pixel can have two weaknesses (e.g. "Water" is weak to both "Grass" and "Electric") but every pixel has exactly one type (e.g. a pixel can't be both "Water" and "Grass").
    For reference, our solution is 2 lines in the constructor. An example of using Random
    is provided below, but again, you shouldn't re-construct rand in your constructor
    since it's already constructed as a program constant.
Random rand = new Random(); int n = rand.nextInt(2); // 1 n = rand.nextInt(2); // 1 n = rand.nextInt(2); // 0 n = rand.nextInt(2); // 1 n = rand.nextInt(2); // 1
PokePixel ex = new PokePixel(); // At this point, ex.type should be a valid pokemon type randomly selected from TYPES
2. getType getter method
    [3]
This getter method that should simply return the PokePixel's type (a 1-line solution).
PokePixel ex = new PokePixel(); // randomly assigned to Water System.out.println(ex.getType()); // Water PokePixel ex2 = new PokePixel(); // randomly assigned to Fire System.out.println(ex2.getType()); // Fire
3. changeType setter method
[3]
To handle "updating" a PokePixel for each iteration of the game loop, we need to be able to change
    the type of each individual PokePixel. Note that a PokePixel simply represents
    a pixel rendered on the board, not an actual "Pokemon"; otherwise, it wouldn't make sense to
    update a Pokemon's type (Pokemon types can't change). We use this approach to avoid
    re-constructing new PokePixel instances each time the game loop iterates.
    
Implement this method to change the type field to the passed type (you do not need
    to enforce valid types, though you may if you'd like with an IllegalArgumentException). This is also a 1-line solution.
PokePixel originallyFire = new PokePixel();
// Randomly initialized to "Fire" type
System.out.println(originallyFire.getType()); // Fire
originallyFire.changeType("Grass");
System.out.println(originallyFire.getType()); // Grass
4. toString method
[5]
The string representation of a PokePixel should be the first three letters
    of its type (this is useful if you want to print out PokePixels!). 
    Use the substring(startIndex, endIndex)
String method (remember these are inclusive, exclusive respectively) to implement this as a 1-line solution, returning the first three characters.
PokePixel ex = new PokePixel(); // randomly assigned some type
ex.changeType("Electric");
System.out.println(ex.toString()); // Ele
System.out.println(ex); // Ele, just like __str__, we don't need to call toString() when printing
ex.changeType("Fire");
System.out.println(ex.toString()); // Fire
Part B: PokeLife.java
        The second class you will finish implementing represents a "board" of PokePixel instances
        with state corresponding to the current state of the game of life at some iteration of a main game loop
        (similar to the game loop used in the Spore lab).
    
        Once you've finished implementing this class, the provided Game.java will use it
        to initialize a new game, powered by a provided DrawingPanel.java class, which
        implements all of the graphics functionality (which you do not need to worry about understanding;
        graphics in Java isn't the most elegant).
    
        Just like in Part A, we recommend testing your PokeLife methods iteratively to
        make sure they behave as described, before running Game.java. Game.java
        just happens to be one example client that uses a PokeLife instance to
        run the game of life variant.
    
        In Part B, you will get practice working with basic arrays in Java, which are like lists in Python,
        only they are of fixed size and must contains items all having the same type. 
        For example, a String[] is the type for an array of Strings in Java.
        In Java, we append [] after the type to define an array of that type. Refer to
        Lecture 26 code/recording for more details.
    
        It then follows that String[][] is the the type for an array of an array of Strings (similar to a 2D list).
        Constructing arrays is a bit tedious, so we've given you the code for that. Your tasks will
        involve applying what you know about Python lists and indexing to access elements by
        index in 1D and 2D arrays. In PokeLife.java, you'll see 1D String[] arrays,
        2D String[][] arrays, and a 2D PokePixel array (the 2D grid of pixels being managed).
    
1. PokeLife Constructor
[10]
The goal of the PokePixel constructor is to initialize the "board"
    that the pokemon will be on. Since this requires the advanced two-dimensional
    array, we have provided you with an initializeBoard helper method, which returns a board
    for you (note that the provided method is declared private, since it is only
    intended to be used as a helper method within PokeLife method, not by clients).
    Internally, this helper method will also check whether or not the isRandom state was
    set in this constructor; if it was set to false, then the types will be assigned based
    on a simple heuristic function (which you don't need to worry about).
    If either width or height are <= 0, the constructor
    should throw an IllegalArgumentException with the message, width and height must be > 0.
    Otherwise, the PokeLife constructor should first set the width, height.
    Then, set the isRandom field to the passed isRandom
    argument so that we can test the game with a random vs. non-random allocation. 
    Finally, assign this.board to the result of calling the initializeBoard
    helper method, which returns a populated PokePixel[][] 
    based on the fields set (don't over-complicate this; you are only setting four fields on exactly four lines, 
    one assigned to the result of the helper method). For reference, our solution is 7 lines (4 of which are the field-setters).
PokeLife life = new PokeLife(4, 5, false); // board won't be randomly-assigned System.out.println(life.getWidth()); // see below for getWidth/getHeight System.out.println(life.getHeight()); // exLife.board should also be initialized, but we can't access it here since it's a private field without a getter). // Internally, the initializeBoard method you will call will populate width*height (5x4) // 2D grid of width*height PokePixels (for this example, that would be 20 pixels) PokeLife invalidWidth = new PokeLife(0, 5, false); // should raise IllegalArgumentException with message: width and height must be > 0 PokeLife invalidHeight = new PokeLife(4, 0, false); // should raise IllegalArgumentException with message: width and height must be > 0
2. getWidth and getHeight getter methods
[5]
These two getter methods should return the width and height of the current
    PokeLife board, respectively.
PokeLife life = new PokeLife(4, 5, false); System.out.println(life.getWidth()); // 4 System.out.println(life.getHeight()); // 5
3. getPoke getter method
[5]
    In this exercise, you will implement the getPoke method, which takes a x coordinate
    and y coordinate and returns the PokePixel at the corresponding
    grid cell (where the top-left corner of the grid is (0, 0), and x/y increase right/down, respectively).
    
        First check if the given x and y bounds are valid to avoid
        an undesirable ArrayIndexOutOfBoundsException (a client shouldn't
        see this internal exception, since its referring to private state and not their arguments).
        A valid x must be between 0 (inclusive) and the board's width (exclusive).
        A valid y must be between 0 (inclusive) and the board's height (exclusive).
        If either is invalid, throw an IllegalArgumentException with the message Invalid x and/or y coordinate..
        Otherwise, return the PokePixel at the given position, where (0, 0)
        is the first element in the first row (using 0-based indexing).
    
Note that a 2D array in Java works like a 2D list in Python. When we
    have a 2D array (or list), it's just an array of arrays. So suppose we have a small 2x3 grid.
    The next two examples show how to construct, access, and modify a 2D list/array in
    both Python and Java; you will not actually write any code to construct a 2D array in MP8,
    and while the examples below illustrate 2D arrays, the solution to getPoke is
    a short solution, first to handle the bounds and then to otherwise return the result of 2D array indexing of this.board.
$ python3 >>> rows = 2 >>> cols = 3 >>> grid = [] >>> # populate a 2D grid with 2 rows and 3 cols >>> # (in other words, a list of two nested 3-element lists) >>> for i in range(rows): ... row = [] ... for j in range(cols): ... row.append(0); ... grid.append(row) ... >>> grid [[0, 0, 0], [0, 0, 0]] >>> second_row = grid[1] # second row >>> second_row [0, 0, 0] >>> yth_cell = second_row[3] >>> yth_cell 0 >>> # update the 3rd cell in the 2nd row (0-based indexing) >>> # This is equivalent to referring to an update of the (1, 2) coordinate/position in getPoke >>> grid[1][2] = 1 >>> grid [[0, 0, 0], [0, 0, 1]] >>> len(grid) # grid is a list holding 2 lists 2 >>> len(grid[0]) # grid[0] is the first of the 2 nested list, having 3 elements 3 >>> second_row [0, 0, 1] >>> yth_cell = second_row[2] >>> yth_cell 1 >>> grid[1][2] = 2 >>> grid >>> second_row [0, 0, 2]
In Java, suppose we have a similar 2D array (we'll assume it's already constructed since you're not constructing any arrays in MP8)
int[][] grid = ...; // assume same contents as above // [[0, 0, 0], [0, 0, 0]] int[] secondRow = grid[1]; // [0, 0, 0] grid.length; // 2 (2 nested lists) grid[0].length; // 3 (3 items per nested list) secondRow.length; // 3 grid[-1]; // java.lang.ArrayIndexOutOfBoundsException raised; only 2 rows grid[2]; // java.lang.ArrayIndexOutOfBoundsException raised; only 2 rows grid[0][3]; // java.lang.ArrayIndexOutOfBoundsException raised; only 3 columns per row, not 4 grid[1][2]; // (1, 2) coordinate, or equivalently, second row, third item in that row (grid[row][col]) grid[1][2] = 1; // update the (1, 2) coordinate grid; // [[0, 0, 0], [0, 0, 1]] // secondRow is a reference to the second (mutable) array; that referenced array changes secondRow; // [0, 0, 1] int ythCell = secondRow[2]; // 1 grid[1][2] = 2; ythCell; // 1 (ythCell is just an int value, not a reference so its unchanged) secondRow; // [0, 0, 2] secondRow[2] != ythCell; // true, 2 != 1
    Here are some examples you can test with using a small non-random board associated with Game.java's testMode = 1 flag
    (as noted above and in the provided code comments, the provided initializeBoard
    will guarantee the same allocation of types for a given width, height when
    isRandom is passed to false, so the following statements should match): 
PokeLife life = new PokeLife(4, 5, false); // board won't be randomly-assigned System.out.println(life); // uses the toString() method, printing out the following Fir Gro Gro Wat Fir Fir Fir Wat Wat Gra Ele Gra Gra Fir Fir Wat Ele Gro Fir Gro // top left is (0, 0) // x increases to the right, y increases down // bottom right is (3, 4) PokePixel px00 = life.getPoke(0, 0); // (0, 0) top left, "Fir" PokePixel px01 = life.getPoke(0, 1); // (0, 1) pixel below px00, "Fir" PokePixel px02 = life.getPoke(0, 2); // (0, 2) pixel below px01, "Wat" PokePixel px10 = life.getPoke(1, 0); // (1, 0) pixel right of px00, "Gro" PokePixel px34 = life.getPoke(3, 4); // (3, 4) bottom right, "Gro" System.out.println(px00.getType()); // Fire at (0, 0) System.out.println(px02.getType()); // Water at (0, 2) System.out.println(px10.getType()); // Ground at (1, 0) // other corner cases for this board PokePixel px30 = life.getPoke(3, 0); // top right, "Wat" PokePixel px04 = life.getPoke(0, 4); // bottom left, "Ele" System.out.println(px30.getType()); // Water System.out.println(px04.getType()); // Electric // All of the following should throw an IllegalArgumentException // with the message: Invalid x and/or y coordinate. life.getPoke(-1, 0); // invalid negative x case life.getPoke(0, -1); // invalid negative y case life.getPoke(-1, -1); // invalid negative x and y case life.getPoke(4, 0); // invalid x upper-bounds case (4 cols, 0-based indexing) life.getPoke(5, 0); // one more test life.getPoke(0, 5); // invalid y upper-bounds case (5 rows) life.getPoke(4, 5); // invalid x and y upper-bounds case
4. getWeaknesses method
[10]
        This method will get you more practice with basic array processing in Java.
        The two class constants, TYPES and WEAKNESSES
        hold the types and their weaknesses (a type with multiple weaknesses has a weakness String
        separated with ,). In Python, the appropriate way to map types to
        their weaknesses would be a dictionary. However, this is less straightforward in Java
        (if you're curious, the data structure for this in Java is called a Map).
    
        For the scope of a small Java project with simple analogs in Python, we have just
        chosen to represent the 1-1 mapping of types and weaknesses with these two arrays,
        and each type/weakness(es) pairing is determined by a shared index in both arrays.
        So the type String TYPES[0] is defined as having a weakness(es)
        of WEAKNESSES[0], and so on.
    
        Finish this method, which takes a String type as a single argument,
        to loop through the TYPES array until the matching String is found
        (use the String's equals method to find the match; don't forget that == doesn't
        correctly check for String equality in Java!) Once your loop finds the index of the matching type, use that index to
        get the corresponding weakness string. You can refer to lecture materials on String equality and the provided
        getTypeIndex method for an example of the .equals method for Strings.
    
Since we support types which might have
        more than one weakness, we want to return an array of all of the weaknesses (in our version,
        there happen to be 1 or 2 weaknesses depending on the type).
        We have chosen to use "," to separate weaknesses in the same String (e.g. "Electric,Grass" for
        a single String that represents these two weaknesses for one type). Just like in Python, we can split 
        a String in Java; the String's split method should be used here,
        which returns an array of Strings (String[]) split by a passed delimiter (in this case, ",").
        Return that split string array result for the matching index. If the given type isn't found,
        the method should return null.
    
For reference, our solution is about 6 lines ignoring inline comments.
// Assume a PokeLife variable called `life` is defined with the following PokePixel board:
Fir Fir Wat Gra 
Ele Gro Wat Gro 
Ele Ele Gro Gra 
Gro Fir Gro Gra 
Gra Gra Ele Ele
System.out.println(TYPES[0]);      // Fire
System.out.println(WEAKNESSES[0]); //  Water,Ground
String[] weaknesses = WEAKNESSES[0].split(",");
// ["Water", "Ground"]
String[] fireWeaknesses = getWeaknesses("Fire");
// ["Water", "Ground"]
System.out.println(TYPES[2]);      // Grass
System.out.println(WEAKNESSES[2]); // Fire
String[] weaknesses = WEAKNESSES[2].split(",");
// ["Fire"] (if a split String does not have ",", it's just a 1-element String[])
String[] grassWeaknesses = getWeaknesses("Grass");
// ["Fire"]
5. isSurroundedBy method
    [10]
            Next, you'll implement the method which determines whether a PokePixel
            is "overrun" by some minimum number of neighbors sharing one of its weaknesses.
        
We have provided you with a getNeighborTypes(int x, int y)
        method, which returns a String[] of the types of the 8
        neighbors of the PokePixel at (x, y).
The isSurroundedBy method takes an x and y position, a String type,
            and a target int count, and should return whether
        there are at least count neighbors of the given type, to the PokePixel at
        (x, y).
Finish this method to use the neighborTypes array we've started you with to
        loop through the array and count how many Strings equal the passed type String.
        
            The method should return true if at least count neighbors have the
            passed type, else false. For reference, our solution is about 6-7 lines.
            You do not need to handle the arguments being valid, though you may choose to
            if you'd like to improve the error-handling for this method (in this case, an IllegalArgumentException
            would make the most sense if x or y are out of bounds or count is < 1).
        
            In updateType, you will pass 3 as the count argument to this method,
            but we have parameterized this method to support other counts to allow for 
            possible extensions of the pixel-changing logic (you should not hard-code 3 anywhere in this isSurroundedBy method).
        
// Assume a PokeLife variable called `life` is defined with the following 5x4 PokePixel board: Fir Gro Gro Wat Fir Fir Fir Wat Wat Gra Ele Gra Gra Fir Fir Wat Ele Gro Fir Gro // The board's (1, 1) pixel is "Fir", surrounded by 3 "Fir", 2 "Gro", 1 "Wat", 1 "Gra" and 1 "Ele" System.out.println(life.isSurroundedBy(1, 1, "Fire", 1)); // true System.out.println(life.isSurroundedBy(1, 1, "Fire", 3)); // true System.out.println(life.isSurroundedBy(1, 1, "Fire", 4)); // false System.out.println(life.isSurroundedBy(1, 1, "Water", 1)); // true System.out.println(life.isSurroundedBy(1, 1, "Grass", 2)); // false System.out.println(life.isSurroundedBy(1, 1, "Ground", 2)); // true System.out.println(life.isSurroundedBy(1, 1, "Ground", 3)); // false // The board's (0, 2) pixel is the left-most "Wat", surrounded by 3 "Fir" and 2 "Gra" System.out.println(life.isSurroundedBy(0, 2, "Fire", 1)); // true System.out.println(life.isSurroundedBy(0, 2, "Fire", 2)); // true System.out.println(life.isSurroundedBy(0, 2, "Fire", 3)); // true System.out.println(life.isSurroundedBy(0, 2, "Fire", 4)); // false System.out.println(life.isSurroundedBy(0, 2, "Water", 1)); // false System.out.println(life.isSurroundedBy(0, 2, "Grass", 2)); // true System.out.println(life.isSurroundedBy(0, 2, "Grass", 3)); // false
6. updateType method
[8]
This method is responsible for checking and updating a given PokePixel.
    It should look through the PokePixel's weaknesses (use your getWeaknesses method,
    which will return a String[] holding a String for each weakness), 
    and then check to see if at least 3 neighbors of that type "beat" the PokePixel. If
    If they do, then the PokePixel's
    type should be updated to that weakness String, using its changeType method you implemented in Part A.
    Note that if a pixel has more than 1 weakness, such as "Water" being weak to both "Grass" and "Electric",
    then you'll want to consider each weakness separately (use a loop over the weaknesses array
    returned by your getWeaknesses). The last weakness, if any, that matches 
    3 or more neighbors should be the updated type (for a type with two weaknesses,
    if 3 neighbors are of one weakness and 3 are of the other, the second weakness in the tie
    should be chosen to change the type). Also note that this method is declared public, though
    in practice, it would best be a private helper method. This is only to
    make it easier for testing the method in any testing programs. 
// Assume a PokeLife variable called `life` is defined with the following 5x4 PokePixel board: // (Cycle 0 of small non-random test) Fir Ele Ele Gro Gra Fir Gro Ele Fir Gra Wat Wat Gro Gro Ele Gra Gro Gra Gra Ele PokePixel px = life.getPoke(2, 3); // "Ele", surrounded by 4 "Gro" life.updateType(2, 3, px); System.out.println(px.getType()); // Ground // Another example PokePixel board Gro Fir Wat Gra Gro Fir Wat Gro Gro Gra Wat Gra Gra Gra Gra Gra Gra Gra Ele Ele PokePixel px = life.getPoke(1, 1); // "Fir" which is weak to "Wat" and "Gro" System.out.println(px.getType()); // Fire // (1, 1) is surrounded by 3 "Wat" and 3 "Gro" // "Gro" occurs later in WEAKNESSES so is the tie-breaker life.updateType(1, 1, px); System.out.println(px.getType()); // Ground
7. getWinningType method
    [10]
At this point, you have everything implemented for the Game.java program to use your 
            PokeLife class (which uses your PokePixel class)
            to run a game of life simulation! In Game.java, you'll notice a variable 
            called lifetime, which defines the number of times the game loop iterates
            (each iteration updates the board using the provided PokeLife lifeCycle
            method).
        
            Currently, when the simulation ends (the game loop has iterated lifetime times),
            the window will show the final result of the simulation. Of course, we'd like to know 
            which type was considered the winner!
        
            Finish PokeLife's getWinningType method to 
            tally up the counts for each of the types in TYPES. We have started the 
            method for you, which includes a nested loop over the 2D board. We have also provided a getTypeIndex
            which takes an String type and returns the int index of that type in 
            the TYPES array. There are 2 TODOs in the method, which you should
            replace as described. For reference, our solution adds no more than 8 lines total when replacing the two TODOs.
            You should only have 3 loops total, two of which are the nested loop we start you with.
        
An example of using this method is provided below, where life is the 
        current state of the PokePixel board.
// Assume a PokeLife variable called `life` is defined with the following 3x3 PokePixel board:
//    Fir Wat Ele
//    Ele Gra Fir
//    Fir Gro Wat
String winningType = life.getWinningType();
// tallies should be populated as follows:
// TYPES ==   {"Fire", "Water", "Grass", "Electric", "Ground"}
// tallies == {3, 2, 1, 2, 1}
System.out.println("Winning type: " + winningType);
// Winning type: Fire
// Example of updating the board to show ties
PokePixel px = life.getPoke(2, 1);  // "Fir"
PokePixel px2 = life.getPoke(0, 0); // "Fir"
px.changeType("Grass");
px2.changeType("Ground");
//    Gro Wat Ele
//    Ele Gra Gra
//    Fir Gro Wat
// tallies == {1, 2, 2, 2, 2}
winningType = life.getWinningType();
System.out.println("Winning type: " + winningType);
// Winning type: Ground
// "Ground" is the type at tallies[4] and is the last occurrence of the 4-way tie
    For the small non-random test mode (2), we see that "Grass" is the winning type
    (Game.java calls your getWinningType to print the results
    after the last game cycle):
... Cycle: 3 Gro Gra Gra Gra Gra Gro Gra Gra Gra Gra Wat Gra Gra Gra Ele Gra Gra Gra Gra Ele Winning type: Grass
Part C: (Optional) Extensions
        If you've finished, great work! We are offering students a chance to implement optional
        features in this assignment, which can earn up to 2 additional points (max 30/30)
        depending on the extent you implement. If you choose to implement optional features,
        submit your additions as additional files suffixed by 2 (e.g. PokeLife2.java,
        PokePixel2.java, and Game2.java. For optional extensions, you must
        at minimum submit a Game2.java which uses a PokeLife2.java
        instance (which may be a copy of PokeLife.java changing all references of PokePixel.java to PokePixel2.java,
        or which modifies/adds features compatible with your Part A PokeLife.java, in 
        which case you don't need to submit another PokeLife2.java)
    
If you choose to implement an extension, submit a partc.txt file with a brief
    summary of your extension, what we should look for when running it, and your design/implementation
    strategies (we will use this when determining the number of Part C extra points).
Here are some ideas:
- Factor out types and weaknesses with a Type.javaclass, which is an alternative way of working with types instead of theTYPESandWEAKNESSESarrays. ThisType.javacould be used to quickly accesstoString(e.g. "Fire"),getWeaknesses, etc. If you choose to do this extension, note thatPokePixel2.javaandPokeLife2.javawould need to be slightly adjusted to work withTypeinstances instead of the two arrays. For an additional challenge, you could use subclasses to implement different types (e.g.FireType).
- 
            Implement PokeLife2.javawhich is a copy of Part B, but has a different heuristic inupdateTypeand/orisSurroundedByto determine whether a pixel is "beaten" by its neighbor types.
- Add other types/weaknesses to the game, extending the 5-type collection we gave you.
- 
            Implement "critical hits" or "misses" with randomness, such that a cell is unchanged 
            if is considered a miss, or all of its neighbors are updated to have its type if it's a critical hit
            (note that this will involve some logical refactoring, since updateTypechanges the type of a cell based on its neighbors instead of neighbors having their types updated based on the current cell in the providedPokeLife'slifeCyclemethod; you are welcome to adjustlifeCycleto update neighbors instead of a cell, which will be good practice to work with 2D arrays to get neighbors ofcurr).
- 
            Implement a feature to support "shiny" PokePixels; in Pokemon, a shiny Pokemon is a rare find with a different color but still has the same type as its non-shiny counterpart. You can use aShinyPokePixelsubclass here or handle it inPokePixel2.javaorPokeLife2.java.
- Extend update rules to handle neighbors differently for different types and/or positions, such as whether 4 adjacent Fire pixels form a 2x2 square, and perform a cell "explosion", updating the surrounding areas (this is also excellent practice to think about edge-cases in nested loops!).
Example Runthrough: Game.java
        Now that you've completed everything, you're ready to see the results in action!
        To run the game, make sure everything is up-to-date by compiling (the $
        just represents the commands ran in the terminal; as usual, don't actually type it; some students 
        may instead see % in the terminal):
    
$ javac PokePixel.java $ javac PokeLife.java $ javac DrawingPanel.java $ javac Game.java // can alternatively compile everything in the current directory: $ javac *.java $ java Game // run the game!
    You should see an animated game of life pop up, which will run for 4 iterations (the lifetime we happened 
    to set in Game.java to get you started with testing a small non-random board). You can
    now update the Game.java testMode to 0 to test for a full random game. After 35 cycles 
    complete in the game loop, you'll see your
    getWinningType used to print the results, e.g.:
Winning Type: Fire
Refer back to the top of this spec for screenshots, and note you shouldn't actually write any
    additional System.out.println statements (Game.java does everything for you).