From several variables to arrays

edited November 2014 in Common Questions

From several variables to arrays

Yes, that's not really a question, but that's still a common problem met by most newcomers to programming, showing their code in the forum.

A base sketch

Lot of newbies with Processing start by making the famous bouncing balls: a circle (or more) that travels in straight line, bouncing on the bounds of the sketch. That's a good starting point to show how to scale this kind of sketch.

The base code, with one ball, is shown in the How do I display a message for a few seconds? article, I won't repeat it here.

Make two balls

Now, say we want two balls. What most beginners do is to duplicate all the code for one ball to have two balls:

// First ball parameters
float posX1, posY1; // Position
float speedX1, speedY1; // Movement (linear)
int radius1; // Radius of the ball
color ballColor1; // And its color

// Second ball parameters
float posX2, posY2;
float speedX2, speedY2;
int radius2;
color ballColor2;

void setup()
{
  size(600, 400);
  smooth();

  // Initialize the ball's data
  posX1 = 120;
  posY1 = 50;
  speedX1 = -2;
  speedY1 = 3;
  radius1 = 24;
  ballColor1 = #002277;

  // Again for the second ball
  posX2 = 220;
  posY2 = 150;
  speedX2 = 2;
  speedY2 = -3;
  radius2 = 32;
  ballColor2 = #007722;
}

void draw()
{
  // Erase the sketch area with some light color
  background(#AAFFEE);
  // Compute the new ball position
  moveBall1();
  // And display it
  displayBall1();
  // And again for the second ball
  moveBall2();
  displayBall2();
}

void moveBall1()
{
  // Move by the amount determined by the speed
  posX1 += speedX1;
  // Check the horizontal position against the bounds of the sketch
  if (posX1 < radius1 || posX1 > width - radius1)
  {
    // We went out of the area, we invert the h. speed (moving in the opposite direction)
    // and put back the ball inside the area
    speedX1 = -speedX1;
    posX1 += speedX1;
  }
  // Idem for the vertical speed/position
  posY1 += speedY1;
  if (posY1 < radius1 || posY1 > height - radius1)
  {
    speedY1 = -speedY1;
    posY1 += speedY1;
  }
}

void displayBall1()
{
  // Simple filled circle
  noStroke();
  fill(ballColor1);
  ellipse(posX1, posY1, radius1 * 2, radius1 * 2);
}

void moveBall2()
{
  posX2 += speedX2;
  if (posX2 < radius2 || posX2 > width - radius2)
  {
    speedX2 = -speedX2;
    posX2 += speedX2;
  }
  posY2 += speedY2;
  if (posY2 < radius2 || posY2 > height - radius2)
  {
    speedY2 = -speedY2;
    posY2 += speedY2;
  }
}

void displayBall2()
{
  noStroke();
  fill(ballColor2);
  ellipse(posX2, posY2, radius2 * 2, radius2 * 2);
}

Well, it works fine...

More balls?

But when you read the code, that's twice as much code to understand, except that's the same code... And if you need to fix or change something, you have to do it twice.

Moreover, if you are asked to make 3 or more balls, you start to be in trouble... This doesn't scale well. At best, you can make one displayBall() routine with parameters:

void displayBall(color ballColor, float posX, float posY, int radius)
{
  noStroke();
  fill(ballColor);
  ellipse(posX, posY, radius * 2, radius * 2);
}

and call it with the parameters of each ball, but moveBall() will have even more parameters; long lists of parameters are not very practical to handle. And the repetitive long declarations at the start, and the initializations in setup() are not handy either.

At this point, some people have an idea, and ask how to generate variable numbers. Their idea is that since we have ballColor1, ballColor2, etc., why not access the variables like: ballColor + n or "ballColor" + n or some other creative syntax, where n is the index.

But, in Java, this doesn't work. This can be done, by reflection, but it is used only in special cases, not for such common usage, so we won't see that here.

Using arrays

The solution is simply to use arrays. This frighten some newbies, but it is really quite simple to use. Where you have one variable, you make an array, and then functions can access the nth item just by being given a number:

// Ball parameters
// Positions
float[] posX;
float[] posY;
// Movements (linear)
float[] speedX;
float[] speedY;
// Radius of the balls
int[] radius;
// And the colors
color[] ballColor;

void setup()
{
  size(600, 400);
  smooth();

  // Initialize the ball's data
  posX[0] = 120;
  posY[0] = 50;
  speedX[0] = -2;
  speedY[0] = 3;
  radius[0] = 24;
  ballColor[0] = #002277;
}

void draw()
{
  // Erase the sketch area
  background(#AAFFEE);
  // Compute the new ball position
  moveBall(0);
  // And display it
  displayBall(0);
}

void moveBall(int n)
{
  // Move by the amount determined by the speed
  posX[n] += speedX[n];
  // Check the horizontal position against the bounds of the sketch
  if (posX[n] < radius[n] || posX[n] > width - radius[n])
  {
    // We went out of the area, we invert the h. speed (moving in the opposite direction)
    // and put back the ball inside the area
    speedX[n] = -speedX[n];
    posX[n] += speedX[n];
  }
  // Idem for the vertical speed/position
  posY[n] += speedY[n];
  if (posY[n] < radius[n] || posY[n] > height - radius[n])
  {
    speedY[n] = -speedY[n];
    posY[n] += speedY[n];
  }
}

void displayBall(int n)
{
  // Simple filled circle
  noStroke();
  fill(ballColor[n]);
  ellipse(posX[n], posY[n], radius[n] * 2, radius[n] * 2);
}

Except this won't work, as I deliberately made an error. If you run this code, you will have a NullPointerException.

Why? The arrays have been declared with the [] notation, accessed with the [0] or [n] notation, but we need to declare how many items there will be in the arrays: in Java, arrays won't grow automatically, unlike some other programming languages.

The arrays can be initialized like this:

// Ball parameters
// Positions
float[] posX = new float[10];
float[] posY = new float[10];
// Movements (linear)
float[] speedX = new float[10];
float[] speedY = new float[10];
// Radius of the balls
int[] radius = new int[10];
// And the colors
color[] ballColor = new color[10];

and then the sketch will work.

Note that all the arrays must have the same size, each index corresponding to one ball object. So, it is better to define a constant instead of repeating the same number everywhere. The advantages: it is less a "magical" number without meaning. You have only one place to change it, if you want more, or less, balls. If you search/replace 10 with another number, you risk to alter another 10 constant with nothing to do with the array size.

So the code will look now like:

// Ball parameters
final int BALL_NB = 10; // final just indicate this value cannot be changed in the program
// Positions
float[] posX = new float[BALL_NB];
float[] posY = new float[BALL_NB];
// Movements (linear)
float[] speedX = new float[BALL_NB];
float[] speedY = new float[BALL_NB];
// Radius of the balls
int[] radius = new int[BALL_NB];
// And the colors
color[] ballColor = new color[BALL_NB];

Now, if you want another ball, you have to change setup() and draw():

void setup()
{
  size(600, 400);
  smooth();

  // Initialize the ball's data
  posX[0] = 120;
  posY[0] = 50;
  speedX[0] = -2;
  speedY[0] = 3;
  radius[0] = 24;
  ballColor[0] = #002277;

  // Initialize the other ball's data
  posX[1] = 220;
  posY[1] = 150;
  speedX[1] = 2;
  speedY[1] = -3;
  radius[1] = 32;
  ballColor[1] = #007722;
}

void draw()
{
  // Erase the sketch area
  background(#AAFFEE);
  // Compute the new ball position
  moveBall(0);
  // And display it
  displayBall(0);
  // Again
  moveBall(1);
  displayBall(1);
}

OK, obviously, the loop in draw() can use a for loop:

void draw()
{
  // Erase the sketch area
  background(#AAFFEE);
  for (int i = 0; i < BALL_NB; i++)
  {
    // Compute the new ball position
    moveBall(i);
    // And display it
    displayBall(i);
  }
}

but we haven't filled yet all the slots of the arrays. It still works, because arrays are filled by default with the default value of their type: 0 for ints and floats, false for booleans, etc. So here we are just displaying balls of size 0...

Notice the loop: array indexes start at 0, and end at the array size minus 1, hence the < condition in the loop.

Array initialization

Initializing the arrays like we do above in setup() isn't very practical: it is quite verbose. We can use a loop and initialize the values with random numbers:

void setup()
{
  size(600, 400);
  smooth();

  // Initialize the balls' data
  for (int i = 0; i < BALL_NB; i++)
  {
    radius[i] = int(random(10, 50));
    posX[i] = random(radius[i], width - radius[i]);
    posY[i] = random(radius[i], height - radius[i]);
    speedX[i] = random(-10, 10);
    speedY[i] = random(-10, 10);
    ballColor[i] = color(0, random(100, 255), random(100, 255));
  }
}

Now, we might want to have a precise color for each ball, and perhaps a precise radius, while keeping pos and speed random. Java has a shortcut notation to declare an array while initializing its values.

// Ball parameters
final int BALL_NB = 5;
// Positions
float[] posX = new float[BALL_NB];
float[] posY = new float[BALL_NB];
// Movements (linear)
float[] speedX = new float[BALL_NB];
float[] speedY = new float[BALL_NB];
// Radius of the balls
int[] radius = { 8, 16, 24, 32, 48 };
// And the colors
color[] ballColor = { #DDEE55, #AA44EE, #22BBAA, #0022FF, #00FF22 };

void setup()
{
  size(600, 400);
  smooth();

  // Initialize the balls' data
  for (int i = 0; i < BALL_NB; i++)
  {
    posX[i] = random(radius[i], width - radius[i]);
    posY[i] = random(radius[i], height - radius[i]);
    speedX[i] = random(-7, 7);
    speedY[i] = random(-7, 7);
  }
}

Note that this notation is only used at the declaration site. If, for any reason, you need to use this notation after the array have been initialized, you have to use a slightly more convoluted notation:

ballColor = new color[] { #DDEE55, #AA44EE, #22BBAA, #0022FF, #00FF22 };

Having all these "parallel" arrays isn't very practical, there isn't much cohesion, we still have repetitive code (the array declarations), information is a bit dispersed.

In the next article, From several arrays to classes, we will show that using classes is an elegant way to address these issues.

Tagged:
Sign In or Register to comment.