Pixel Art Tips

This page has some tips for the Pixel Art assignment:

  1. The sketch (when you finish it) will have two recursive functions:
    1. buildRandomFunction constructs an Array. If the first element of this Array is “avg” or “sin_pi” etc. (anything except “x” or “y”), then the second element is also an Array. For example, ["sin_pi", ["x"]], or ["sin_pi", ["sin_pi", ["x"]]]. buildRandomFunction can call buildRandomFunction to create this second element.
    2. evaluateRandomFunction is also recursive. Its argument is a Array (for example, ["x"] or ["sin_pi", ["x"]]), and it returns a number. If its argument is a list whose first element is e.g. “sin_pi” or "avg", then its second argument is also an Array, that needs to be turned into a number. If you had a function that took an Array as an argument and returned a Number, then you could write this function that takes an Array as an argument and returns a Number. This is a job for recursion!
  2. A recursive function needs to get closer to its base case (the values that will cause it not to call itself). The function function badFactorial(n) { return n * badFactorial(n - 1); } will get an error “Maximum call stack size exceeded” because there is no base case. The function function badFactorial(n) { if (n === 1) { return 1; } else { return n * badFactorial(n); } } will also get an error, because when it calls itself, it doesn’t get any closer to the base case. (Do you see why?)
  3. One way to get closer to a base case is to if the argument is a number that counts down (is lower each time the function calls itself), like (good) factorial or fib from Sunday. Another is to use an argument that counts up to some limit, that the base case tests for. A third is to if the function breaks its argument into smaller pieces, that the function applies itself recursively to. This is common when the argument is an array of arrays, or an object whose properties are objects, etc.
  4. How does buildRandomFunction call itself with arguments that are closer to its base case, so that it eventually reaches the base case instead of calling itself each time (like badFactorial, above)? What is the base case, and what will bring the argument closer to this base case?
  5. Don’t try to develop buildRandomFunction and evaluateRandomFunction at the same time. (Don’t use whether the entire program works as a way to tell whether both of these functions work, before you have verfied that either of them works.) Instead, do this:
    1. Call buildRandomFunction, and use console.info to print its results. This tests just buildRandomFunction whether or not evaluateRandomFunction is working.
    2. Add an early return to buildRandomFunction that returns the same value each time: for example, return ["x"], or return ["sin_pi", ["x"]]. You can use this to debug evaluateRandomFunction for just that value, without having to worry that evaluateRandomFunction will be called with a different value each time (this makes debugging difficult), or with a very complicated valu
  6. buildRandomFunction and evaluateRandomFunction don’t depend on anything in p5.js! (They don’t call rect() or any other p5.js functions, and they don’t use the width or mouseX or variables or any other variables that are provided by p5.js.) This means that you can use JavaScript Tutor to trace through team.
  7. While you are developing the program, try smaller numbers. Use smaller numbers for xSize and ySize so that your program starts more quickly, and you can see your changes more quickly. Use smaller numbers for minDepth and maxDepth so that the trees that buildRandomFunction creates are not so deep, and they are easier to inspect – for example, with console.log. (This will also make evaluateRandomFunction faster.)

Here is an illustration of the kind of data that buildRandomFunction creates (returns), and that evaluateRandomFunction accepts (as its first argument).

image-20200421112834019