13 Feb 2012 [ 201 week6 ]

This week we'll go over more examples of class design, with a focus on expanding our view of what we can represent with a class.

Up to now, we've seen examples of modeling real life (more or less) objects in code, like Robot or Human or Timer. The real power of object-oriented programming (and some of the biggest conceptual differences from the more straightforward procedural paradigm) comes in modeling more abstract concepts like rules, strategies, and procedures.

Helpful organizational tools: packages and enums

A couple of new details we'll start to use in class:

Packages are the way to organize groups of classes in Java. Package structure reflects the file system hierarchy: package x.y.z lies in folder x/y/z on disk. You can use a package declaration statement at the top of each source file, like package x.y.z;.

Enums provide a way to organize a group of constant values in a convenient way. In the tic-tac-toe example we'll use, the possible entries on the game board are either X, O, or EMPTY. We could just pick numbers or char data to represent each type, but Java provides a shortcut for defining groups of such related constants. In the simplest case, we can just say:

public enum Piece {
  EMPTY, X, O
}

Then we can use Piece.X, Piece.O, and Piece.EMPTY to represent the various entries. In addition to convenience, we are given an extra mesasure of type safety: if we had represented the pieces using a char, we would have to worry about handling errors due to the wrong letter being used. But with an enum, the three listed values are the only ones possible anywhere in our program.

Modeling procedures as objects

Here's a simple example that filters standard input for numbers in a desired range. How would you use this class to build a more complicated filter that looks for numbers in any one of several ranges? For example, checking for numbers that lie in [0, 1], [2, 3], or [4, 5].

import java.util.Scanner;

public class RangeFilter {
    private double lower;
    private double upper;
    
    public RangeFilter(double min, double max) {
        lower = min;
        upper = max;
    }
    
    public boolean contains(double x) {
        return x >= lower && x <= upper;
    }
    
    // Filter standard input for numbers in the range [0, 10].
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        RangeFilter range = new RangeFilter(0, 10);
        while (input.hasNextDouble()) {
            double x = input.nextDouble();
            if (range.contains(x))
                System.out.println(x);
        }
    }
}

Modeling rules as objects

Look in the examples directory for a tic-tac-toe-like project. We'll fill in definitions for methods used to model the rules of the game, starting from the template here:

package org.ilzd.tictacx;

/** Represent rules for a tic-tac-toe style game, 
 *  and track status of game instance with respect
 *  to the rules.
 * 
 */
public class Rules {
    private Board board;
    /** Track current player. */
    private Piece player = Piece.X;
    /** Track winner (when game is over) */
    private Piece winner = null;
    /** Number in-a-row required for win */
    private int goal = 3;
    
    /** Construct rules object to govern given board.
     *
     * @param b board to track
     */
    public Rules(Board b) {
        board = b;
    }
    
    // Override default goal of 3.
    public Rules(Board b, int goal) {
        this.board = b;
        this.goal = goal;
    }
    
    /** Determine if game is over.
     * 
     * @return true if game is over
     */
    public boolean gameOver() {
        // Check all possible starting points
        for (int r = 0; r < board.getRows(); r++) {
            for (int c = 0; c < board.getCols(); c++) {
                // Check all possible directions
                if (check(r, c, 1, 0) // Columns
                    || check(r, c, 0, 1) // Rows
                    || check(r, c, 1, 1) // Diag down-right
                    || check(r, c, 1, -1) // Diag down-left
                    ) {
                    // Track winner
                    winner = board.get(r, c);
                    return true;
                }
            }
        }
        return false;
    }

    /** Check starting at position (r, c) and moving in direction
     *  (deltaR, deltaC), check to see if all squares are occupied
     *  by same player.
     */
    private boolean check(int r, int c, int deltaR, int deltaC) {
        // Determine piece to check for
        Piece player = board.get(r, c);
        if (player == Piece.EMPTY)
            return false;
        // Count of pieces in-a-row
        int count = 1;
        // Current location to check
        int r1 = r + deltaR;
        int c1 = c + deltaC;
        while (count < goal) {
            // Check for going off board
            if (r1 < 0 || r1 >= board.getRows())
                return false;
            if (c1 < 0 || c1 >= board.getCols())
                return false;
            // Check for not hitting desired piece type
            if (board.get(r1, c1) != player)
                return false;
            // If we make it past checks, we have another hit
            count++;
            // And go on to next square
            r1 += deltaR;
            c1 += deltaC;
        }
        // If we exit the loop, we've hit the goal
        return true;
    }
    

    /** Determine if given move is legal.
     * 
     * @param r row location
     * @param c column location
     * @return true if move is legal
     */
    public boolean legal(int r, int c) {
        // Check bounds.
        if (r < 0 || r >= board.getRows())
            return false;
        if (c < 0 || c >= board.getCols())
            return false;
        
        // Check earlier plays.
        if (board.get(r, c) != Piece.EMPTY)
            return false;
        
        // Check if square below exists and is empty
        if (r + 1 < board.getRows() && board.get(r + 1, c) == Piece.EMPTY)
            return false;
        
        return true;
    }
    
    /** Determine active player.
     * 
     * @return player who can take the next turn
     */
    public Piece getPlayer() {
        // Fill in logic to track current player.
        return player;
    }

    /** Return winning player.
     *
     * @return winning player, or null if game is not over
     */
    public Piece getWinner() {
        return winner;
    }
    
    
    /** Play a piece at given location.
     * 
     * @param row row location
     * @param col column location
     * @return true if play was allowed.
     */
    public boolean play(int row, int col) {
        if (!legal(row, col))
            return false;
        board.play(getPlayer(), row, col);
        nextPlayer();
        return true;
    }
    
    private void nextPlayer() {
        if (player == Piece.X) 
            player = Piece.O;
        else
            player = Piece.X;
    }
}