Классическая игра-пятнашки на JavaScript и canvas.

HTML-разметка

Разметка самая простая — только один элемент canvas с одноименным идентификатором.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    canvas { border:2px solid #333; }
  </style>
  <script src="script.js"></script>
</head>
<body>
  <canvas id="canvas"></canvas>
</body>
</html>

Класс Game

Все действия будут происходить в файле script.js, где мы напишем код нашей игры.

Начнем с того, что создадим класс Game, который будет представлять собой Пятнашки в целом. Он будет получать в качестве параметров контекст рисования и размер одной пятнашки.

Для справки: ООП в JavaScript

Конструктор


function Game(context, cellSize){
  this.state = [
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,0]
  ];

  this.color = "#FFB93B";

  this.context = context;
  this.cellSize = cellSize;

  this.clicks = 0;
}

Game.prototype.getClicks = function() {
  return this.clicks;
};

В конструкторе определяем:

  • начальное состояние поля (this.state);
  • количество кликов, за которое пользователь прошел игру (this.clicks);
  • цвет заливки пятнашек (this.color).

Метод getClicks возвращает актуальное значение поля clicks. Он нам пригодится в конце игры, при выигрыше.

Отрисовка

Метод cellView получает координаты и рисует в них квадрат пятнашки заданного размера и цвета.

Game.prototype.cellView = function(x, y) {
  this.context.fillStyle = this.color;
  this.context.fillRect(
    x + 1, 
    y + 1, 
    this.cellSize - 2, 
    this.cellSize - 2
  );
};

Метод numView устанавливает стили для текста на пятнашках:

Game.prototype.numView = function() {
  this.context.font = "bold " + (this.cellSize/2) + "px Sans";
  this.context.textAlign = "center";
  this.context.textBaseline = "middle";
  this.context.fillStyle = "#222";
};

Метод draw отрисовывает всю игру:

Game.prototype.draw = function() {
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (this.state[i][j] > 0) {
        this.cellView(
          j * this.cellSize, 
          i * this.cellSize
        );
        this.numView();
        this.context.fillText(
          this.state[i][j], 
          j * this.cellSize + this.cellSize / 2,
          i * this.cellSize + this.cellSize / 2
        );
      }
    }
  }
};

Он проходится по всем клеткам игрового поля, получает значение каждой клетки из this.state, для каждой непустой клетки рисует квадрат (this.cellView), затем устанавливает стили для текста и рисует соответствующую цифру.

Перемещение

При клике на пятнашку будет вызван метод move, который должен переместить ее на пустую клетку.

Game.prototype.getNullCell = function(){
  for (let i = 0; i<4; i++){
    for (let j=0; j<4; j++){
      if(this.state[j][i] === 0){
        return {x: i, y: j};
      }
    }
  }
};

Game.prototype.move = function(x, y) {
  let nullCell = this.getNullCell();
  let canMoveVertical = (x - 1 == nullCell.x || x + 1 == nullCell.x) && y == nullCell.y;
  let canMoveHorizontal = (y - 1 == nullCell.y || y + 1 == nullCell.y) && x == nullCell.x;

  if (canMoveVertical || canMoveHorizontal) {
    this.state[nullCell.y][nullCell.x] = this.state[y][x];
    this.state[y][x] = 0;
    this.clicks++;
  }
};

Вспомогательный метод getNullCell возвращает позицию пустой клетки на поле.

Метод move сначала проверяет, находится ли клетка по указанным координатам рядом с пустой клеткой, то есть можно ли ее сдвинуть. Если можно, то он меняет местами две клетки и увеличивает количество ходов.

Проверка результата

Метод victory проверяет, сложены ли пятнашки правильно:

Game.prototype.victory = function() {
  let combination = [[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,0]];
  let res = true;
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (combination[i][j] != this.state[i][j]) {
        res = false;
        break;
      }
    }
  }
  return res;
};

Перемешивание поля

function getRandomBool() {
  if (Math.floor(Math.random() * 2) === 0) {
    return true;
  }
}

Game.prototype.mix = function(count) {
  let x, y;
  for (let i = 0; i < count; i++) {
    let nullCell = this.getNullCell();

    let verticalMove = getRandomBool();
    let upLeft = getRandomBool();

    if (verticalMove) {
      x = nullCell.x; 
      if (upLeft) {
        y = nullCell.y - 1;
      } else {
        y = nullCell.y + 1;
      }
    } else {
      y = nullCell.y; 
      if (upLeft) {
        x = nullCell.x - 1;
      } else {
        x = nullCell.x + 1;
      }
    }

    if (0 <= x && x <= 3 && 0 <= y && y <= 3) {
      this.move(x, y);
    }
  }

  this.clicks = 0;
};

Метод mix перемешивает пятнашки заданное количество раз. Он случайным образом выбирает клетку рядом с пустой и меняет их местами. Для выбора клетки используется вспомогательная функция getRandomBool.

Инициализация игры

Как только страница полностью загрузится, мы найдем на странице холст, создадим контекст рисования и инициализируем нашу игру.

window.onload = function(){
  let canvas = document.getElementById("canvas");
  canvas.width  = 320;
  canvas.height = 320;

  let context = canvas.getContext("2d");
  context.fillRect(0, 0, canvas.width, canvas.height);

  let cellSize = canvas.width / 4;

  let game = new Game(context, cellSize);
  game.mix(300);
  game.draw();
}

Обработка кликов

Осталось только добавить обработку события клика по пятнашке. Кроме клика мы будем поддерживать и событие касания, поэтому общую логику вынесем в отдельную функцию onEvent, которая будет получать координаты выбранной пятнашки:

canvas.onclick = function(e) {
  let x = (e.pageX - canvas.offsetLeft) / cellSize | 0;
  let y = (e.pageY - canvas.offsetTop)  / cellSize | 0;
  onEvent(x, y); 
};

canvas.ontouchend = function(e) {
  let x = (e.touches[0].pageX - canvas.offsetLeft) / cellSize | 0;
  let y = (e.touches[0].pageY - canvas.offsetTop)  / cellSize | 0;

  onEvent(x, y);
};  

function onEvent(x, y) { 
  game.move(x, y);
  context.fillRect(0, 0, canvas.width, canvas.height);
  game.draw();
  if (game.victory()) {
    alert("Собрано за "+game.getClicks()+" касание!"); 
    game.mix(300);
    context.fillRect(0, 0, canvas.width, canvas.height);
    game.draw(context, cellSize);
  }
}

Оператор побитового ИЛИ (|) здесь используется для округления полученного числа.

После выполнения хода, проверяем, не собраны ли пятнашки. Если собраны, то выводим сообщение.

Пятнашки готовы:

See the Pen Fifteen game by FurryCat (@mohnatus-the-lessful) on CodePen.

5 комментариев

  • Анон
    Анон
    17/04/2023, 15:23
    в конструкторе в тексте код задвоился: function Game(context, cellSize){ this.state = [ [1,2,3,4], function Game(context, cellSize){ this.state = [ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,0] ];
  • Никита
    Никита
    16/12/2020, 19:26
    Добрый вечер, подскажите, как можно добавить кнопки, которые будут менять цвет заливки пятнашек?
  • Татьяна
    Татьяна
    11/02/2020, 09:52
    не работают вроде, только картинка...
    • Furry_web
      Furry_web
      24/02/2020, 22:38
      Спасибо за внимательность :)

Оставить комментарий

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован