Perlin Snake - snake game variation with noise based obstacles

This is a snake game variation, creating obstacles with the Processing noise function.

//License:CC BY-NC-SA 4.0
//https://creativecommons.org/licenses/by-nc-sa/4.0/

//Fonts: 
//JuliaMono Typeface (by cormullion)
//Kobata (by Ariel Martin Perez)
//www.fontesk.com

PFont mono;
PFont kobata;
Snake snake;
Grid grid;
Table highscore;
boolean lost = false;
int levelno=1;
int points=0;
String playername="";
int state = 0;
//variable parameters
PVector[] foodloc;
int foodamount = 5;//amount of food
int itemsize =12; //size of food and width of snake
int snakelength = 200; //startsize and growth after food uptake
int speed = 1; //speed of snake
int edge; //frame around play area

void setup(){
  size(700, 700);
  edge = 50;
  mono = createFont("JuliaMono-Medium.ttf", 32);
  kobata = createFont("Kobata-Regular.otf", 32);
  snake = new Snake();
  grid = new Grid(itemsize);
}

void draw(){
  if (frameCount == 1){
    startscreen();
  }
  background(125);
  textFont(mono);
  //Top display
  textSize(20);
  fill(168, 34, 38);
  textAlign(LEFT);
  text("Level: "+levelno, edge-10, 30);
  textAlign(RIGHT);
  text("Points: "+points, width-(edge-10), 30);
  
  strokeWeight(20);
  stroke(168, 34, 38);
  fill(255);
  rectMode(CORNER);
  rect(edge,edge,float(width-2*edge), float(height-2*edge));
  
  grid.display();
  
  fooddisplay();
  
  snake.display();
  snake.move();
  snake.collisioncheck();
  
  if (lost == true) {
    endl();
  }
  
  if (foodloc.length == 0) {
    endw();
  }
  
  control();
  if (frameCount == 1){
    startscreen();
  }
}

void control(){
  if (keyPressed == true && keyCode == DOWN && snake.move.y != -speed){
    snake.move.x = 0;
    snake.move.y = speed;
  }
  if (keyPressed == true && keyCode == UP && snake.move.y != speed){
    snake.move.x = 0;
    snake.move.y = -speed;
  }
  if (keyPressed == true && keyCode == LEFT && snake.move.x != speed){
    snake.move.x = -speed;
    snake.move.y = 0;
  }
  if (keyPressed == true && keyCode == RIGHT && snake.move.x != -speed){
    snake.move.x = speed;
    snake.move.y = 0;
  }
}
  void keyPressed() {
    if (keyPressed == true && (key == 'C' || key == 'c') && foodloc.length == 0 ){
      //start of new level
      levelno++;
      if (levelno < 4){
        grid.limit -= 0.5;
      } else {
          grid.limit -= 0.25;
        }
      snake.startcoord();
      grid.coordinates();
      food();
      loop();
  }
  if (keyPressed == true && (key == 'S' || key == 's' & frameCount == 2)){
        loop();
  }
  if (lost == true && highscore.getInt(4, "Points") < points && key==ENTER || key == RETURN){
   state=1; 
  } else {
    if (lost == true){
      playername += key;
    }
  }
  if (keyPressed == true && key == 'H' || key =='h' & lost == true){
    frameCount = 1;
    loop();
  }
  }
      



  void endl () {
    snake.move.x = 0;
    snake.move.y = 0;
    fill(255);
    rectMode(CENTER);
    rect (float(width/2),float(height/2), width-(2*edge+20), 150);
    textSize(50);
    textAlign(CENTER,CENTER);
    fill (166,3,19);
    text ("Game over", float(width/2), float(height/2)-20);
    if (highscore.getInt(4, "Points") < points){
      switch(state){
        case(0):
          textSize(15);
          text("Enter your name", float(width/2), float(height/2+20));
          text(playername,float(width/2), float(height/2+40));
          break;
        
        case(1):
          highscore.addRow();
          highscore.setString(highscore.getRowCount()-1,"Name", playername);
          highscore.setInt(highscore.getRowCount()-1,"Level", levelno);
          highscore.setInt(highscore.getRowCount()-1,"Points", points);
          highscore.sortReverse("Points");
          highscore.removeRow(highscore.getRowCount()-1);
          saveTable(highscore, "plhs.csv");
          textSize(15);
          text("Press h for homescreen",float(width/2), float(height/2+30));
          noLoop();
          break;
      }
    } else {
      textSize(15);
      text("Press h for homescreen",float(width/2), float(height/2+30));
      noLoop();
    }
  }
  
  void endw () {
    snake.move.x = 0;
    snake.move.y = 0;
    fill(255);
    rectMode(CENTER);
    rect (float(width/2),float(height/2+50), width-(2*edge+20), 250);
    textSize(50);
    textAlign(CENTER,CENTER);
    fill (166,3,19);
    text ("Level finished", float(width/2), float(height/2));
    textSize(15);
    text ("Press c, if you are ready for the next level.", float(width/2), float(height/2+50));
    noLoop();
  }
  
  void food() {
    foodloc = new PVector[0];
    PVector f;
    boolean ftest = true;
    for (int i = 0; foodloc.length < foodamount; i++){
      f = new PVector (int(map(random(1),0,1,edge+1.5*itemsize,width-edge-1.5*itemsize)), int(map(random(1),0,1,edge+1.5*itemsize,height-edge-1.5*itemsize)));
      //this checks that the food is not on the same coordinate as the walls
      for (int k=0; k < grid.walls.length; k++){
        if (abs(grid.walls[k].x-f.x) <= itemsize && abs(grid.walls[k].y-f.y) <= itemsize ) {         
              ftest = false;
              k=grid.walls.length-1;
        }
      }
      //this checks that the food is not on the same coordinate as other food
      for (int fl=0; fl < foodloc.length; fl++){
        if (abs(foodloc[fl].x-f.x) <= itemsize && abs(foodloc[fl].y-f.y) <= itemsize ) {         
              ftest = false;
              fl=foodloc.length-1;
        }  
      }
      //only if both tests above do not find a match the new coordinate is added
      if (ftest == true){
        foodloc = (PVector[])append(foodloc,f);
      } else {
        ftest = true;
      }
    }
  }
  
  void fooddisplay(){
  fill(254,190,20);
  rectMode(CENTER);
  for (PVector i : foodloc) {
    noStroke();
    square(i.x,i.y, itemsize);
  }  
}
  
  void startscreen(){
   //reset of variables
   lost = false;
   levelno=1;
   points=0;
   playername="";
   state = 0; 
   //start of new games
   snake.startcoord();
   grid.coordinates();
   food();
   highscore = loadTable("plhs.csv","header");
   highscore.setColumnType("Points", Table.INT);
   highscore.setColumnType("Level", Table.INT);
   
   background(255);
   fill(168, 34, 38);
   textFont(kobata);
   textSize(100);
   textAlign(CENTER);
   text("Perlin Snake", width/2, height/4);
   textSize(10);
   textFont(mono);
   text("Press s to start", width/2, height*6/8);
   text("Highscores", width/2, height*9/24);
   
   textSize(20);
   textAlign(LEFT);
   text("Name", width*5/18, height*11/24);
   textAlign(CENTER);
   text("Level", width*10/18, height*11/24);
   text("Points", width*13/18, height*11/24);
   for(int i=0; i < highscore.getRowCount(); i++){
     TableRow row = highscore.getRow(i);
       textAlign(LEFT);
       text(row.getString("Name"), width*5/18, height*12/24+i*20);
       textAlign(CENTER);
       text(row.getInt("Level"), width*10/18, height*12/24+i*20);
       text(row.getInt("Points"), width*13/18, height*12/24+i*20);
   }
   
   textAlign(LEFT);
   textSize(10);
   text("Fonts: \nJuliaMono Typeface (cormullion) \nKobata (Ariel Martin Perez) \nwww.fontesk.com", 50, height*7/8);
   noLoop();
  }
  
class Grid {
  int gridsize;
  float elevation;
  float limit = 8; //limit for wall generation 10 = no wall
  PVector[] walls;
  
  Grid(int tempgridsize){
    gridsize = (width-2*edge)/(tempgridsize);
    elevation=0;
    } 
  
  void coordinates(){
    noiseSeed(int(random(100)));
    walls = new PVector[0];
    PVector w = new PVector(0,0);
    //starts at one. because first coordinate should not be 0+edge
    //goes to gridize-1 because last values would be already out of boundaries of play area
      for (int i=1; i < gridsize-1; i ++) {
        for (int j=1; j < gridsize-1; j ++) {
          elevation = map(noise (i/1.5,j/1.5),0,1,0,10);
          if (elevation > limit){
            w = new PVector(i*itemsize+edge+itemsize/2, j*itemsize+edge+itemsize/2);//+itemsize/2 to avoid overlap with edge
            //keeps area around snake free
            if (w.x < snake.location[0].x-snakelength
              ||
              (w.y < snake.location[0].y-2*itemsize || w.y > snake.location[0].y+2*itemsize)
              ) {
              walls = (PVector[]) append(walls,w);
            }
          }
        }
      }
  } 
  
  void display(){
    strokeWeight(itemsize);
    stroke(168, 34, 38);
    strokeCap(PROJECT);
    for (PVector i : walls){
      point(i.x,i.y);
    }
  }
}

class Snake{
  PVector[] location;
  PVector move = new PVector();
  
  Snake(){
    }
  
  void startcoord(){
    location = new PVector[snakelength];
    move.set(1,0);
    location[0] = new PVector (int(map(random(1),0,1,edge+snakelength,3*width/4)),int(map(random(1),0,1,height/4,3*height/4)));
    for (int i=1; i < location.length; i++) {
      location[i] = new PVector (location[i-1].x-1, location[i-1].y);
      }
  }
  
  void display(){
    noStroke();
    fill(23,115,69);
    for (PVector i : location) {
      rectMode(CENTER);
      square(i.x,i.y, itemsize);
    }
  }
  
  void move() {
    if (lost == false){
      for (int i=location.length-1; i > 0 ;i--) {
        location[i].x = location[i-1].x;
        location[i].y = location[i-1].y;
      }
        location[0].add(move);
    }
  }
  
  void collisioncheck(){
    //wall
    if (location[0].x < edge+itemsize || location[0].x > width-(edge+itemsize)) { //strokeWeight wall and background
      lost = true;
    }
    if (location[0].y < edge+itemsize || location[0].y > height-(edge+itemsize)) { //strokeWeight wall and background
      lost = true;
    }
    //snake
    for (int i=1; i < location.length; i++) {
      if (location[0].x == location[i].x && location[0].y == location[i].y){
        lost =true;
      }
    }
    //grid
    for (PVector k : grid.walls){
      if (k.dist(location[0]) < itemsize*0.75){
        lost = true;
      }
    }
    
    
    
    //food
    for (int i=0; i < foodloc.length; i++) {
      float xd = foodloc[i].dist(location[0]); 
      if (xd < itemsize*0.75){
          location = (PVector[])expand(location,location.length+snakelength);
          points++;
          for (int k=location.length-1; k > location.length-(snakelength+1) ;k--){
        location[k] = new PVector(-100,-100);
      }
    //remove food
    PVector[] foodloctemp;
    foodloctemp = new PVector[0];
    for (int j = 0; j < foodloc.length; j++){
           if(j < i) {
             foodloctemp = (PVector[])append(foodloctemp,foodloc[j]);
           } else {
            if (j > i) {
              foodloctemp = (PVector[]) append(foodloctemp,foodloc[j]);
            }
            }
           }
           foodloc = foodloctemp;
      }
    }
  }
}
1 Like