Skip to content
Advertisement

Trying to allow the user to step through a for loop for an algorithm using JS and P5 via a button press

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.4.1/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/elses 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.4.1/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.

Advertisement