Code Sketch


Random Art
By: Saurabh Kapoor
Category: Art
// Random Art 
// Partial implementation of the Nifty assignment on Random Art:
// http://nifty.stanford.edu/2009/stone-random-art/

import System.currentTimeMillis

abstract class Expr
case class SinPi(exp: Expr) extends Expr
case class CosPi(exp: Expr) extends Expr
case class Avg(exp1: Expr, exp2: Expr) extends Expr
case class Mul(exp1: Expr, exp2: Expr) extends Expr
case object X extends Expr
case object Y extends Expr

type Environment = Expr => Double

// Pretty print the expression
def prettyPrint(exp: Expr) : String = exp match {
    case SinPi(exp) => format("Sin(Pi * %s)", prettyPrint(exp))
    case CosPi(exp) => format("Cos(Pi * %s)", prettyPrint(exp))
    case Avg(exp1, exp2) => format("Avg(%s, %s)", prettyPrint(exp1), prettyPrint(exp2))
    case Mul(exp1, exp2) => format("Mul(%s, %s)", prettyPrint(exp1), prettyPrint(exp2))
    case X => "X"
    case Y => "Y"
}

// Evaluate the expression
def eval(exp: Expr, env: Environment): Double = exp match {
    case SinPi(exp) => Math.sin(Math.Pi * eval(exp, env))
    case CosPi(exp) => Math.cos(Math.Pi * eval(exp, env))
    case Avg(exp1, exp2) => (eval(exp1, env) + eval(exp2, env)) / 2
    case Mul(exp1, exp2) => eval(exp1, env) * eval(exp2, env)
    case X => env(X)
    case Y => env(Y)
}

// Generate a random expression upto the specified depth
// Note that the random 
def genRandomExpression(maxDepth: Int) : Expr = {
    def genExpressionAtDepth(curDepth: Int): Expr = {
        val r:Int = random(maxDepth)
        // Return a literal expression with the probability of curDepth / maxDepth
        if(r <= curDepth  - 1){
            if((random(2) % 2) == 0)  X else Y
        }
        else {
            // Randomly return a compound expression
            val randMultiple: Int = random(4)
            randMultiple match {
                case 0 => SinPi(genExpressionAtDepth(curDepth + 1))
                case 1 => CosPi(genExpressionAtDepth(curDepth + 1))
                case 2 => Avg(genExpressionAtDepth(curDepth + 1),
                              genExpressionAtDepth(curDepth + 1))
                case 3 => Mul(genExpressionAtDepth(curDepth + 1),
                              genExpressionAtDepth(curDepth + 1))
            }
        }
    }
    if(maxDepth == 0) // Base case
        if((random(2) % 2) == 0)  X else Y
    else
        genExpressionAtDepth(0)
}

// Plot the Red, Green and Blue values on the grid
def plotRandomArt(redExp: Expr, greenExp: Expr, blueExp: Expr)(maxAxes: Int) {
    def mapDoubleToColor(x: Double) : Int = (((x + 1) * 255) / 2).intValue()

    def plotPoint(x: Int, y: Int) {
        val env : Environment = { case X => x.doubleValue()/maxAxes.doubleValue() 
                                  case Y => y.doubleValue()/maxAxes.doubleValue() }
        val R = mapDoubleToColor(eval(redExp, env))
        val G = mapDoubleToColor(eval(greenExp, env))
        val B = mapDoubleToColor(eval(blueExp, env))
        Staging.setPenColor(new Color(R, G, B))
        Staging.dot(x, y)
    }

    
    // This for comprehension is hard to read
    // Plot all points between the max point on the axes
    (for { y <- List.range(-maxAxes, maxAxes + 1)
           x <- List.range(-maxAxes, maxAxes + 1) } yield((x,y))) map (pair => plotPoint(pair._1, pair._2))

}

// Create a random art of a given size with expressions of supplied depth
def createRandomArt(depth: Int, size: Int) {
    val redExp = genRandomExpression(depth)
    val greenExp = genRandomExpression(depth)
    val blueExp = genRandomExpression(depth)
    // Print the expressions
    println("Red expression: ", redExp)
    print("Green expression: ", greenExp)
    print("Blue expression: ", blueExp)
    plotRandomArt(redExp, greenExp, blueExp)(size)
    
}

axesOn()
Staging.clear()
Staging.reset()
setAnimationDelay(0)
// Create a random art
createRandomArt(20, 30)