lib/game.js

import { MemoryImageList } from './image-list';
import { MemoryCanvaslist } from './canvas-list';
import { MemoryPairsList } from './pairs-list';
import { Counter } from './counter';

/**
 * @classdesc Controls main actions directly related to the game itself.
 * @class
 */
export class Game {
  /**
   * @summary Contruct
   * @param {Array} canvas List of canvas elements to be drawn image on.
   * @param {HTMLImageElement} cardBack Image element to be drown
   * as card back face.
   */
  constructor(canvas, cardBack) {
    // Event Listener Object. This is not actually used as DOM object.
    this.events = document.createElement('div');

    this.cardBack = cardBack;
    this.canvas = new MemoryCanvaslist();
    this.images = new MemoryImageList();
    this.pairs = new MemoryPairsList();

    this.canvas.addCanvas(canvas);
    this.state = [];
    this.counter = new Counter();
    this.prepared = false;

    this.cards = {
      open: 0,
      length: 0,
    };
  }

  /**
   * @summary Includes given image to the game.
   * @param {HTMLImageElement} img image to be added.
   */
  addImage(img) {
    this.images.push(img);

    if (this.images.length === this.canvas.length / 2) {
      this.prepare();
    }
  }
  /**
   * Prepares the Game.
   */
  prepare() {
    const self = this;

    self.cards.open = 0;
    self.cards.length = self.images.length;
    this.canvas.forEach((c) => {
      c.addEventListener('click', (e) => {
        if (this.counter.timer.diff === 0) {
          this.counter.start();
        }
        if (e.target.classList.contains('open')) {
          return;
        }
        if (!self.pairs.hasPair(e.target)) {
          const randomPair = self.canvas.randomPair(e.target);
          self.pairs.push(randomPair.concat(self.images.random()));
        }
        switch (self.state.length) {
          case 2: {
            self.closeCard(self.state.pop());
            self.closeCard(self.state.pop());
          }
          case 0: {
            self.state.push(e.target);

            self.openCard(e.target);
            break;
          }
          case 1: {
            self.state.push(e.target);
            self.openCard(e.target);
            if (self.pairs.arePairs(self.state)) {
              self.state = [];
              self.cards.open++;
              if (self.cards.open === self.cards.length) {
                self.counter.stop();
                const eventWin = new CustomEvent('win', {});
                self.events.dispatchEvent(eventWin);
              }
            }
            break;
          }

          default:
        }
      });
    });

    self.prepared = true;
    const delay = 1000 / self.canvas.length;
    self.canvas.forEach((c, i) => {
      setTimeout(() => {
        c.classList.remove('wait');
        self.closeCard(c);
      }, i * delay);
    });
    const eventReady = new CustomEvent('ready', {});
    self.events.dispatchEvent(eventReady);
  }

  /**
   * @summary Opens  card by drawing it's image on given canvas.
   * @param {HTMLCanvasElement} canvas Elment to be opened.
   */
  openCard(canvas) {
    const self = this;
    const img = self.pairs.getImage(canvas);

    if (img.constructor !== HTMLImageElement) {
      console.warn('Translate-error: no image for the canvas found ');
      return;
    }
    canvas.classList.add('open');
    self.drawImageOnCanvas(canvas, img);
  }
  /**
   * Closes card by drawing cardback image on given canvas.
   * @param {HTMLCanvasElement} canvas Canvas element to be closed.
   */
  closeCard(canvas) {
    const self = this;
    self.drawImageOnCanvas(canvas, self.cardBack);
    canvas.classList.remove('open');
  }
  /**
   * Draws any image on given canvas.
   * @param {HTMLCanvasElement} canvas target element to be drawn on to.
   * @param {HTMLImageElement} image Image drawn on.
   */
  drawImageOnCanvas(canvas, image) {
    const ctx = canvas.getContext('2d');
    ctx.restore();

    const props = {
      cw: canvas.width,
      ch: canvas.height,
      iw: image.width,
      ih: image.height,
    };

    ctx.clearRect(0, 0, props.cw, props.ch);
    ctx.drawImage(image, 0, 0, props.iw, props.ih, 0, 0, props.cw, props.ch);
  }
  /**
   * Calculates dimensions of next level.
   * @param {{}} options All attriburtes.
   * @return {string} Matrix of the next level.
   */
  nextLevel(options) {
    self = this;
    let col;
    let row;
    [col, row] = options.matrix.split('x').map((n) => parseInt(n));
    col === row ? col++ : row++;

    if (col > 10 || row > 10) {
      return '2x2';
    } else {
      return `${col}x${row}`;
    }
  }
}