Game Snake: fluidity problem

Hello,

I’m currently programming a snake game under processing and overall the game works with the most basic features: moving snakes that can be directed to the keyboard and I record positions already occupied that can not be occupied again.

My problem lies in the fluidity of the game.

Initially, FrameRate was 60 frames per second and snakes were moving px by px.

Then, I added a grid and I wish that the players move only on the grid (50 squares of 15px * 15px) ie without crossing the cells of the grid. So I made sure that the snakes move box by box (15px with each movement). I had to drop the FrameRate to 4 for it to be playable. But by doing this, the snakes move jerkily.

So I considered 2 solutions:

  • return to the displacement px by px by “delaying” the changes of direction when the player presses a key for the program wait until the snake has reached the end of a box to turn even if the player presses between 2 boxes

  • set up a ‘graphic transition’ totally independent of the movement of snakes. Basically, it would fill px by px the box that will be occupied by the snake at the same time as it moves on it (making a ‘jump’ of 15px so). I have tried to schematize this second option below.

I admit that I block and do not know which of the 2 solutions is the best or even how to implement them. I tried both without success.

Without giving me the code (obviously) I am taking advice to help me move forward.

Maybe you should post your code, so we can help you more precisely, but if i had to guess based on what you said, then you probably made some error during your changes. What you should do, as far as i can tell from your description, is go back to the movement of 1 pixel each step, and just add + 14 after the number… so if you have something like this :
moveUp();
void moveUp() {
snake.setPosition(snake.x, snake.y-1);
}
then just change that -1 into -15. There shouldn’t be any complex calculation, but if there is, then just change snake.y-(complexCalc)) to snake.y-(complexCalc)-14). That shouldn’t take more than adding 1, so i don’t really see where that frameloss could come from. Need more info (like some Code) for that. But for your second question, you should probably go with a smooth transition, but not using a delay while the box moves, unless you add some variable (maybe you have one already) to store the next direction for the snake. Because if you block input until the delay for the block movement is done, the next move will begin and input is blocked again. You’d have around a frame of time inbetween where you might be able to give input, but to hit that frame is pretty difficult, even if you keep pressing. In any case, even if you don’t want code as an answer, but only hints, you should still post some code so we can really know what is going on :slight_smile:.

Hello and thanks for replying
I think the frameRate is the problem
so if I can not increase the frameRate, px by px movement will always be jerky
for example, I add another square that move px by px below my snake that move 15px by 15px and the movement of the added square is jerky

so I think the best idea would be the first solution with a movement px by px, a frameRate(60) and delaying the change direction. For that, I need to save the position of the square, and more precisely the last grid corner exeeded by the snake. For example, the snake move from the left to right, and the UP key is pressed:

below the current code:

int tailleCase=15;
int NB_LIGNES = 50;
int NB_COLONNES = 50;
color ROUGE = color(255,0,0);
color BLEU = color(0,0,255);
color GREEN = color(0,255,0);
Snake snake1 = new Snake(195,345,ROUGE,tailleCase,0);
Snake snake2 = new Snake(555,345,BLEU,-tailleCase,0);
boolean[][] occupe = new boolean[52*tailleCase][52*tailleCase];

void setup(){
  frameRate(4);
  size(750,750);
  background(color(0));
  dessiner_grille();
}

void draw(){
  //println(frameCount);
  snake1.dessinerSnake();
  //snake2.dessinerSnake();
  snake1.deplacerSnake();
  //snake2.deplacerSnake();
  
  //snakeTransition1.dessiner();
  //snakeTransition1.deplacer();
  
  if(snake1.testCollision()){
    dessiner_grille();
    //background(BLEU);
    fill(0);
    noStroke();
    rect(200,350,352,60);
    fill(BLEU);   
    textSize(48);
    textAlign(CENTER, CENTER);
    text("Snake 2 win", 750/2, 750/2);
    snake1.xdep=0;
    snake1.ydep=0;
    noLoop();
  }
  
  if(snake2.testCollision()){
    dessiner_grille();
    //background(BLEU);
    fill(0);
    noStroke();
    rect(200,350,352,60);
    fill(ROUGE);
    textSize(48);
    textAlign(CENTER, CENTER);
    text("Snake 1 win", 750/2, 750/2);
    snake2.xdep=0;
    snake2.ydep=0;
    noLoop();
  }  
  
  occupe[snake1.xPos][snake1.yPos]=true;
  //occupe[snake2.xPos][snake2.yPos]=true;
}

void keyPressed() {
  
  if (key == 'z'){snake1.tournerHaut();}
  if (key == 's'){snake1.tournerBas();}
  if (key == 'q'){snake1.tournerGauche();}
  if (key == 'd'){snake1.tournerDroite();}
  //if (key == 'z'){snake1.xdep=0; snake1.ydep=-tailleCase;}
  //if (key == 's'){snake1.xdep=0; snake1.ydep=tailleCase;}
  //if (key == 'q'){snake1.xdep=-tailleCase; snake1.ydep=0;}
  //if (key == 'd'){snake1.xdep=tailleCase; snake1.ydep=0;}
  
  if (keyCode == UP){snake2.xdep=0; snake2.ydep=-tailleCase;}
  if (keyCode == DOWN){snake2.xdep=0; snake2.ydep=tailleCase;}
  if (keyCode == LEFT){snake2.xdep=-tailleCase; snake2.ydep=0;}
  if (keyCode == RIGHT){snake2.xdep=tailleCase; snake2.ydep=0;}

}

void temporisation() {
  try {
    Thread.sleep(30);
  } catch (Exception e) {
  }
}

void dessiner_grille() {
  int x0=0;
  int y0=0;
  for (int i=0; i<NB_LIGNES; i++) {
    for (int j=0; j<NB_COLONNES; j++) {
      stroke(255);                          //blanc
      strokeWeight(1);                      //largeur trait case
      fill(0);
      rect(x0+i*15, y0+j*15, 15, 15); 
    }
  }
}

class Snake {
  int xPos;
  int yPos;
  color c;
  int xdep;
  int ydep;
  int direction;

  Snake(int X, int Y, color C, int xD, int yD) {
    xPos = X;
    yPos = Y;
    c = C;
    xdep = xD;
    ydep = yD;
  }

  void dessinerSnake() {
    noStroke();
    fill(c);
    rect(xPos+1, yPos+1, tailleCase-1, tailleCase-1); 
  }

  void deplacerSnake() {
    println(frameCount);
    xPos+=xdep;
    yPos+=ydep;
    temporisation();
  }
  
  void tournerHaut(){
    xdep=0; 
    ydep=-tailleCase;
  }

  void tournerBas(){
    xdep=0; 
    ydep=tailleCase;
  }
  
  void tournerDroite(){
    xdep=tailleCase; 
    ydep=0;
  }
  
  void tournerGauche(){
    xdep=-tailleCase; 
    ydep=0;
  }
  
  int position_precedente() { 
    int p=xPos;
    //println(p);
    return p;
  }
 

  boolean testCollision() {
    if (xPos<=0 || xPos>750 || yPos<=0 || yPos>750) {
      return true;
    }
    if (occupe[xPos][yPos]) {
      return true;
    }

    return false;
  }
}

Uhm ok, i‘ll get on my PC and try to adjust THe code a bit, because i just can‘t imagine that Snake can only run with 4 frames ( a Game that is older than the Internet). Should take around 30 min, Max an Hour if i cant find anything. I‘ll Update this Reply once i got it^^

First of all, i made some changes to your code to make it easier to read and use, so here it is:

final int GridSize = 50; // sets the size of the Grid (50*50)
final int size = 15; // sets the size of the snake and the grid (you could also
//make it into a PVector to have the size of each grid be width 10 height 20 for example and same for GridSize
final color RED = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE = color(0, 0, 255);
//i removed the xD from Snake so just let both start by going upwards. Should save a lot of trouble
Snake snake1 = new Snake(195, 345, RED);
Snake snake2 = new Snake(555, 345, BLUE);
boolean[][] occupied = new boolean[50][50];

void setup() {
  size(750, 750);
  background(0);
  createGrid();
  frameRate(4);
}

void draw() {
  snake1.drawSnake();
  snake2.drawSnake();
  snake1.moveSnakeForward();
  snake2.moveSnakeForward();
  if (snake1.testCollision()) {
    win(2);
  }

  if (snake2.testCollision()) {
    win(1);
  }

  occupied[(int)snake1.pos.x/size][(int)snake1.pos.y/size] = true;
  occupied[(int)snake2.pos.x/size][(int)snake2.pos.y/size] = true;
}

void win(int i) {
  createGrid();
  fill(0);
  noStroke();
  rect(200, 350, 352, 60);
  fill(i == 1 ? RED : i == 2 ? BLUE : 0);
  textSize(48);
  textAlign(CENTER, CENTER);
  text("Snake " + i + " win", width/2, height/2);
  if (i == 1) {
    snake1.dir = new PVector(0, 0);
  } else if (i == 2) {
    snake2.dir = new PVector(0, 0);
  }
  noLoop();
}

void keyPressed() {

  if (key == 'z') {
    snake1.turnUp();
  }
  if (key == 's') {
    snake1.turnDown();
  }
  if (key == 'q') {
    snake1.turnLeft();
  }
  if (key == 'd') {
    snake1.turnRight();
  }

  if (keyCode == UP) {
    snake2.turnUp();
  }
  if (keyCode == DOWN) {
    snake2.turnDown();
  }
  if (keyCode == LEFT) {
    snake2.turnLeft();
  }
  if (keyCode == RIGHT) {
    snake2.turnRight();
  }
}

void temporisation() {
  try {
    Thread.sleep(30);
  } 
  catch (Exception e) {
  }
}

void createGrid() {
  for (int x = 0; x < GridSize; x++) { // if you changed GridSize to PVector, just use GridSize.x
    for (int y = 0; y < GridSize; y++) {//and here GridSize.y
      stroke(255);
      fill(0);
      rect(x*size, y*size, size, size);
    }
  }
}

class Snake {
  PVector pos;
  PVector dir;
  color c;

  Snake(int x, int y, color col) {
    pos = new PVector(x, y);
    dir = new PVector(0, -size);
    c = col;
  }

  void drawSnake() {
    noStroke();
    fill(c);
    rect(pos.x, pos.y, size, size);
  }

  void moveSnakeForward() {
    pos = new PVector(pos.x + dir.x, pos.y + dir.y);
    temporisation();
  }

  void turnUp() {
    dir = new PVector(0,-size);
  }

  void turnDown() {
    dir = new PVector(0, size);
  }

  void turnLeft() {
    dir = new PVector(-size, 0);
  }

  void turnRight() {
    dir = new PVector(size, 0);
  }

  boolean testCollision() {
    if (pos.x <= 0 || pos.x > 750 || pos.y <= 0 || pos.y > 750) {
      return true;
    }
    if (occupied[(int)pos.x/size][(int)pos.y/size]) {
      return true;
    }
    return false;
  }
}

I’ll also send you one version where the movement is per pixel, so that it looks better :

final int GridSize = 50; // sets the size of the Grid (50*50)
final int size = 15; // sets the size of the snake and the grid (you could also
//make it into a PVector to have the size of each grid be width 10 height 20 for example and same for GridSize
final color RED = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE = color(0, 0, 255);
//i removed the xD from Snake so just let both start by going upwards. Should save a lot of trouble
Snake snake1 = new Snake(195, 345, RED);
Snake snake2 = new Snake(555, 345, BLUE);
boolean[][] occupied = new boolean[50][50];

void setup() {
  size(750, 750);
  background(0);
  createGrid();
  frameRate(60);
}

void draw() {
  snake1.drawSnake();
  snake2.drawSnake();
  snake1.moveSnakeForward();
  snake2.moveSnakeForward();

  if (frameCount % 15 == 0) {

    snake1.updateNextDir();
    snake2.updateNextDir();

    if (snake1.testCollision()) {
      win(2);
    }

    if (snake2.testCollision()) {
      win(1);
    }

    occupied[(int)snake1.pos.x/size][(int)snake1.pos.y/size] = true;
    occupied[(int)snake2.pos.x/size][(int)snake2.pos.y/size] = true;
  }
}

void win(int i) {
  createGrid();
  fill(0);
  noStroke();
  rect(200, 350, 352, 60);
  fill(i == 1 ? RED : i == 2 ? BLUE : 0);
  textSize(48);
  textAlign(CENTER, CENTER);
  text("Snake " + i + " win", width/2, height/2);
  if (i == 1) {
    snake1.dir = new PVector(0, 0);
  } else if (i == 2) {
    snake2.dir = new PVector(0, 0);
  }
  noLoop();
}

void keyPressed() {

  if (key == 'z') {
    snake1.turnUp();
  }
  if (key == 's') {
    snake1.turnDown();
  }
  if (key == 'q') {
    snake1.turnLeft();
  }
  if (key == 'd') {
    snake1.turnRight();
  }

  if (keyCode == UP) {
    snake2.turnUp();
  }
  if (keyCode == DOWN) {
    snake2.turnDown();
  }
  if (keyCode == LEFT) {
    snake2.turnLeft();
  }
  if (keyCode == RIGHT) {
    snake2.turnRight();
  }
}


void temporisation() {
  try {
    Thread.sleep(30);
  } 
  catch (Exception e) {
  }
}

void createGrid() {
  for (int x = 0; x < GridSize; x++) { // if you changed GridSize to PVector, just use GridSize.x
    for (int y = 0; y < GridSize; y++) {//and here GridSize.y
      stroke(255);
      fill(0);
      rect(x*size, y*size, size, size);
    }
  }
}

class Snake {
  PVector pos;
  PVector dir;
  PVector nextDir;
  color c;

  Snake(int x, int y, color col) {
    pos = new PVector(x, y);
    dir = new PVector(0, -1);
    c = col;
  }

  void drawSnake() {
    noStroke();
    fill(c);
    rect(pos.x, pos.y, size, size);
  }

  void moveSnakeForward() {
    pos = new PVector(pos.x + dir.x, pos.y + dir.y);
    temporisation();
  }

  void turnUp() {
    if (frameCount % 15 == 0) {
      dir = new PVector(0, -1);
    } else {
      nextDir = new PVector(0, -1);
    }
  }

  void turnDown() {
    if (frameCount % 15 == 0) {
      dir = new PVector(0, 1);
    } else {
      nextDir = new PVector(0, 1);
    }
  }

  void turnLeft() {
    if (frameCount % 15 == 0) {
      dir = new PVector(-1, 0);
    } else {
      nextDir = new PVector(-1, 0);
    }
  }

  void turnRight() {
    if (frameCount % 15 == 0) {
      dir = new PVector(1, 0);
    } else {
      nextDir = new PVector(1, 0);
    }
  }

  void updateNextDir() {
    if (nextDir != null) {
      dir = nextDir;
      nextDir = null;
    }
  }

  boolean testCollision() {
    if (pos.x <= 0 || pos.x > 750 || pos.y <= 0 || pos.y > 750) {
      return true;
    }
    if (occupied[(int)pos.x/size][(int)pos.y/size]) {
      return true;
    }
    return false;
  }
}

The changes are in the new nextDir PVector, the turnUp/Down and so on methods, the new updateNextDir() method changing where size was before into 1 and the frameRate(60) and frameCount % 15 == 0 addition. Took a bit longer than expected :sweat_smile:

Btw, if you cant find the differences, just use an online difference checker. Just search for “find difference between two texts online”. Used it to find what i changed :sweat_smile:

Hope it helps :wink: