Tuesday, December 28, 2010

Even-cleaner Checkerboard code

After I finished the checkerboard code, it really pained me to reuse the code for the inner loop that checked whether a square is an "every other" square (ie if ((i+j) % 2 != 0) ). We have to use this both for when determining if a square should be filled, and if a checker belongs in the spot.

I got a couple of suggestions to avoid repeating code. Bill simply suggested storing the boolean in a local variable, which makes a lot of sense; I'm still getting the hang of how booleans work in structure, so it was good to know that I could store the logic that way.

Usman suggested initiating the "checker" variable outside the loop that used the boolean and set the value of "checker" to null at first. Then within the loop, I could change the value of "checker" to be a new object and set it to a proper position and color. At the end of the loop, if the value of "checker" has been changed form "null" to a new object, plop that sucker down. This lets me avoid the problem of plopping the square down after the checker (and covering the checker up.)

I rewrote the code according to Usman's suggestion, and it worked! Yippee! I hadn't known what null was, but it seems to be handy. It sounds like "null" is just a placeholder that is totally neutral, so Java won't object if we set a GOval variable object equal to null at first, and we can check whether the variable is or isn't "null" after the loop.

However, Usman mentioned that it's not good to use "null" a whole lot. I wonder why....

/*


/*
* File: CheckerboardWithoutRepeatedCode.java
* Name: Chu
* Section Leader: Usman. He rules
* Description: This program draws a checkerboard graphic and checkers on it in the original starting position.
* To draw the board, we use the GRect class, with the "x" and "y" coordinates set by the index of the loop
* as well as the square size (determined by private constants).  The checkers use the GOval class, and since they 
* are right on top of the squares, they can use the same "x" and "y" coordinates. To determine if the squares
* should be filled in, we use the boolean ((i+j) % 2 != 0), which resolves to true every other square by
* row and column. We reuse that test for determining whether a checker should be set down.
* 
* I used Usman's suggestion of initiating a checker outside the inner loop with the boolean and setting it 
* equal to null at first, and if the inner loop determines that there should be a checker, we'll change the
* "checker" variable from "null" to a new GOval object. We can check at the end of the loop whether "checker"
* is null or not, and if not, then plot it on the board in the right place.
* ------------------
*/
 
package Ch4Practice;

import acm.graphics.*;
import acm.program.*;
import java.awt.*;

public class CheckerboardWithoutRepeatedCode extends GraphicsProgram {
 
    //private constants
    private static final int N_ROWS = 8;
    private static final int N_COLUMNS = 8; 

    public void run() {
        double sqSize = (double) getHeight() / N_ROWS; 
        double boardWidth = N_COLUMNS * sqSize; 

        for (int i = 0; i < N_ROWS; i++) { //for the outer rows
            for (int j = 0; j < N_COLUMNS; j++) { //for the inner rows
                double x = (j * sqSize) + (getWidth()/2 - boardWidth/2);
                double y = i* sqSize;
   
                //Starting off the checkerboard:
                GRect sq = new GRect(x, y, sqSize, sqSize); //Make up a new square with the proper coordinates
   
                //Initiate the variable checker at null.
                GOval checker = null;    
    
                if ((i+j) % 2 != 0) { //If it's every other square, both by row and column
                    sq.setFilled(true); //Make it filled
          
                    //Now for the checkers:
                    checker = new GOval (x, y, sqSize, sqSize); //Making the checker NOT null, if it appropriate
                    checker.setFilled(true);
     
                    if ((i < 3)) {//If it's every other AND the first three rows, make red checkers 
                        checker.setFillColor(Color.RED);
      
                    }
                    else if (N_ROWS-4 < i) { //If it's every other AND the last 3 rows, make white checkers
                        checker.setFillColor(Color.WHITE); 
      
                    } 
  
                }
    
                add(sq); //Plop that square down
                if (checker != null) {
                    add(checker); //Plop that checker down-- BAM!
                }
            }
 
        }
    }
 
}

2 comments:

  1. Renee, I'm really impressed, first of all.

    About the null thing, imagine you worked with many objects initialized to null, and you wanted to invoke a method on an object later on in the code, but you would need to check if it was null first or risk get a null reference error. It's easier to make sure to initialize an object via its constructor, then you can invoke methods without having to check if it's null.

    One consideration you could look into is storing the board state in an array, which would simplify the logic in your inner for loop:

    board = [
    [null, red, ...],
    ...,
    [null, white, ...],
    ]

    Then your inner loop can check if board[i, j] has something, and if it does, plop it in. The tradeoff is that if you wanted to change the dimensions of the board, you'd have to modify the array instead of just changing N_ROWS/N_COLUMNS.

    Have a look at the difference between declarative and imperative code styles: http://stackoverflow.com/questions/1784664/what-is-the-difference-between-declarative-and-imperative-programming.

    ReplyDelete
  2. I think I get what you mean WRT null...The way Usman put it, it's unwise to use null a whole lot because when we do, we're building implicit meaning into "null", in this case, null means the checker is NOT on playable square. Going on with what you say, if we suddenly want to make all the checkers a bit smaller (which I probably should for aesthetics sake), I'd have to first specify make all the NON-NULL checkers smaller or else get a bug.

    ReplyDelete