YCP Logo Assignment 3.5: Random Art

Due Monday, October 7th by 11:59pm

Update 9/30 - suggested methods for adding and accessing child nodes

Acknowledgment: The idea for this assignment comes from Christopher Stone of Harvey Mudd College.

Getting Started

Download RandomArt.zip.

Unzip it. In a terminal window (e.g., Cygwin terminal), change directory to the RandomArt directory.

Use the command make to compile the program. Run the program using the command


The program will write an output image in a file called randomart.bmp. It will also print textual representations of three functions. Example:

R: sin(pi*avg(avg(avg(sin(pi*sin(pi*sin(pi*avg(sin(pi*sin(pi*avg(x,x))),y,x)))),sin(pi*sin(pi*avg(sin(pi*sin(pi*sin(pi*sin(pi*x)))),sin(pi*y)))),avg(sin(pi*y),sin(pi*sin(pi*sin(pi*sin(pi*x)))))),y),x))
G: sin(pi*sin(pi*sin(pi*avg(sin(pi*sin(pi*sin(pi*y))),y,sin(pi*y)))))
B: sin(pi*sin(pi*avg(sin(pi*sin(pi*avg(sin(pi*sin(pi*sin(pi*sin(pi*y)))),sin(pi*x)))),sin(pi*sin(pi*sin(pi*sin(pi*x)))),y)))

Your Task

You have two tasks:

  1. Implement a tree data structure
  2. Use the tree to represent "random" functions of variables x and y

The random functions in step 2 are used to create "random art". Here's the idea

x and y values are restricted to the range -1 to 1.

The random functions are built from operations which are guaranteed to produce values in the range -1 to 1.

The random art is created by generating three random functions: one for red, one for blue, one for green. Each pixel in a square region of the GUI window corresponds to an x,y coordinate in the 2x2 region of the x/y plane centered at the origin. Each pixel's color is determined by evaluating the three functions for the pixel's x/y values, and mapping each result to the range 0..255. This produces three color component values, which together determine the overall color of the pixel.

Here is an example (click for full-size image):

The following primitive operations can be composed to create functions which produce values in the range -1..1 (as long as x and y are in the same range):

  • x
  • y
  • sin(π * _)
  • cos(π * _)
  • average(_, _, ...)

In the above operations, the placeholder "_" is any expression built from the same operations. Thus, operations are nested recursively to represent arbitrary functions.

Representing Functions as Trees

The random functions are represented of trees consisting of ExprNode objects.

The ExprNode class is defined as follows:

class ExprNode {
        // TODO: add field(s), e.g., to keep track of child nodes

        // copy constructor and assignment are forbidden: do not implement them
        ExprNode(const ExprNode &);
        ExprNode& operator=(const ExprNode &);

        virtual ~ExprNode();

        // Evaluate for given x and y values
        virtual double evaluate(double x, double y) const = 0;

        // Generate a textual representation of the function represented
        // by this node and return it as a string.
        virtual std::string exprAsString() const = 0;

        // TODO: add method(s), e.g., to add a child node, to get a child node, etc.

The evaluate method evaluates the expression for given values of x and y.

The exprAsString method returns a string containing a textual representation of the expression.

You will need to modify this class so that child nodes can be added and accessed. You can use either a linked list or an array to store the references to the child nodes.

Suggestion: add the following methods to the ExprNode class:

void addChild(ExprNode *child);
int getNumChildren() const;
ExprNode *getChild(int index) const;

These methods will allow you to add and access child nodes.

Node that the virtual keyword in C++ means "can be overridden". This is used in a superclass to designate methods that superclasses may override with their own definitions.

Also note that ExprNode is an abstract class. (Virtual methods defined with = 0 at the end are abstract.) You will need to implement concrete subclasses that represent the 5 fundamental operations described above. The node types representing the variables x and y will be the leaves of the overall expression tree. The node types representing sin(π * _) and cos(π * _) expressions will each have a single child. The node type representing average(_, _, ...) expressions will have 1 or more children.

An implementation of the XNode class is provided for you: this represents an expression that evaluates to the specified value of x. You can use this as a guide for how to implement the other ExprNode subclasses.

Examples: here are how two expressions would be represented as trees:

cos(π*sin(π*avg(cos(π*cos(π*sin(π*avg(y, y, x)))), cos(π*x))))

Generating "Random" Expression Trees

The ExprBuilder class is used to generate "random" expression trees. It is defined as follows:

class ExprBuilder {
        // You can add fields here, but you might not need any

        // Copy constructor and assignment operator are not allowed:
        // don't implement them
        ExprBuilder(const ExprBuilder &);
        ExprBuilder& operator=(const ExprBuilder &);


        // Build a random expression
        ExprNode *build() const;

        // TODO: add additional methods

You will implement the build method.

The following rules should be used to build random expression trees.

(1) Trees must have no more than MAX_LEVEL levels. The leaves of the tree are nodes representing x and y expressions (with equal probability). All nodes added at level MAX_LEVEL of the tree must be x or y nodes.

(2) For nodes at levels of the tree less than MAX_LEVEL, the following probabilities should be used to determine what kind of node to create:

  • 10% chance of creating an x node
  • 10% chance of creating a y node
  • 20% chance of creating an average(_, _, ...) node with 2 or 3 randomly-generated child expressions
  • 30% chance of creating a sin(π * _) node with a single randomly-generated child expression
  • 30% chance of creating a cos(π * _) node with a single randomly-generated child expression


Your grade is determined as follows:

  • Implementation of ExprNode and tree operations (adding/accessing child nodes): 30%
  • Implementation of ExprNode subclasses to represent expressions (evaluation, formatting as text): 30%
  • Building random expression trees: 40%


To submit, run the command

make submit

Enter your Marmoset username and password when prompted.