Word Search

/**
 * See https://en.wikipedia.org/wiki/Word_search
 * 
 * Enhance the program shown below.
 * 
 * 1. Create a class called Snake that implements WordLocations.
 *    A Snake object should represent the location of a word that
 *    may snake around in a word search grid.
 * 
 * 2. Create a class called SerpentineWordSearcher that extends
 *    LinearWordSearcher and has a method that overrides search.
 * 
 * 3. Expand the main method of the program shown below so that it
 *    searches the same randomly generated word search grid using both
 *    LinearWordSearcher and SerpentineWordSearcher objects. Enhance
 *    the output of the program accordingly.
 * 
 * 4. Add the following methods to the WordLocation interface:
 * 
 *        int wordLength();
 *        Cell getCell(final int i);
 * 
 *    Update Segment and Snake.
 * 
 * 5. Add the following method to AbstractWordSearcher:
 * 
 *        String showWords(final List<WordLocation> locations);
 * 
 *    The method should return a string corresponding to the word
 *    search grid associated with the searcher; the letters of the
 *    words identified by parameter locations should somehow be
 *    highlighted such that they are easy to spot. For example, the
 *    other letters could be converted to lowercase or they could be
 *    replace by a single character, such as a period. Update the
 *    main method of the program so that it somehow uses showWords.
 */
 
import java.util.*;

final class Cell {
    public final int row;
    public final int col;
    public Cell(final int row, final int col) {
        this.row = row;
        this.col = col;
    }
    public String toString() {
        return "(" + row + "," + col + ")";
    }
}

interface WordLocation {
}

final class Segment implements WordLocation {
    public final Cell first;
    public final Cell last;
    public Segment(final Cell first, final Cell last) {
        this.first = first;
        this.last = last;
    }
    public String toString() {
        return "[" + first + "," + last + "]";
    }
}

abstract class CharacterGridBuilder {
    public final char[][] getGrid(final int numRows, final int numCols) {
        final char[][] grid = new char[numRows][numCols];
        final int n = numRows * numCols;
        for (int i = 0; i < numRows; i = i + 1) {
            for (int j = 0; j < numCols; j = j + 1) {
                grid[i][j] = getCharacter();
            }
        }
        return grid;
    }
    public abstract char getCharacter();
}

final class RandUpperGridBuilder extends CharacterGridBuilder {
    public char getCharacter() {
        return (char)('A' + (int)(Math.random() * 26));
    }
}

final class ConstCharGridBuilder extends CharacterGridBuilder {
    private final char constChar;
    public ConstCharGridBuilder(final char constChar) {
        this.constChar = constChar;
    }
    public char getCharacter() {
        return constChar;
    }
}

final class WordSearchBuilder {
    private final RandUpperGridBuilder randUpperBuilder = new RandUpperGridBuilder();
    private final ConstCharGridBuilder constCharBuilder = new ConstCharGridBuilder('.');
    private final int numRows, numCols;
    public WordSearchBuilder(final int numRows, final int numCols) {
        this.numRows = numRows;
        this.numCols = numCols;
    }
    public char[][] getRandomGrid() {
        return randUpperBuilder.getGrid(numRows, numCols);
    }
    public char[][] getEmptyGrid(final int numRows, final int numCols) {
        return constCharBuilder.getGrid(numRows, numCols);
    }
}

interface WordSearcher {
    List<WordLocation> occurrencesOf(final String word);
}

abstract class AbstractWordSearcher implements WordSearcher {
    protected final char[][] grid;
    protected final int numRows, numCols;
    protected AbstractWordSearcher(final char[][] grid) {
        this.grid = grid;
        this.numRows = grid.length;
        this.numCols = grid[0].length;
    }
    public final String toString() {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < numRows; i = i + 1) {
            for (int j = 0; j < numCols; j = j + 1) {
                sb.append(grid[i][j]);
            }
            sb.append('\n');
        }
        return sb.toString();
    }
}

final class LinearWordSearcher extends AbstractWordSearcher {
    public LinearWordSearcher(final char[][] grid) {
        super(grid);
    }
    public List<WordLocation> occurrencesOf(final String word) {
        List<WordLocation> locations = new LinkedList<WordLocation>();
        for (int i = 0; i < numRows; i++) {
            for (int j = 0; j < numCols; j++) {
                search(locations, word, i, j);
            }
        }
        return locations;
    }
    protected void search(final List<WordLocation> locations,
        String word, final int startRow, final int startCol
    ) {
        final int n = word.length();

        // The search is simple if the length of word is 1;
        if (n == 1) {
            if (word.charAt(0) == grid[startRow][startCol]) {
                final Cell startEnd = new Cell(startRow, startCol);
                locations.add(new Segment(startEnd, startEnd));
            }
            return;
        }

        // otherwise,
        final List<Cell> terminals = new LinkedList<Cell>();
        int i, r, c, lastRow=-1, lastCol=-1;

        // search down;
        for (i=0, r=startRow, c=startCol; i < n; i++, r++) {
            if (r >= numRows || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // search up;
        for (i=0, r=startRow, c=startCol; i < n; i++, r--) {
            if (r < 0 || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // search right;
        for (i=0, r=startRow, c=startCol; i < n; i++, c++) {
            if (c >= numCols || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));

        // search left;
        for (i=0, r=startRow, c=startCol; i < n; i++, c--) {
            if (c < 0 || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // search down and right;
        for (i=0, r=startRow, c=startCol; i < n; i++, r++, c++) {
            if (r >= numRows || c >= numCols || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // search up and right;
        for (i=0, r=startRow, c=startCol; i < n; i++, r--, c++) {
            if (r < 0 || c >= numCols || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // search down and left;
        for (i=0, r=startRow, c=startCol; i < n; i++, r++, c--) {
            if (r >= numRows || c < 0 || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // search up and left;
        for (i=0, r=startRow, c=startCol; i < n; i++, r--, c--) {
            if (r < 0 || c < 0 || word.charAt(i) != grid[r][c]) break;
            lastRow = r;
            lastCol = c;
        }
        if (i == n) terminals.add(new Cell(lastRow, lastCol));
            
        // then compile a list of locations where the word was found.
        if (terminals.size() > 0) {
            final Cell begin = new Cell(startRow, startCol);
            for (Cell end : terminals) {
                locations.add(new Segment(begin, end));
            }
        }
    }
}

public class WordSearch {
    public static void main(final String[] args) {

        final int maxPrintable = 10000;
        final int numArgs = args == null ? 0 : args.length;        
        if (numArgs < 2) {
            System.out.println("Syntax: row_count column_count optional_word_list");
            System.out.print("Example: ");
            System.out.println("100 100 A QUICK BROWN FOX JUMPED OVER THE LAZY DOG");
            System.out.println();
            System.out.print("Prints the number of times each word in ");
            System.out.println("optional_word_list appears in a grid.");
            System.out.print("Prints the grid if column_count times row_count ");
            System.out.println("is not greater than " + maxPrintable + ". ");
            return;
        }
        
        final int numRows = Integer.parseInt(args[0]);
        final int numCols = Integer.parseInt(args[1]);
        final WordSearchBuilder wsb = new WordSearchBuilder(numRows, numCols);
        final char[][] grid = wsb.getRandomGrid();
        final List<String> words = new LinkedList<String>();
        
        for (int i = 2; i < numArgs; i = i + 1) {
            words.add(args[i]);
        }
        final WordSearcher ws = new LinearWordSearcher(grid);
        System.out.println("Word Search\n");
        for (String word : words) {
            System.out.print(word + ": ");
            System.out.println(ws.occurrencesOf(word).size());
        }
        if (numRows * numCols <= maxPrintable) {
            System.out.println("\n" + ws);
        }
    }
}