Array Index Out of Bounds error

I’m currently building a pattern for an Art Car at Burning Man, and I’m running into a funny error with it.
The basics of the pattern is to be a Rubics cube. It mixes itself up, and then solves it by reversing the mixes. The pattern itself works fine, but if I have it do too many moves, I end up with an Array index out of bounds error in either the rotateRow or RotateColumn functions, on the temp rows array declaration

  var tempRows = array(300);

This doesn’t make any sense as I’m just creating that array.

I recently converted the program from using 81 pixels a face to 225, and I’ve seen a decrease in the number of allowable moves. Currently it will succeed at 8 moves (so 16 round trip) sometimes work at 9 and 10, and always fail at 11 (22 moves round trip)

I’ve added debugging to it, but it’s always telling me it’s successfully undone all the moves, which is not true.

Code
var RED = 0, GREEN = 0.333, BLUE = 0.667, YELLOW = 0.167, ORANGE = 0.083, WHITE = 5;

var maxMoves = 10; // Adjust as needed
var moves = array(maxMoves * 3);
var moveIndex = 0;
var isMixing = true;
export var moveCount = 0;
var framesPerMove = 2;

export var debugLastOperation = 0;
export var debugMoveIndex = 0;
export var debugIsMixing = 1;
export var debugLastMoveType = 0;
export var debugLastGroupIndex = 0;
export var debugLastMoveDirection = 0;
export var debugSolveComplete = 0;
export var debugTotalFrames = 0;

var solveComplete = false;

function createFilledArray(size, value) {
  var arr = array(size);
  for (var i = 0; i < size; i++) {
    arr[i] = value;
  }
  return arr;
}

// Cube representation (6 sides, each side is 15x15)
var cube = [
  createFilledArray(225, WHITE),   // Front (0)
  createFilledArray(225, GREEN),   // Left (1)
  createFilledArray(225, YELLOW),  // Back (2)
  createFilledArray(225, BLUE),    // Right (3)
  createFilledArray(225, ORANGE),  // Top (4)
  createFilledArray(225, RED)      // Bottom (5)
];

function rotateface(faceIndex, clockwise) {
  var oldFace = cube[faceIndex];
  var newFace = array(225);
  debugLastOperation = 1; // rotateface
  
  for (var i = 0; i < 15; i++) {
    for (var j = 0; j < 15; j++) {
      if (clockwise) {
        newFace[j * 15 + (14 - i)] = oldFace[i * 15 + j];
      } else {
        newFace[(14 - j) * 15 + i] = oldFace[i * 15 + j];
      }
    }
  }
  
  cube[faceIndex] = newFace;
}

function rotateRow(rowGroup, direction) {
  var startRow = rowGroup * 5;
  var tempRows = array(300);
  debugLastOperation = 2; // rotateRow
  
  for (var i = 0; i < 5; i++) {
    for (var j = 0; j < 15; j++) {
      tempRows[i * 15 + j] = cube[0][(startRow + i) * 15 + j];         // Front
      tempRows[(i + 5) * 15 + j] = cube[3][(startRow + i) * 15 + j];   // Right
      tempRows[(i + 10) * 15 + j] = cube[2][(startRow + i) * 15 + j];  // Back
      tempRows[(i + 15) * 15 + j] = cube[1][(startRow + i) * 15 + j];  // Left
    }
  }
  
  if (direction > 0) { // Rotate right
    for (var i = 0; i < 5; i++) {
      for (var j = 0; j < 15; j++) {
        cube[3][(startRow + i) * 15 + j] = tempRows[i * 15 + j];           // Front to Right
        cube[2][(startRow + i) * 15 + j] = tempRows[(i + 5) * 15 + j];     // Right to Back
        cube[1][(startRow + i) * 15 + j] = tempRows[(i + 10) * 15 + j];    // Back to Left
        cube[0][(startRow + i) * 15 + j] = tempRows[(i + 15) * 15 + j];    // Left to Front
      }
    }
    if (rowGroup === 0) rotateface(4, true);  // Top face clockwise
    if (rowGroup === 2) rotateface(5, false); // Bottom face counterclockwise
  } else { // Rotate left
    for (var i = 0; i < 5; i++) {
      for (var j = 0; j < 15; j++) {
        cube[1][(startRow + i) * 15 + j] = tempRows[i * 15 + j];           // Front to Left
        cube[0][(startRow + i) * 15 + j] = tempRows[(i + 5) * 15 + j];     // Right to Front
        cube[3][(startRow + i) * 15 + j] = tempRows[(i + 10) * 15 + j];    // Back to Right
        cube[2][(startRow + i) * 15 + j] = tempRows[(i + 15) * 15 + j];    // Left to Back
      }
    }
    if (rowGroup === 0) rotateface(4, false); // Top face counterclockwise
    if (rowGroup === 2) rotateface(5, true);  // Bottom face clockwise
  }
}

function rotateColumn(colGroup, direction) {
  var startCol = colGroup * 5;
  var tempCols = array(300);
  debugLastOperation = 3; // rotateColumn
  
  for (var i = 0; i < 5; i++) {
    for (var j = 0; j < 15; j++) {
      tempCols[i * 15 + j] = cube[4][j * 15 + (startCol + i)];         // Top
      tempCols[(i + 5) * 15 + j] = cube[0][j * 15 + (startCol + i)];   // Front
      tempCols[(i + 10) * 15 + j] = cube[5][j * 15 + (startCol + i)];  // Bottom
      tempCols[(i + 15) * 15 + j] = cube[2][(14 - j) * 15 + (14 - startCol - i)]; // Back (reversed)
    }
  }
  
  if (direction > 0) { // Rotate down
    for (var i = 0; i < 5; i++) {
      for (var j = 0; j < 15; j++) {
        cube[0][j * 15 + (startCol + i)] = tempCols[i * 15 + j];           // Top to Front
        cube[5][j * 15 + (startCol + i)] = tempCols[(i + 5) * 15 + j];     // Front to Bottom
        cube[2][(14 - j) * 15 + (14 - startCol - i)] = tempCols[(i + 10) * 15 + j]; // Bottom to Back (reversed)
        cube[4][j * 15 + (startCol + i)] = tempCols[(i + 15) * 15 + j];    // Back to Top
      }
    }
    if (colGroup === 0) rotateface(1, false); // Left face clockwise
    if (colGroup === 2) rotateface(3, true);  // Right face clockwise
  } else { // Rotate up
    for (var i = 0; i < 5; i++) {
      for (var j = 0; j < 15; j++) {
        cube[2][(14 - j) * 15 + (14 - startCol - i)] = tempCols[i * 15 + j];     // Top to Back (reversed)
        cube[5][j * 15 + (startCol + i)] = tempCols[(i + 15) * 15 + j];   // Back to Bottom
        cube[0][j * 15 + (startCol + i)] = tempCols[(i + 10) * 15 + j];   // Bottom to Front
        cube[4][j * 15 + (startCol + i)] = tempCols[(i + 5) * 15 + j];    // Front to Top
      }
    }
    if (colGroup === 0) rotateface(1, true);  // Left face counterclockwise
    if (colGroup === 2) rotateface(3, false); // Right face counterclockwise
  }
}

function performNextMove() {
  debugLastOperation = 4; // performNextMove start
  debugMoveIndex = moveIndex;
  debugIsMixing = isMixing ? 1 : 0;

  if (isMixing && moveCount < maxMoves) {
    var moveType = random(2);
    var groupIndex = floor(random(3));
    var moveDirection = random(1) > 0.5 ? 1 : -1;
    
    debugLastMoveType = moveType;
    debugLastGroupIndex = groupIndex;
    debugLastMoveDirection = moveDirection;
    
    if (moveType > 1.0) {
      rotateRow(groupIndex, moveDirection);
    } else {
      rotateColumn(groupIndex, moveDirection);
    }
    
    recordMove(moveType, groupIndex, moveDirection);
    moveCount++;
    if (moveCount == maxMoves) {
      isMixing = false;
      moveIndex = maxMoves * 3; // Ensure moveIndex is at the end
    }
    debugLastOperation = 5; // performNextMove: mixed
    return true;
  } else if (!isMixing && moveCount > 0) {
    moveCount--;
    moveIndex -= 3;
    var lastMoveType = moves[moveIndex];
    var lastGroupIndex = moves[moveIndex + 1];
    var lastMoveDirection = moves[moveIndex + 2]; // Note: We're not negating this anymore
    
    debugLastMoveType = lastMoveType;
    debugLastGroupIndex = lastGroupIndex;
    debugLastMoveDirection = lastMoveDirection;
    
    if (lastMoveType > 1.0) {
      rotateRow(lastGroupIndex, -lastMoveDirection); // Negate direction here
    } else {
      rotateColumn(lastGroupIndex, -lastMoveDirection); // Negate direction here
    }
    debugLastOperation = 7; // performNextMove: solved
    return true;
  }
  
  debugLastOperation = 12; // All moves undone
  return false;
}


function recordMove(moveType, groupIndex, moveDirection) {
  debugLastOperation = 9; // recordMove start
  if (moveIndex < maxMoves * 3 - 2) {
    moves[moveIndex] = moveType;
    moves[moveIndex + 1] = groupIndex;
    moves[moveIndex + 2] = moveDirection;
    moveIndex += 3;
    debugLastOperation = 10; // recordMove: recorded
  } else {
    isMixing = false;
    debugLastOperation = 11; // recordMove: max moves reached
  }
}

var frameCounter = 0;

export function beforeRender(delta) {
  debugTotalFrames++;
  
  if (debugSolveComplete == 1) {
    return;
  }

  frameCounter++;
  
  if (frameCounter >= framesPerMove) {
    if (!performNextMove()) {
      debugSolveComplete = 1;
    }
    frameCounter = 0;
  }
}

export function render(index) {
  var faceIndex, x, y;
  
  if (index < 225) {
    faceIndex = 0; // Front
  } else if (index < 450) {
    faceIndex = 1; // Left
    index -= 225;
  } else if (index < 675) {
    faceIndex = 2; // Back
    index -= 450;
  } else if (index < 900) {
    faceIndex = 3; // Right
    index -= 675;
  } else if (index < 1125) {
    faceIndex = 4; // Top
    index -= 900;
  } else if (index < 1350) {
    faceIndex = 5; // Bottom
    index -= 1125;
  } else {
    rgb(0, 0, 0); // Set to black if outside the cube
    return;
  }

  x = index % 15;
  y = floor(index / 15);

  var pixelIndex = y * 15 + x;
  var color = cube[faceIndex][pixelIndex];
  
  if (color < 1) {
    hsv(color, 1, 1);
  } else if (color >= 1) {
    hsv(0, 0, 1); // White
  }
}
Mapping
function (pixelCount) {
  width = 15
  //generate a 2d matrix in 3d space
  //targets is an array of source coordinates 0 = fixed, 1 = row, 2 = column
  //e.g. [1,2,0] will generate rows = x, cols = y, and z is fixed (at zero)
  //sign indicates direction so -2 is columns in reverse order
  //offsets lets you translate the coordinates by some offset
  function side(targets, offsets) {
    var matrix = [], coords, row, col
    for (i = 0; i < width * width; i++) {
      row = Math.floor(i / width)
      col = i % width
      // col = row % 2 == 1 ? width - 1 - col : col //zigzag
      coords = [0, row, col]
      matrix.push(targets.map(function (target, index) {
        var coord = coords[Math.abs(target)]
        if (target < 0)
          coord = width - 1 - coord
        return coord + offsets[index]
      }))
    }
    return matrix
  }

  var map = []
  var gap = .75
  map = map.concat(side([2, 1, 0], [0, 0, width - 1 + gap]))
  map = map.concat(side([0, 1, 2], [-gap, 0, 0]))
  map = map.concat(side([1, 0, 2], [0, width - 1 + gap, 0]))
  map = map.concat(side([0, -1, 2], [width - 1 + gap, 0, 0]))
  map = map.concat(side([-1, 0, 2], [0, -gap, 0]))

  map = map.concat(side([-1,2,0], [0,0,-.75]))
  return map
}

If I understand correctly, the PixelBlaze doesn’t perform garbage collection, so you’re leaking heap memory every render cycle with those temp arrays until you can’t allocate any more. Rather than declare them inside the functions, try allocating them globally like you do with the cube itself and reuse them each time.

I think the error message for this could be improved, as currently it is a bit confusing and misleading.

3 Likes

Thanks so much, moves to reusable arrays for the temp ones worked!
Didn’t think of the garbage collection problem with this one.

1 Like