Pause Line Execution Until Condition Happens in P5

I am making an abacus simulator in p5 (sketch file) that does all the moves step by step. To animate the movement, I have variable x incrementally increase/decrease in a show function I created (lines 166 and 172), which is getting called in draw() over and over again. It knows what to do based on the state (bead on left or right side).

I am currently writing a function add(a, b) that adds two numbers. It works correctly, but it only animates the final answer instead of animating it step by step (lines containing //animate should be what animates). When I change the row variable (two-dimensional array) in a for loop, I would like to have it fully animate (I think by calling the show function within the loop) before iterating through the next row element. The lines with //animate in it are what I want to animate. I’ve tried doing a while loop to stall it, but it doesn’t work and show() needs to get called over and over again before proceeding.

I’ve even tried running a for loop (with distance / speed) which should be the number of times (11,100) show() would need to get called to fully animate to the other side. None of these things are working! Any ideas how I can make line execution not continue until it finishes animating (starting at line 93 in add())? Try it out first in the console with the add function. I am going to be doing some refactoring later.

draw() is your animation loop – it updates the screen every time it ends. You need to work within that constraint. You can’t update the screen multiple times within a single draw loop – instead you need to build something outside / around draw, even if it is just state.

Here is one approach that will work.

  1. on input, add an action to a queue of actions
  2. if all beads are at their targets, start the next queued action
  3. on action (with all beads at rest), set new location targets for selected beads

Separately from this, in draw, implement all animation like this:

  1. If a bead is not at its target, move it towards its target.

Now the speed of individual bead motion will also control how fast actions get processed out of the queue. But the queue system doesn’t know anything about animation – it is just a list of operations – and the action targeting system doesn’t know about the queue – it just takes a static list of beads and described their new positions – and the animation system doesn’t know anything about actions – it just moves any beads that aren’t “there yet” closer to wherever they want to go.

Thanks for the response. What do you mean by “on input”? Do I have to completely re-write how it gets updated?

Well, for example, you press a 2 key. Or let’s just say for now that any key you press is the +2 action.

void keyReleased(){
  addAction(2);
}

That is your input.

Let’s make this a really simple abacus with a single line of beads that only takes one number as an action.

Okay, we might still be sliding beads, so we don’t know if we are ready to add 2 yet. Let’s put that 2 somewhere.

We could use an IntList, but a queue will be easier.
https://docs.oracle.com/javase/8/docs/api/java/util/ArrayDeque.html

import java.util.ArrayDeque;
ArrayDeque<Integer> queue = new ArrayDeque();

Now let’s add our action. If we just spammed five other actions while the beads were in motion, this has to go to the back of the line – it will get processed last.

void addAction(int num){
   queue.addLast(num)
}

That adds an action “2” to the back of the queue.

To do an action, first draw checks if any beads are in motion, for example like this:

void draw(){
  boolean moving = false;
  for(bead : Beads){
    if(bead.moving){
      moving = bead.moving;
    }
  }
  if(!moving){
    doAction(queue);
  }
}

Now doAction knows that no beads are sliding. It is going to get the next action off the queue if it exists.

if(queue.size() > 0){
   int action = queue.removeFirst()
}

Okay, time to do your “2” action, which adds 2. I’ll skip the part where you decide what beads should now go where – I believe that you already know how to do this. Once you have a bead and a location, give it the new target coordinates.

bead.targety = newTargetYLocation;

Because every draw you loop over beads and call each bead.move(), the beads with new marching orders will start moving to their targets. And because they know that they are moving, your next action won’t get called until they are done – because you only call an action when no beads are moving.

class Bead {
  float y, targety;
  // ....
  void move(){
    if(this.moving()){
      // move bead closer to target
    }
  }
  boolean moving(){
    return (this.y!=this.targety);
  }
}

Now you have a system that stores a queue of actions:

+1 -2 +3 -2

…and a system that takes those actions one at a time, tells beads where to go next, and animates those beads until they get there.

This is just one of many ways to approach your problem – and there are lots of different ways to implement the individual parts, depending on your needs and preferred coding style. Hope it was helpful.

Thanks again for the response! Unfortunately, I’m having a hard time trying to figure out how I would try to implement this in my code. Would you be able to post an example (maybe using the sketch.js) implementing this?

Sorry, that pseudocode in Java Processing snippets is all the help that I can manage at the moment. If you decide to try the approach and have questions about it then ask here. Actually I forgot that this was a JS question, or I wouldn’t have recommended ArrayDeque – that is a Java datatype, sorry for the confusion.

Hopefully you can adapt it, and / or someone with more time right now can help map it into JS – or suggest a different approach.

Although I know more or less what an abacus is, never seen 1 in front of me yet. :see_no_evil:

From the various models at Wikipedia:

This Danish 1 seems the most simple to implement:

It is 10 rows, and each row contains 10 beads.

We can represent it as an array of length 10: abacus = new Uint8Array(10);

And a bead is either on the left or on the right side of its row.

We can make it so that each indexed value stored in the array represents how many beads are on the right side: return abacus[idx];

On the other hand, in order to know how many beads are on the left side from an index row, we subtract the beads total (10) from the number of beads on the right side: return 10 - abacus[idx];

I’ve made a class Abacus w/ basic methods that can query and modify the current state of each row of the abacus:

“abacus.js”:

class Abacus {
  constructor() {
    this.abacus = new Uint8Array(this.ROWS);
  }

  resetAllAbacusToLeft() {
    this.abacus.fill(0);
    return this;
  }

  resetAllAbacusToRight() {
    this.abacus.fill(this.BEADS);
    return this;
  }

  getNumberOfLeftBeadsFromRowIndex(idx) {
    return this.BEADS - this.abacus[idx];
  }

  getNumberOfRightBeadsFromRowIndex(idx) {
    return this.abacus[idx];
  }

  setNumberOfLeftBeadsFromRowIndex(idx, num=0) {
    this.abacus[idx] = Math.max(this.BEADS - Math.abs(num), 0);
    return this;
  }

  setNumberOfRightBeadsFromRowIndex(idx, num=0) {
    this.abacus[idx] = Math.min(Math.abs(num), this.BEADS);
    return this;
  }

  increaseNumberOfLeftBeadsFromRowIndex(idx, num=1) {
    return this.decreaseNumberOfRightBeadsFromRowIndex(idx, num);
  }

  increaseNumberOfRightBeadsFromRowIndex(idx, num=1) {
    this.abacus[idx] = Math.min(this.abacus[idx] + Math.abs(num), this.BEADS);
    return this;
  }

  decreaseNumberOfLeftBeadsFromRowIndex(idx, num=1) {
    return this.increaseNumberOfRightBeadsFromRowIndex(idx, num);
  }

  decreaseNumberOfRightBeadsFromRowIndex(idx, num=1) {
    this.abacus[idx] = Math.max(this.abacus[idx] - Math.abs(num), 0);
    return this;
  }

  addOrRemoveNumberOfLeftBeadsFromRowIndex(idx, num=0) {
    return this.addOrRemoveNumberOfRightBeadsFromRowIndex(idx, -num);
  }

  addOrRemoveNumberOfRightBeadsFromRowIndex(idx, num=0) {
    return num > 0 &&
           this.increaseNumberOfRightBeadsFromRowIndex(idx, num) ||
           this.decreaseNumberOfRightBeadsFromRowIndex(idx, num);
  }

  isBeadIndexOnLeftSideFromRowIndex(rowIdx, beadIdx) {
    return beadIdx < this.getNumberOfLeftBeadsFromRowIndex(rowIdx);
  }

  isBeadIndexOnRightSideFromRowIndex(rowIdx, beadIdx) {
    return !this.isBeadIndexOnLeftSideFromRowIndex(rowIdx, beadIdx);
  }

  getNumberOfBeadsOnTheLeftSideOfBeadsIndexFromRowIndex(rowIdx, beadIdx) {
    const leftBeads = beadIdx - this.getNumberOfLeftBeadsFromRowIndex(rowIdx);
    return leftBeads >= 0? leftBeads : beadIdx;
  }

  getNumberOfBeadsOnTheRightSideOfBeadsIndexFromRowIndex(rowIdx, beadIdx) {
    const rightBeadsOnRight = this.BEADS - 1 - beadIdx,
          rightBeadsOnLeft  = rightBeadsOnRight - this.abacus[rowIdx];

    return rightBeadsOnLeft >= 0? rightBeadsOnLeft : rightBeadsOnRight;
  }
}

Abacus.prototype.ROWS = Abacus.ROWS = 10;
Abacus.prototype.BEADS = Abacus.BEADS = 10;

Obviously, the class above can’t render the abacus itself, much less animate its beads; but it’s a good base to start the code from. :innocent:

The thing to understand is that your abacus class (in that case) is only representing what your values should be each time your perform an operation – only snapshots of before and after.

It also needs a collection of bead objects that move to the appropriate positions in real time.

An interesting thing about an abacus is that each bead has (I believe?) only two valid positions on the rail – a specific left position, and a right position. You could precompute those as left and right targets.

Another option is to not model beads at all. Model a block of left beads, a block of right beads, and a block of (moving) center beads. So when a rail is in motion, you lerp the whole rail and render it in one pass. That won’t get you bead physics (bouncing and clacking etc.) but it will get the job done.

1 Like
  • I’ve made a Bead class too! :star_struck:
  • It’s got 4 states: LEFT, GOING_LEFT, GOING_RIGHT, RIGHT.
  • Even though it has its own local state, it also has an update() method which compares itself to the Abacus’ global state.
  • If the local state doesn’t match the global state, it automatically puts itself in either the GOING_LEFT or GOING_RIGHT state in order to move() to the global state.
  • Therefore, we shouldn’t directly change the Bead::state, but rather invoke the Abacus’s methods, and all the Bead objects will follow suit. :grinning:

“bead.js”:

class Bead {
  constructor(sketch, abacus, row, col, xLeft, xRight, y, color='red') {
    this.p = sketch;
    this.a = abacus;

    this.row = Math.min(Math.abs(row), abacus.ROWS  - 1);
    this.col = Math.min(Math.abs(col), abacus.BEADS - 1);

    this.xl = +xLeft;
    this.xr = +xRight;
    this.y = +y;
    this.c = color;

    this.state = abacus.isBeadIndexOnLeftSideFromRowIndex(this.row, this.col)?
                 this.LEFT : this.RIGHT;

    this.x = this.state === this.LEFT? this.xl : this.xr;
  }

  action() {
    return this.update().move().render();
  }

  update() {
    const { a, row, col, state, x, xl, xr } = this,
          { LEFT, GOING_LEFT, GOING_RIGHT, RIGHT } = Bead,
          onLeft = a.isBeadIndexOnLeftSideFromRowIndex(row, col);

    if (x <= xl)       this.state = LEFT;
    else if (x >= xr)  this.state = RIGHT;

    if (onLeft)  state > GOING_LEFT  && (this.state = GOING_LEFT);
    else         state < GOING_RIGHT && (this.state = GOING_RIGHT);

    return this;
  }

  move() {
    const { p, state, x, xl, xr, SPD, GOING_LEFT, GOING_RIGHT } = this;

    if (state === GOING_LEFT | state === GOING_RIGHT) {
      const spd = state === GOING_LEFT? -SPD : SPD;
      this.x = p.constrain(x + spd, xl, xr);
    }

    return this;
  }

  render() {
    this.p.fill(this.c).circle(this.x, this.y, this.DIAM);
    return this;
  }
}

Bead.prototype.DIAM = Bead.DIAM = 30;
Bead.prototype.SPD = Bead.SPD = 4;

Bead.prototype.LEFT = Bead.LEFT = 0;
Bead.prototype.GOING_LEFT = Bead.GOING_LEFT = 1;
Bead.prototype.GOING_RIGHT = Bead.GOING_RIGHT = 2;
Bead.prototype.RIGHT = Bead.RIGHT = 3;

On a 2nd glance, there aren’t any significant differences between the states LEFT & GOING_LEFT or RIGHT & GOING_RIGHT. :thinking:

And on a 2nd thought, it makes no much sense to keep an internal state if said state gotta match an external 1. :roll_eyes:

Class Bead already has to invoke method Abacus::isBeadIndexOnLeftSideFromRowIndex() all the time in order to sync its property Bead::state after all. :flushed:

So why not remove Bead::state along w/ all of its 4 related constants? :smiling_imp:
We can even get rid of the now useless method Bead::update() as well! :partying_face:

“bead.js”:

class Bead {
  constructor(sketch, abacus, row, col, xLeft, xRight, y, color='red') {
    this.p = sketch;
    this.a = abacus;

    this.row = Math.min(Math.abs(row), abacus.ROWS  - 1);
    this.col = Math.min(Math.abs(col), abacus.BEADS - 1);

    this.xl = +xLeft;
    this.xr = +xRight;
    this.y = +y;
    this.c = color;

    const onLeft = abacus.isBeadIndexOnLeftSideFromRowIndex(this.row, this.col);
    this.x = onLeft? this.xl : this.xr;
  }

  action() {
    return this.move().render();
  }

  move() {
    const { p, a, row, col, x, xl, xr, SPD } = this,
          spd = a.isBeadIndexOnLeftSideFromRowIndex(row, col)? -SPD : SPD;

    this.x = p.constrain(x + spd, xl, xr);
    return this;
  }

  render() {
    this.p.fill(this.c).circle(this.x, this.y, this.DIAM);
    return this;
  }
}

Bead.prototype.DIAM = Bead.DIAM = 30;
Bead.prototype.SPD = Bead.SPD = 4;
1 Like