Problems with understanding language

There is code for the snake game i am trying to recreate, but it fails on line 44 after eating food with ArrayIndexOutOfBoundsException : 1
Can anybody explain why this happens and how can i fix this?
(only death by overlapping yourself is not coded)

void setup(){
  size(200,200);
  background(0);
  stroke(24,201,46);
  fill(24,201,46);
}
float cs = 20;
int hx = 0, hy = 0, s = 10, xs = 1, ys = 0, t, fx = floor(random(cs))*10, fy = floor(random(cs))*10, tail = 0;
int[] xt = {0,}, yt = {0,};
void keyPressed(){
  if(key == CODED){
    if (keyCode == UP && ys != 1){
      xs = 0;
      ys = -1;
    }
    if (keyCode == DOWN && ys != -1){
      xs = 0;
      ys = 1;
    }
    if(keyCode == RIGHT && xs != -1){
      xs = 1;
      ys = 0;
    }
    if (keyCode == LEFT && xs != 1){
      xs = -1;
      ys = 0;
    }
  }
}
void draw(){
  if (t == 8){
    append(xt,hx);
    append(yt,hy);
    hx+=xs*10;
    hy+=ys*10;
   if (hx >= 201){hx = 0;}
   if(hx < 0){hx = 200;}
   if (hy > 200){hy = 0;}
   if(hy < 0){hy = 200;}
    t = 0;
    background(0);
    rect(hx, hy, s, s);
    for (int i = tail; i > 0; i--){
    xt[i] = xt[i-1];
    yt[i] = yt[i-1];
    rect (xt[i],yt[i],s,s);
    }
  }
  t++;
  if(hx == fx && hy == fy){
    tail++;
    fx = floor(random(cs))*10;
    fy = floor(random(cs))*10;
  }
  stroke(255,0,0);
  fill(255,0,0);
  rect(fx, fy, s, s);
  stroke(24,201,46);
  fill(24,201,46);
}

Thanks for any help!

That means that you are trying to access an element that does not exist in your array. So if you have 4 elements in it, it means that you are trying to access the 5, 6, 7…
I can’t see any place in your code when you are changing the size of your array.

Check out the official documentation about Array for more infos: Array / Reference / Processing.org

1 Like

When you eat the food, the head of the snake is in the same position as the food.

Thus, hx equals fx and hy equals fy. The h and f stand for head and food, you see?

So anyway, this block suddenly starts to happen:

  if (hx == fx && hy == fy) {
    tail++;
    fx = floor(random(cs))*10;
    fy = floor(random(cs))*10;
  }

What does that say? First, make the snake longer. Sounds good. Then, move the food. Okay.

But how many times does that happen? Ah ha! It happens each time draw() happens when the head is at the same position as the food.

Now the next question is, how often is the snake’s position actually updated?

It’s not easy, but if you dive into the use of the t variable, you’ll see that the snake is being updated every 8 frames! The giveaway is how t is updated:

void draw() { 
  if (t == 8) {
    // Snake updates... length actually increases.
    t = 0;
    // ...
  }
t++;
// Head and food collide? Tail length increases.
}

So here’s what’s happening.

The head of the snake collides with the food. This causes the conditions to resize the snake and move the food to be met. But that happens up to 8 times before the snake moves again. But the snake’s tail only has positions added when the snake updates! So when you eat the food, suddenly your snake is 8 longer - not 1 - but none of those 8 tails positions have been added!

In short, there is a mismatch between tail - the supposed length of the snake’s tail - and the length of xt/yt - the actual number of tail segments there are. Knowing this, and with a bit of code reshuffle, we can fix the problem:

float cs = 20;
int hx = 0, hy = 0;
int s = 10, xs = 1, ys = 0;
int t;
int fx = floor(random(cs))*10, fy = floor(random(cs))*10;
int[] xt = {0}, yt = {0};
boolean grow = false;

void setup() {
  size(200, 200);
  background(0);
  stroke(24, 201, 46);
  fill(24, 201, 46);
}

void draw() {
  if (t == 8) {
    append(xt, hx);
    append(yt, hy);
    if(grow){
      append(xt, hx+xs*10);
      append(yt, hy+ys*10);
      grow = false;
    }
    hx+=xs*10;
    hy+=ys*10;
    if (hx >= 201) {
      hx = 0;
    }
    if (hx < 0) {
      hx = 200;
    }
    if (hy > 200) {
      hy = 0;
    }
    if (hy < 0) {
      hy = 200;
    }
    t = 0;
    background(0);
    rect(hx, hy, s, s);
    for (int i = xt.length-1; i > 0; i--) {
      xt[i] = xt[i-1];
      yt[i] = yt[i-1];
      rect (xt[i], yt[i], s, s);
    }
  }
  t++;
  if (hx == fx && hy == fy) {
    grow = true;
    fx = floor(random(cs))*10;
    fy = floor(random(cs))*10;
  }
  stroke(255, 0, 0);
  fill(255, 0, 0);
  rect(fx, fy, s, s);
  stroke(24, 201, 46);
  fill(24, 201, 46);
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP && ys != 1) {
      xs = 0;
      ys = -1;
    }
    if (keyCode == DOWN && ys != -1) {
      xs = 0;
      ys = 1;
    }
    if (keyCode == RIGHT && xs != -1) {
      xs = 1;
      ys = 0;
    }
    if (keyCode == LEFT && xs != 1) {
      xs = -1;
      ys = 0;
    }
  }
}

Works, right? Well, no. It no longer crashes. But now the tail doesn’t grow at all! Isn’t debugging fun?

2 Likes

So why doesn’t the tail grow now? Well, it never did, did it?

The problem is that append() returns an array - it doesn’t assign that array to anything. Basically, whoever wrote this didn’t use append() properly.

There are several other problems with this code as well. But I think the biggest problem, really, is that you ALREADY HAD this code. The best thing to do would be to take this code as an example and write a BETTER VERSION yourself, from scratch.

So let’s do that. Start with an empty sketch.

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

void draw(){
  background(0);
}

This code is already better than what you started with because it is 100% bug free & you understand it completely.

For most people, the next step is to try to write code. This is the wrong approach. For good coders, the next step is to break the large problem of “MAKE A SNAKE GAME” down into smaller steps. That’s better. But for once, we’re going to jump right into thinking with Portals. No wait, Objects. Now we’re thinking with Objects.

What sort of Objects would you need to have a snake game?

You need a snake.
You need food.
Anything else?
I can think of one that might be useful. A timer. That way we can avoid the weird t variable and how it was being used.

Let’s write those objects!

class Snake {
  // Maybe sme sort of way of storing body segments?
  int d; // Direction
  Snake() {
    d = 0;
  }
  void draw() {
  }
  void update() {
  }
  void grow() {
  }
  void onKeyPressed() {
  }
}

class Food {
  int x, y;
  Food() {
  }
  void draw() {
  }
  boolean wasEaten() {
    return false;
  }
}

class Timer {
  int time;
  int duration;
  Timer() {
    duration = 500;
    reset();
  }
  void reset() {
    time = millis() + duration;
  }
  boolean alarm() {
    if ( millis() > time ) {
      time = millis() + duration;
      return true;
    }
    return false;
  }
}

Why bother doing it this way?!?

This is why! Our main driving code becomes much easier to understand:

Snake snake;
Food food;
Timer timer;

void setup() {
  size(600, 400);
  snake = new Snake();
  food = new Food();
  timer = new Timer();
}

void draw() {
  background(0);
  if ( timer.alarm() ) {
    snake.update();
    if ( food.wasEaten() ) {
      snake.grow();
    }
  }
  food.draw();
  snake.draw();
}

void keyPressed() {
  snake.onKeyPressed();
}
2 Likes

Anyway, all you have to do next is fill in those classes.

This is what I came up with:

// Useful arrays to decode/use direction.
int[] dx = {1, 0, -1, 0};
int[] dy = {0, 1, 0, -1};
int[] dk = { RIGHT, DOWN, LEFT, UP };

class Snake {
  int x, y; // Current head position.
  ArrayList<PVector> tail; // Other segments.
  int d; // Direction of movement;
  boolean isGrowing = false;
  Snake() {
    d = 0;
    tail = new ArrayList();
  }
  void draw() {
    fill(50,200,50);
    stroke(150,200,50);
    rect(x,y,20,20);
    for( int i = 0; i < tail.size(); i++){
      rect(tail.get(i).x,tail.get(i).y,20,20);
    }
  }
  void update() {
    tail.add( new PVector(x,y,d) );
    if( isGrowing ){
      isGrowing = false;
    } else {
      tail.remove(0);
    }
    x += 20*dx[d];
    y += 20*dy[d];
    x += width;
    y += height;
    x %= width;
    y %= height;
  }
  void grow() {
    isGrowing = true;
  }
  void onKeyPressed() {
    for( int i = 0; i < dk.length; i++){
      if( key == CODED && keyCode == dk[i] ){
        d = i;
      }
    }
  }
}

class Food {
  int x, y;
  Food() {
    newPosition();
  }
  void draw() {
    fill(255,0,0);
    stroke(200,0,200);
    ellipse(x+10,y+10,20,20);
  }
  boolean wasEaten(int ox, int oy) {
    if ( x == ox && y == oy ) {
      newPosition();
      return true;
    }
    return false;
  }
  void newPosition() {
    x = 20 * int(random(width/20));
    y = 20 * int(random(height/20));
  }
}

class Timer {
  int time;
  int duration;
  Timer() {
    duration = 100;
    reset();
  }
  void reset() {
    time = millis() + duration;
  }
  boolean alarm() {
    if ( millis() > time ) {
      time = millis() + duration;
      return true;
    }
    return false;
  }
}

// --- Main driving code follows. Notice how simple its logic is to understand!

Snake snake;
Food food;
Timer timer;

void setup() {
  size(600, 400);
  snake = new Snake();
  food = new Food();
  timer = new Timer();
}

void draw() {
  background(0);
  if ( timer.alarm() ) {
    snake.update();
    if ( food.wasEaten( snake.x, snake.y ) ) {
      snake.grow();
    }
  }
  food.draw();
  snake.draw();
}

void keyPressed() {
  snake.onKeyPressed();
}

Using a dynamic structure (an ArrayList) for storing the body segments makes handling the tail a lot easier.

Anyway, this is a MUCH better snake game example to learn from.

2 Likes