Изучите объектно-ориентированное программирование на JavaScript, создав тетрис (3)

В это время цель состоит в том, чтобы вы могли создать программу для перемещения мины, используя принципы ООП.

Вот ссылка на статьи из этой серии: Предыдущая статья / Следующая статья

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

function MinoGfx(color, blkpos8) {
  const _ctx = gFieldGfx.context2d;
  const _color = color;
  const _pxpos8 = [];
  for (let idx = 0; idx < 8; idx += 2) {
    _pxpos8[idx] = blkpos8[idx] * g.Px_BLOCK;
    _pxpos8[idx + 1] = blkpos8[idx + 1] * g.Px_BLOCK;
  }

  this.draw = () => {
    _ctx.fillStyle = _color;
    for (let idx = 0; idx < 8; idx += 2) {
      _ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
                    , g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
    }
  }
}

const t_mino = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
t_mino.draw();

При создании мино с помощью MinoGfx мы упростили указание формы мино. В случае создания T-mino мы можем прямо указать его форму как (1, 0), (0, 1), (1, 1), (2, 1). См. рис.1.

Как показано на рис. 2, Т-мино изображен зарытым в стене, так что переместите его, прежде чем рисовать.

В таком случае мы должны подумать о том, чтобы MinoGfx добавить функцию, которая перемещает мину. Программа будет следующей. В этой статье нужно добавить много программных фрагментов, поэтому я прикрепляю весь код в конце этой статьи.

function MinoGfx(color, blkpos8) {
  const _ctx = gFieldGfx.context2d;
  const _color = color;
  const _pxpos8 = [];

  // omitted...
  
  this.move = (dx, dy) => {
    for (let idx = 0; idx < 8; idx += 2) {
      _pxpos8[idx] += dx * g.Px_BLOCK;
      _pxpos8[idx + 1] += dy * g.Px_BLOCK;
    }
  }
}

Теперь давайте попробуем эту функцию. Как показано на рис.3, Т-мино будет нарисовано на четыре клетки вправо.

const t_mino = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
t_mino.move(4, 0);
t_mino.draw();

Если вы создаете o_mino, используя MinoGfx с добавленной функцией, вы, естественно, можете использовать новую функцию и в o_mino. Добавьте программу ниже и запустите ее, как показано на рис.4.

const o_mino = new MinoGfx('yellow', [1, 0, 2, 0, 1, 1, 2, 1]);
o_mino.move(7,0);
o_mino.draw();

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

Теперь, когда MinoGfx получил новую функцию для перемещения мины, давайте сделаем так, чтобы она могла перемещаться по экрану с помощью ввода с клавиатуры.

Вот что важно учитывать. Как мы обсуждали в прошлом выпуске, в ООП важно сначала рассмотреть, за что отвечает объект. В настоящее время у нас есть два functions: gFieldGfx, который рисует игровое поле, и MinoGfx, который рисует мино. Очевидно, что эти двое не должны отвечать за прием ввода с клавиатуры. Итак, подготовьте новый объект gGame для координации всей игры, включая прием ввода с клавиатуры.

При нажатии клавиши объект document уведомляется об этом. Если функция зарегистрирована с помощью document.onkeydown, она получает информацию о нажатой клавише. Учитывая эти вещи, gGame будет следующим.

const gGame = new function() {
  let _curMinoGfx = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
  _curMinoGfx.move(4, 0);
  _curMinoGfx.draw();

  document.onkeydown = (e) => {
    switch (e.key)
    {
      case 'ArrowLeft':
        _curMinoGfx.move(-1, 0);
        _curMinoGfx.draw();
        break;

      case 'ArrowRight':
        _curMinoGfx.move(1, 0);
        _curMinoGfx.draw();
        break;

      case 'ArrowDown':
        _curMinoGfx.move(0, 1);
        _curMinoGfx.draw();
        break;
    }
  }
}

Самостоятельный запуск программы — это кратчайший путь к пониманию ООП. Это требует некоторой работы, но, пожалуйста, запустите tetris.html в свой браузер и попробуйте.

Если вы переместите T-mino вниз, вы увидите экран, подобный показанному ниже.

Вот что бывает, когда не стираешь Ти-мино. Теперь давайте добавим функцию удаления мино в MinoGfx.

function MinoGfx(color, blkpos8) {
  const _ctx = gFieldGfx.context2d;
  const _color = color;
  const _pxpos8 = [];

  // omitted...
  
  this.erase = () => {
    _ctx.fillStyle = 'black';
    for (let idx = 0; idx < 8; idx += 2) {
      _ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
                      , g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
    }
  }
}

Измените gGame на стирание, а затем двигайтесь дальше.

const gGame = new function() {
  let _curMinoGfx= new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
  _curMinoGfx.move(4, 0);
  _curMinoGfx.draw();

  document.onkeydown = (e) => {
    switch (e.key)
    {
      case 'ArrowLeft':
        _curMinoGfx.erase();
        _curMinoGfx.move(-1, 0);
        _curMinoGfx.draw();
        break;

      case 'ArrowRight':
        _curMinoGfx.erase();
        _curMinoGfx.move(1, 0);
        _curMinoGfx.draw();
        break;

      case 'ArrowDown':
        _curMinoGfx.erase();
        _curMinoGfx.move(0, 1);
        _curMinoGfx.draw();
        break;
    }
  }
}

Внесите вышеуказанные изменения и запустите его снова.

Легче понять концепцию ООП, когда вы создаете программу самостоятельно. В ООП есть и другие важные концепции, и я постараюсь разъяснить их вам. Спасибо, что прочитали эту статью.

// tetris.js
'use strict';

const divTitle = document.createElement('div');
divTitle.textContent = "TETRIS";
document.body.appendChild(divTitle);

const g = {
  Px_BLOCK: 30,
  Px_BLOCK_INNER: 28,

  PCS_COL: 10,
  PCS_ROW: 20,
  PCS_FIELD_COL: 12,
}

const gFieldGfx = new function() {
  const pxWidthField = g.Px_BLOCK * g.PCS_FIELD_COL;
  const pxHeightField = g.Px_BLOCK * (g.PCS_ROW + 1);

  const canvas = document.createElement('canvas');        
  canvas.width = pxWidthField;
  canvas.height = pxHeightField;
  document.body.appendChild(canvas);

  const _ctx = canvas.getContext('2d');
  _ctx.fillStyle = "black";
  _ctx.fillRect(0, 0, pxWidthField, pxHeightField);

  const yBtmBlk = g.Px_BLOCK * g.PCS_ROW;
  const xRightBlk = pxWidthField - g.Px_BLOCK + 1;

  _ctx.fillStyle = 'gray';
  for (let y = 1; y < yBtmBlk; y += g.Px_BLOCK) {
    _ctx.fillRect(1, y, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
    _ctx.fillRect(xRightBlk, y, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
  }

  for (let x = 1; x < pxWidthField; x += g.Px_BLOCK) {
    _ctx.fillRect(x, yBtmBlk + 1, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
  }

  this.context2d = _ctx;
}

function MinoGfx(color, blkpos8) {
  const _ctx = gFieldGfx.context2d;
  const _color = color;
  const _pxpos8 = [];

  for (let idx = 0; idx < 8; idx += 2) {
    _pxpos8[idx] = blkpos8[idx] * g.Px_BLOCK;
    _pxpos8[idx + 1] = blkpos8[idx + 1] * g.Px_BLOCK;
  }

  this.draw = () => {
    _ctx.fillStyle = _color;
    for (let idx = 0; idx < 8; idx += 2) {
      _ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
                    , g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
    }
  }
  
  this.move = (dx, dy) => {
    for (let idx = 0; idx < 8; idx += 2) {
      _pxpos8[idx] += dx * g.Px_BLOCK;
      _pxpos8[idx + 1] += dy * g.Px_BLOCK;
    }
  }
  
  this.erase = () => {
    _ctx.fillStyle = 'black';
    for (let idx = 0; idx < 8; idx += 2) {
      _ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
                      , g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
    }
  }
}

const gGame = new function() {
  let _curMinoGfx = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
  _curMinoGfx.move(4, 0);
  _curMinoGfx.draw();

  document.onkeydown = (e) => {
    switch (e.key)
    {
      case 'ArrowLeft':
        _curMinoGfx.erase();
        _curMinoGfx.move(-1, 0);
        _curMinoGfx.draw();
        break;

      case 'ArrowRight':
        _curMinoGfx.erase();
        _curMinoGfx.move(1, 0);
        _curMinoGfx.draw();
        break;

      case 'ArrowDown':
        _curMinoGfx.erase();
        _curMinoGfx.move(0, 1);
        _curMinoGfx.draw();
        break;
    }
  }
}