2

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.

Vagabond43
  • 25
  • 4
  • probably an overkill, I'm thinking on using [Generator function*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) could be a good match here. – LironZ Apr 21 '22 at 19:12

2 Answers2

2

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.6.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/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.6.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.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
0

One way you can do this is to make the script that you want to execute once into a separate function that you call on a button press while keeping track of completed iterations in a separate variable

Puupuls
  • 76
  • 12
  • Hmm that might work but i am running a text pattern matching algorithm so at each stage 2 things can happen, it either matches characters and highlites the matches cells in green or doesnt match and then marks the cell in the text red to show it moving along the text making comparisons. – Vagabond43 Apr 19 '22 at 17:55
  • You can use one of two options in this case, either you keep track of what character will you be coloring, and pass that to function (or just keep it scoped in a way that that function can access it and on each iteration make the function compute color for the cell. Other option (better performance wise) is to precompute each cells color and then same as in option 1 give the function iteration step AND precomputed colors so that function just displays next character – Puupuls Apr 19 '22 at 18:12