// A class that represents Java Canvas's that draw maps of fractal landmasses
// on themselves. Note that I called this class "IslandCanvas" because the
// landmasses often, but not always, look like islands.

// History:
//
//   January 2007 -- Created by Doug Baldwin as a prototype for a CSci 240
//     exercise.




import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Color;
import java.util.Random;




class IslandCanvas extends Canvas {
    
    
    
    
    // Internally, island canvases store an array of elevations, and the size
    // of each side of this array. They also store a random number generator for
    // computing elevations:
    
    private double[][] elevations;
    private int size;
    
    private Random generator;
    
    
    
    
    // The constructor. This takes the size (i.e., height and width) of the
    // map, in pixels, as its parameter. This creates a size-by-size array
    // to hold the map's elevations, and stores the size for future reference.
    // It also creates the random number generator that will be used to
    // randomize elevations. Finally, it seeds the elevation array with
    // below-sea-level elevations at the corners, and calls an auxiliary
    // method to fill in the remaining elevations.
    
    public IslandCanvas( int size ) {

        this.size = size;
        this.elevations = new double[size][size];
        
        generator = new Random();
        
        elevations[0][0] = elevations[0][size-1]
                         = elevations[size-1][0]
                         = elevations[size-1][size-1]
                         = -25.0;
        this.calculateElevations( 0, 0, size-1, size-1 );
    }
    
    
    
    
    // The "paint" method that all Canvas subclasses must provide. This simply
    // steps through the array of elevations, deducing the color for pixel (i,j)
    // in the window from the elevation of cell (i,j). Pixels are colored according
    // to their elevations.
    
    public void paint( Graphics g ) {
        
        
        // Elevations at which map colors change, and the colors associated
        // with them:
        
        final double seaLevel = 0.0;
        final Color seaColor = new Color( (float)0.35, (float)0.35, (float)1.0 );
        
        final double forestLevel = 25.0;
        final Color forestColor = new Color( (float)0.0, (float)0.6, (float)0.0 );
        
        final double plainsLevel = 60.0;
        final Color plainsColor = new Color( (float)0.75, (float)0.55, (float)0.25 );
        
        final Color peakColor = new Color( (float)1.0, (float)1.0, (float)1.0 );
        
        
        // Fill in colors for each pixel according to its elevation:
        
        for ( int i = 0; i < size; i++ ) {
            for ( int j = 0; j < size; j++ ) {
                
                if ( elevations[i][j] < seaLevel ) {
                    g.setColor( seaColor );
                }
                else if ( elevations[i][j] < forestLevel ) {
                    g.setColor( forestColor );
                }
                else if ( elevations[i][j] < plainsLevel ) {
                    g.setColor( plainsColor );
                }
                else {
                    g.setColor( peakColor );
                }
                
                g.fillRect( j, i, 1, 1 );
            }
        }
    }
    
    
    
    
    // The auxiliary method that calculates elevations for the "map." The parameters
    // to this method are the indices in the elevations array of the west (left),
    // north (top), east (right) and south (bottom) sides of the region to calculate
    // elevations for. This algorithm uses a recursive subdivision approach to assign
    // heights to the center of the region and the centers of its sides, and then
    // use those heights to recursively calculate heights within each quadrant.
    //
    // Precondition: The region is square, i.e., east-west = south-north.
    
    private void calculateElevations( int west, int north, int east, int south ) {
        
        final double scaleFactor = 0.25;            // Scale factor for converting region sizes to
                                                    // random offsets
        
        // There is nothing to do unless the region is more than 1 pixel high and wide.
        
        double regionSize = south - north;
        
        if ( regionSize > 1.0 ) {
            
            double diagonal = 1.414 * regionSize;
            
            int nsCenter = ( north + south ) / 2;
            int ewCenter = ( east + west ) / 2;
            
            elevations[nsCenter][west] =   ( elevations[north][west] + elevations[south][west] ) / 2.0
                                         + generator.nextGaussian() * regionSize * scaleFactor;
            
            elevations[nsCenter][east] =   ( elevations[north][east] + elevations[south][east] ) / 2.0
                                         + generator.nextGaussian() * regionSize * scaleFactor;
            
            elevations[north][ewCenter] =   ( elevations[north][east] + elevations[north][west] ) / 2.0
                                          + generator.nextGaussian() * regionSize * scaleFactor;
            
            elevations[south][ewCenter] =   ( elevations[south][east] + elevations[south][west] ) / 2.0
                                          + generator.nextGaussian() * regionSize * scaleFactor;
            
            elevations[nsCenter][ewCenter] =   (   elevations[north][west]
                                                 + elevations[north][east]
                                                 + elevations[south][west]
                                                 + elevations[south][east] ) / 4.0
                                             + generator.nextGaussian() * diagonal * scaleFactor;
            
            this.calculateElevations( west, north, ewCenter, nsCenter );
            this.calculateElevations( ewCenter, north, east, nsCenter );
            this.calculateElevations( west, nsCenter, ewCenter, south );
            this.calculateElevations( ewCenter, nsCenter, east, south );
        }
    }
    
}