i am trying to figure out how i can enable a user to step through an algorithm using a button click on P5 and JS. The other code i have takes some text and displays some custom character cells that are used in the algorithm i mentioned below. I want the user to click a next button and have it step through and wait for user input before doing each step.
Below is a code snippet
async function straightforward(patternCells, textCells){ const timeout = async ms => new Promise(res => setTimeout(res, ms)); let nextStep = false; forwardButton = createButton("->",0,0); forwardButton.position(confirmButton.x + backButton.width, 400); forwardButton.mousePressed(() => next = true) //Do some set up and display the button for (var i = 0; i < textLen; i++) { var j = 0; await waitButtonNext(); //algorithm runs here } async function waitButtonNext() { while (nextStep === false) await timeout(1); // pause script but avoid browser to freeze ;) nextStep = false; // reset var }
There are no errors in the console on chrome either.
Advertisement
Answer
There are lots of ways to do this. One way is to create an array of functions per step and execute them one at a time whenever a button is pressed.
For example:
const steps = [ () => { text("step 1; click to go to step 2", 10, 50); }, () => { text("step 2; click to go to step 3", 10, 50); }, () => { text("step 3; click to go to end", 10, 50); }, ]; const defaultAction = () => text("that's it", 10, 50); function setup() { createCanvas(300, 100); textSize(20); noLoop(); } function draw() { text("click to start", 10, 50); } function mousePressed() { clear(); (steps.shift() || defaultAction)(); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
This example is somewhat contrived since no animation occurs per step. A more realistic example would involve animation.
One approach that continues to avoid nasty chains of if
/else
s in the draw
function (although that certainly works in a pinch) is to replace draw
per step and optionally manipulate noLoop()
and loop()
as desired to start and stop the animation.
const sleep = ms => new Promise(r => setTimeout(r, ms)); let allowClick = true; const steps = [ () => { let y = 0; draw = () => { clear(); text("click to start step 2", 50, sin(y) * 20 + 50); y += 0.1; }; loop(); }, async () => { allowClick = false; let y = 20; let n = 4; draw = () => { clear(); text(`pausing for ${n} seconds...`, 50, y += 0.2); }; setInterval(() => --n, 1000); // not precise but OK for this await sleep(4000); allowClick = true; let x = 0; y = 0; draw = () => { clear(); text( "click to end", cos(x) * 20 + 50, sin(y) * 20 + 50 ); x += 0.21; y += 0.13; }; }, // ... ]; const defaultAction = () => { draw = () => {}; noLoop(); clear(); text("that's it", 50, 50); }; function setup() { createCanvas(300, 100); textSize(20); noLoop(); } function draw() { text("click to start", 50, 50); } function mousePressed() { if (allowClick) { (steps.shift() || defaultAction)(); } }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
Going further, let’s say you want to repeat a step. That’s pretty easy with this design. Instead of shifting each function permanently from the array of actions, keep an index to reference which action should be taken. In response to a button click, change the index and call the respective function for that behavior. This is one way to implement “scenes” in p5.js. In some cases, it might make sense to use an object with clearly-named keys per state, e.g. {titleScreen: () => ..., endingScreen: () => {...}}
etc. See Transitioning from one scene to the next with p5.js for a full treatment of this.
You could also “rotate” the array of behaviors to create cyclical repetitions, like:
function mousePressed() { const action = steps.shift(); steps.push(action); action(); }
If you wish, you can store all of these scene or step functions in separate external files, making the code easy to maintain.