Tom Auger
Tom Auger

Reputation: 20111

Bizarre do..while behaviour: can you explain?

Consider the following block of code, run repeatedly ( jsFiddle ):

var length = 50,
  xOffset = 0,
  yOffset = 0;
for (var a = 0; a < 100; ++a) { // just so we can see it "break"
  for (var l = 1; l <= length; l++) {
    var percentComplete = l / length,
      scaledPercent = (.5 - Math.abs(percentComplete - .5)) * 2,
      shake = 0,
      shakeTries = 0,
      deviationCeil = Math.ceil(10 * scaledPercent);
    if (Math.random() < .1 || Math.abs(xOffset) > deviationCeil)
      do {
        shake = Math.floor(Math.random() * 3) - 1;
        if (++shakeTries > 100) throw "X shake exceeded"
    }
    while (Math.abs(xOffset + shake) > deviationCeil);
    xOffset += shake;
    shakeTries = 0; // if we set shake = 0 here, everything works!
    if (Math.random() < .1 || Math.abs(yOffset) > deviationCeil)
      do {
        shake = Math.floor(Math.random() * 3) - 1;
        if (++shakeTries > 100) throw "Y shake exceeded"
    }
    while (Math.abs(yOffset + shake) > deviationCeil);
    yOffset += shake;
  }
}

When run repeatedly, the "Y shake exceeded" exception is thrown (the "X shake exceeded" is never thrown).

The solution is to set shake to 0 just before the Y block: shake = shakeTries = 0.

I don't see why this should be the case. In both blocks we start by assigning to shake, so it really shouldn't matter what the hell shake was, prior to going into the do block. My understanding of do...while (and the reason I'm using it) is that it executes its block first before testing the condition.

So why does it fail (not every time) when I don't reset shake before the do block?

Upvotes: 2

Views: 93

Answers (1)

zkhr
zkhr

Reputation: 729

This weird behavior becomes more visible if we put in some additional { and }. Lets first look at just the X section. At the start, both shake and shakeOffset equal 0.

if (Math.random() < .1 || Math.abs(xOffset) > deviationCeil) {
  do {
    shake = Math.floor(Math.random() * 3) - 1;
    if (++shakeTries > 100) throw "X shake exceeded"
  } while (Math.abs(xOffset + shake) > deviationCeil);
}
xOffset += shake;

At this point, shake has whatever value was last used in the previous block (-1, 0, or 1). Then we get to the Y section:

shakeTries = 0; // if we set shake = 0 here, everything works!
if (Math.random() < .1 || Math.abs(yOffset) > deviationCeil) {
  do {
    shake = Math.floor(Math.random() * 3) - 1;
    if (++shakeTries > 100) throw "Y shake exceeded"
  } while (Math.abs(yOffset + shake) > deviationCeil);
}
yOffset += shake;

If we don't meet the condition that (Math.random() < .1 || Math.abs(yOffset) > deviationCeil), then we skip the do...while entirely and add the value of shake from the X section to yOffset.

Upvotes: 3

Related Questions