Snake game (Игра змейка), на чистом javascript



В очередной раз проснулся во мне старый игрофил. И решил я запилить змейку на чистейшем Javascript'е. Времени это заняло не долго, примерно 2-2.5 часа, включая перекуры.

Вот сама игра на отдельное странице, а ниже немного подробностей как и что устроено.

В этот раз я решил не страдать хернёй с рендерингом в чистый html, а заюзал canvas. Весь канвас делится на кватраты, которые могут принимать несколько фиксированных значений: ничего, голова змейки, тушка змейки, еда.

const rectStyles = [
  {
    id: 0,
    type: 'Background',
    stroke: 'rgb(25, 25, 25)',
    fill: 'rgb(15, 15, 15)'
  },
  {
    id: 1,
    type: 'Snake head',
    stroke: 'rgb(250, 250, 250)',
    fill: 'rgb(220, 220, 220)'
  },
  {
    id: 2,
    type: 'Snake body',
    stroke: 'rgb(200, 200, 200)',
    fill: 'rgb(180, 180, 180)'
  },
  {
    id: 3,
    type: 'Food',
    stroke: 'rgb(250, 250, 0)',
    fill: 'rgb(230, 230, 0)'
  }
]

function createDisplay(width=15, height=15) {
  for(let w=0; w<width; w++) {
    for(let h=0; h<height; h++) {
      if( !display[ h ] )
        display[ h ] = [];
      
      display[ h ][ w ] = {
        h: h,
        w: w,
        id: 0,
        type: 0
      };
    }
  }
}

function clearDisplay() {
  for(let h=0; h<display.length; h++)
    for(let w=0; w<display[ 0 ].length; w++)
      display[ h ][ w ].type = 0;
}

function render(element) {
  let ctx = element.getContext('2d');
  
  ctx.canvas.width = ctx.canvas.clientWidth;
  ctx.canvas.height = ctx.canvas.clientHeight;
  
  let rectWidth = parseInt(ctx.canvas.width / display[ 0 ].length - 1);
  let rectHeight = parseInt(ctx.canvas.height / display.length - 1);
  
  for(let h=0; h<display.length; h++)
    for(let w=0; w<display[ h ].length; w++) {
      let left = w * rectWidth + w;
      let top = h * rectHeight + h;
      
      ctx.strokeStyle = rectStyles[ display[ h ][ w ].type ].fill;
      ctx.fillStyle = rectStyles[ display[ h ][ w ].type ].stroke;
      
      ctx.fillRect(left, top, rectWidth, rectHeight);
      ctx.strokeRect(left, top, rectWidth, rectHeight);
    }
}

Интересно получилось с генератором еды. Так как диспей у нас- это двумерный массив, то мы не можем просто взять случайное число и по нему поместить еду на дисплей, клетки могут быть заняты.

По этому я сперва создаю одномерный массив со ссылками на свободные клетки дисплея, уже потом беру случайное число, узнаю координаты клетки, и создаю объект "еда" с новыми координатами.

function createFood() {
  let emptyRects = [];
  for(let h=0; h<display.length; h++)
    for(let w=0; w<display[ 0 ].length; w++)
      if( display[ h ][ w ].type == 0 )
        emptyRects.push(display[ h ][ w ]);
  
  let foodRect = emptyRects[ getRandomIntInclusive(0, emptyRects.length-1) ];
  
  return {
    x: foodRect.w,
    y: foodRect.h,
    type: 1
  }
}

Перерисовка сцены и пересчёт змейки происходит по таймеру, и в таймере же задаётся скорость игры, которая возрастает каждые 25 ходов.

function tick() {
  if( gameStatus !== 'game' ) return; // game paused
  renderIndex ++;
  if( renderIndex > speedUpLimit  ) {
    renderIndex =0;
    gameSpeed ++;
    clearInterval(gameTimer);
    gameTimer = setInterval(tick, 1000 - (gameSpeed * 50));
  }
  
  clearDisplay();
  renderFood();
  renderSnake();
  render(viewPort);
  
  if( onTick ) onTick();
}
...
function startGame(onTickHandler) {
  snake = createSnake();
  food = createFood();
  gameStatus = 'game';
  onTick = onTickHandler;
  gameTimer = setInterval(tick, 1000 - (gameSpeed * 50));
}

В остальном код очень простой. Полностью в функциональном стиле. Открывайте исходник страницы, смотрите что там и как.

Ну и конечно играйте с удовольствием!


Сообщество: AWS

Комментариев(0)


Всего: 0 комментариев на 0 страницах

Ваш комментарий будет анонимным. Чтобы оставить не анонимный комментарий, пожалуйста, зарегистрируйтесь



Сообщества