shapers_JigSaw.js

/**
 *  File    : shapers/JigSaw.js
 *  Created : 01/04/2015
 *  By      : Francesc Busquets <francesc@gmail.com>
 *
 *  JClic.js
 *  An HTML5 player of JClic activities
 *  https://projectestac.github.io/jclic.js
 *
 *  @source https://github.com/projectestac/jclic.js
 *
 *  @license EUPL-1.2
 *  @licstart
 *  (c) 2000-2020 Educational Telematic Network of Catalonia (XTEC)
 *
 *  Licensed under the EUPL, Version 1.1 or -as soon they will be approved by
 *  the European Commission- subsequent versions of the EUPL (the "Licence");
 *  You may not use this work except in compliance with the Licence.
 *
 *  You may obtain a copy of the Licence at:
 *  https://joinup.ec.europa.eu/software/page/eupl
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 *  Licence for the specific language governing permissions and limitations
 *  under the Licence.
 *  @licend
 *  @module
 */

import Shaper from './Shaper';
import { Path, PathStroke } from '../AWT';

/**
 *
 * This {@link module:shapers/Shaper.Shaper Shaper} returns a set of rectangular shapes with teeth and slots that fit between them.
 * @extends module:shapers/Shaper.Shaper
 */
export class JigSaw extends Shaper {
  /**
   * JigSaw constructor
   * @param {number} nx - Number of columns
   * @param {number} ny - Number of rows
   */
  constructor(nx, ny) {
    super(nx, ny);
  }

  /**
   * Builds the jigsaw shapes based on the number of rows and columns
   * @override
   */
  buildShapes() {
    // Create two two-dimension arrays for storing the type of horizontal and vertical lines
    let hLineType = [], vLineType = [];
    for (let i = 0; i <= this.nRows; i++) {
      hLineType[i] = [];
      vLineType[i] = [];
    }

    for (let row = 0; row < this.nRows; row++) {
      for (let col = 0; col < this.nCols; col++) {
        hLineType[row][col] = row === 0 ? 0 : 1 + (this.randomLines ? Math.round(Math.random() * 9) : row + col) % 2;
        vLineType[row][col] = col === 0 ? 0 : 1 + (this.randomLines ? Math.round(Math.random() * 9) : col + row + 1) % 2;
        if (col === this.nCols - 1)
          vLineType[row][col + 1] = 0;
        if (row === this.nRows - 1)
          hLineType[row + 1][col] = 0;
      }
    }

    const w = 1 / this.nCols, h = 1 / this.nRows;
    for (let r = 0; r < this.nRows; r++) {
      for (let c = 0; c < this.nCols; c++) {
        const x = w * c;
        const y = h * r;
        const sd = new Path([new PathStroke('M', [x, y])]);
        this.hLine(sd, hLineType[r][c], x + 0, y + 0, w, h, false);
        this.vLine(sd, vLineType[r][c + 1], x + w, y + 0, w, h, false);
        this.hLine(sd, hLineType[r + 1][c], x + w, y + h, w, h, true);
        this.vLine(sd, vLineType[r][c], x + 0, y + h, w, h, true);
        sd.addStroke(new PathStroke('X'));
        sd.calcEnclosingRect();
        // Save the Path in `shapeData`
        this.shapeData[r * this.nCols + c] = sd;
      }
    }
    this.initiated = true;
  }

  /**
   * Adds an horizontal line to the provided path
   * @param {module:AWT.Path} sd - The Path to which the line will be added
   * @param {number} type - Type  of tooth: 0 is flat (no tooth), 1 means tooth up, and 2 means tooth down
   * @param {number} x - X coordinate of the starting point
   * @param {number} y - Y coordinate of the starting point
   * @param {number} w - Width of the piece
   * @param {number} h - Height of the piece
   * @param {boolean} inv - The line must be drawn right to left
   */
  hLine(sd, type, x, y, w, h, inv) {
    const
      kx = inv ? -1 : 1,
      ky = type === 1 ? 1 : -1;

    if (type === 0)
      // Flat line
      sd.addStroke(new PathStroke('L', [x + w * kx, y]));
    else {
      const x0 = x + (w - w * this.baseWidthFactor) / 2 * kx;
      const wb = w * this.baseWidthFactor * kx;
      // Approximation to the tooth:
      sd.addStroke(new PathStroke('L', [x0, y]));
      // The tooth:
      const hb = h * this.toothHeightFactor * ky;
      sd.addStroke(new PathStroke('L', [x0, y + hb]));
      sd.addStroke(new PathStroke('L', [x0 + wb, y + hb]));
      sd.addStroke(new PathStroke('L', [x0 + wb, y]));
      // Draw the remaining of the line
      sd.addStroke(new PathStroke('L', [x + w * kx, y]));
    }
  }

  /**
   *
   * Adds a vertical line to the provided path
   * @param {module:AWT.Path} sd - The Path to which the line will be added
   * @param {number} type - Type  of tooth: 0 is flat (no tooth), 1 means tooth right, and 2 means tooth left
   * @param {number} x - X coordinate of the starting point
   * @param {number} y - Y coordinate of the starting point
   * @param {number} w - Width of the piece
   * @param {number} h - Height of the piece
   * @param {boolean} inv - The line must be drawn bottom to top
   */
  vLine(sd, type, x, y, w, h, inv) {
    const
      ky = inv ? -1 : 1,
      kx = type === 1 ? 1 : -1;

    if (type === 0) {
      // Flat line
      sd.addStroke(new PathStroke('L', [x, y + h * ky]));
    } else {
      const y0 = y + (h - h * this.baseWidthFactor) / 2 * ky;
      const hb = h * this.baseWidthFactor * ky;
      // Approximation to the tooth:
      sd.addStroke(new PathStroke('L', [x, y0]));
      // The tooth:
      const wb = w * this.toothHeightFactor * kx;
      sd.addStroke(new PathStroke('L', [x + wb, y0]));
      sd.addStroke(new PathStroke('L', [x + wb, y0 + hb]));
      sd.addStroke(new PathStroke('L', [x, y0 + hb]));
      // Draw the remaining line
      sd.addStroke(new PathStroke('L', [x, y + h * ky]));
    }
  }
}

// Register this class in the list of known shapers
export default Shaper.registerClass('@JigSaw', JigSaw);