Pixel Art Tips
This page has some tips for the Pixel Art assignment:
- The sketch (when you finish it) will have two recursive functions:
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 callbuildRandomFunction
to create this second element.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!
- 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 functionfunction 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?) - 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
orfib
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. - 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 (likebadFactorial
, above)? What is the base case, and what will bring the argument closer to this base case? - Don’t try to develop
buildRandomFunction
andevaluateRandomFunction
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:- Call
buildRandomFunction
, and useconsole.info
to print its results. This tests justbuildRandomFunction
whether or notevaluateRandomFunction
is working. - 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
- Call
buildRandomFunction
andevaluateRandomFunction
don’t depend on anything in p5.js! (They don’t callrect()
or any other p5.js functions, and they don’t use thewidth
ormouseX
or variables or any other variables that are provided by p5.js.) This means that you can use JavaScript Tutor to trace through team.- 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, withconsole.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).