// Random Art
// Idea inspired from Nify assignment on Random Art:
// http://nifty.stanford.edu/2009/stone-random-art/
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 harder to read
// Plot all points between the max axes
(for { y <- List.range(-maxAxes, maxAxes)
x <- List.range(-maxAxes, maxAxes) } 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)