<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="style.css">
  <script src="1.js" defer></script>
  <title>Document</title>
</head>
<body>
  <div class="snakeBoard">
    <canvas id="canvas" class="canvas">
      1111
    </canvas>
  </div>
</body>
</html>
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.snakeBoard {
   width: 100%;
   text-align:center;
}

.canvas{
  position: relative;
  border: 1px solid black;
  margin: 0 auto;
  margin-top: 50px;
  display: inline;
}

class Apples{
  constructor(board, amount){
    this.board = board;
    this.board.tests.push(this);
    this.amount = amount;
    this.list = [];
    this.reset();
  }
  reset(){
    this.clear();
    this.add(this.amount);
  }
  getPos(target){
    const list = this.list.filter(apple=>{
      if( target.x === apple.x &&  target.y === apple.y )
        return false;
      return true;
    });
    if( this.list.length === list.length ) 
      return {type: "empty", action: "move", position: target };;
    
    this.list = list;
    return {type: "apple", action: "eat", position: target };
  }   
  clear(){
    this.list = [];
  }
  draw(){
    this.list.forEach(apple=>{
      this.board.drawCeil(apple.x,apple.y,3,"#a44","#722");
    });    
  }
  add(amount){
    for(let i=0; i<amount; i++){
      let apple ={
        x: Math.floor(Math.random()*this.board.width),
        y: Math.floor(Math.random()*this.board.height)
      };

      
      const position = this.getPos(apple);
      console.log(position);
      if( position.type === "empty" )
        this.list.push(apple);
   
    }    
  }
}

class Snake{
  constructor(board,length){
    console.log("Snake.constructor 01")
    this.board = board;
    this.board.tests.push(this);
    this.body = [];
    this.direct = "left";
    this.length = length;
    this.reset();
    console.log("Snake.constructor 02")
  }
  reset(){
    this.clear();
    const x = this.board.width>>1;
    const y = this.board.height>>1;
    for(let i=0;i<this.length;i++){
      this.body.push({x:x+i,y:y})
    }
    // console.log("body 00:", JSON.stringify(this.body) );
  }
  getPos(target){
    const isEmpty = this.body.every(part=>{
      if( target.x === part.x &&  target.y === part.y )
        return false;
      return true;
    });
    if( isEmpty ) return false;
    
    return {type: "snake", action: "break", position: target };
  }  
  clear(){
    this.body = [];
  }
  draw(){
    this.body.forEach(part=>{
      this.board.drawCeil(part.x,part.y,3,"#4a4","#272");
    });    
  }
  setDirect(direct){
    this.direct = direct||this.direct;
  }
  next(){
    let head = this.body[0];
    let target = {
      x: ((this.direct=="left")?head.x-1:(this.direct=="right")?head.x+1:head.x),
      y: ((this.direct=="up")?head.y-1:(this.direct=="down")?head.y+1:head.y)
    }
    // console.log("direct",this.direct,head,target);
    if(this.direct)
      this.move(target);
  }
  move(target){
    const position = this.board.getPos(target);
    switch( position.action ){
      case "break":
        this.board.reset();
        this.board.pause();
        break;
      case "eat":
        this.body.unshift(target);
        this.board.apples.add(1);
        this.board.
        break;
      case "move":
        this.body.pop();
        this.body.unshift(target);
        break;
    }
  }
}

class Board {
  constructor(targetId, width=20, height=15, size=10){
    this.width = width;
    this.height = height;
    this.size = size;
    this.tests = [];


    this.canvas = document.getElementById(targetId);
    this.canvas.width = width * size + 1;
    this.canvas.style.width = width * size + 1 + "px";
    this.canvas.height = height * size + 1;
    this.canvas.style.height = height * size + 1 + "px";
    this.ctx    = this.canvas.getContext('2d');
    
    this.snake = new Snake(this, 3);
    this.apples = new Apples(this, 2);
    
    this.draw();
    this.isPaused = true;
    const self = this;
    this.timer = setInterval(()=>{
      self.next();
    },200);
  }
  play2pause(){
    if(this.isPaused){
      this.isPaused = false;
    }else{
      this.isPaused = true;
    }
      
  }
  pause(){
    this.isPaused = true;
  }
  play(){
    this.isPaused = false;
  }
  next(){
    if(this.isPaused) return;
    this.snake.next();
    this.draw();
  }
  reset(){
    this.tests.forEach(item=>{
      return item.reset();
    })    
    this.draw();
  }  
  clear(){
    this.tests.forEach(item=>{
      return item.clear();
    })    
    this.draw();
  }
  getPos(target){
    if(target.x<0 || target.x>=this.width )
      return {type: "border", action: "break", position: target };
    if(target.y<0 || target.y>=this.height )
      return {type: "border", action: "break", position: target };    
    
    const body = this.snake.getPos(target);
    if( body ) return body;

    const apple = this.apples.getPos(target);
    if( apple.type === "apple" ) return apple;

    return {type: "empty", action: "move", position: target };
  }  
  // отрисовывает ячейку по по заданым координатам
  // x  [number] - координата игрового поля по горизонтальной оси
  // y  [number] - координата игрового поля по горизонтальной оси
  // b  [number] - толщина границы в пикселях
  // fc [rgb] - цвет заливки
  // bc [rgb] - цвет границы
  drawCeil(x,y,b,fc,bc){
    this.ctx.fillStyle = fc;
    this.ctx.strokeStyle = bc;
    this.ctx.lineWidth = b;
    this.ctx.fillRect(x*this.size, y*this.size, this.size, this.size);
    this.ctx.strokeRect(x*this.size+0.5, y*this.size+0.5, this.size, this.size);
  }
  
  // перерисовывает все поле и его содержимое
  draw(){
    for( let y=0; y<this.height; y++ ){
    for( let x=0; x<this.width; x++ ){
      this.drawCeil(x,y,1,"#ddd","#aaa");
    }}
    this.tests.forEach(item=>{
      return item.draw();
    });

  }
}

const board = new Board("canvas",25,10,20);

  addEventListener('keydown', function(e){
    switch(e.keyCode){
      case 32:
        board.play2pause()
        break;
      case 38:
        board.snake.setDirect("up")
        break;
      case 39:
        board.snake.setDirect("right")
        break;
      case 40:
        board.snake.setDirect("down")
        break;
      case 37:
        board.snake.setDirect("left")
        break;
    }    
  })

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.