Source: manufacturer.js

  1. const Puzzle = require('./puzzle');
  2. const Piece = require('./piece');
  3. const {Anchor} = require('./anchor');
  4. const {anchor} = require('./anchor');
  5. const {fixed, InsertSequence} = require('./sequence')
  6. const Metadata = require('./metadata');
  7. /**
  8. * A manufacturer allows to create rectangular
  9. * puzzles by automatically generating inserts
  10. */
  11. class Manufacturer {
  12. constructor() {
  13. this.insertsGenerator = fixed;
  14. this.metadata = [];
  15. /** @type {Anchor} */
  16. this.headAnchor = null;
  17. }
  18. /**
  19. * Attach metadata to each piece
  20. *
  21. * @param {object[]} metadata list of metadata that will be attached to each generated piece
  22. */
  23. withMetadata(metadata) {
  24. this.metadata = metadata;
  25. }
  26. /**
  27. * @param {import('./sequence').InsertsGenerator} generator
  28. */
  29. withInsertsGenerator(generator) {
  30. this.insertsGenerator = generator || this.insertsGenerator;
  31. }
  32. /**
  33. * Sets the central anchor. If not specified, puzzle will be positioned
  34. * at the distance of a whole piece from the origin
  35. *
  36. * @param {Anchor} anchor
  37. */
  38. withHeadAt(anchor) {
  39. this.headAnchor = anchor;
  40. }
  41. /**
  42. * If nothing is configured, default Puzzle structured is assumed
  43. *
  44. * @param {import('../src/puzzle').Settings} structure
  45. */
  46. withStructure(structure) {
  47. this.structure = structure
  48. }
  49. /**
  50. *
  51. * @param {number} width
  52. * @param {number} height
  53. */
  54. withDimensions(width, height) {
  55. this.width = width;
  56. this.height = height;
  57. }
  58. /**
  59. * @returns {Puzzle}
  60. */
  61. build() {
  62. const puzzle = new Puzzle(this.structure);
  63. const positioner = new Positioner(puzzle, this.headAnchor);
  64. let verticalSequence = this._newSequence();
  65. let horizontalSequence;
  66. for (let y = 0; y < this.height; y++) {
  67. horizontalSequence = this._newSequence();
  68. verticalSequence.next();
  69. for (let x = 0; x < this.width; x++) {
  70. horizontalSequence.next();
  71. const piece = this._buildPiece(puzzle, horizontalSequence, verticalSequence);
  72. piece.centerAround(positioner.naturalAnchor(x, y));
  73. }
  74. }
  75. this._annotateAll(puzzle.pieces);
  76. return puzzle;
  77. }
  78. /**
  79. * @param {Piece[]} pieces
  80. */
  81. _annotateAll(pieces) {
  82. pieces.forEach((piece, index) => this._annotate(piece, index));
  83. }
  84. /**
  85. * @param {Piece} piece
  86. * @param {number} index
  87. */
  88. _annotate(piece, index) {
  89. const baseMetadata = this.metadata[index];
  90. const metadata = baseMetadata ? Metadata.copy(baseMetadata) : {};
  91. metadata.id = metadata.id || String(index + 1);
  92. piece.annotate(metadata);
  93. }
  94. _newSequence() {
  95. return new InsertSequence(this.insertsGenerator);
  96. }
  97. /**
  98. * @param {Puzzle} puzzle
  99. * @param {InsertSequence} horizontalSequence
  100. * @param {InsertSequence} verticalSequence
  101. */
  102. _buildPiece(puzzle, horizontalSequence, verticalSequence) {
  103. return puzzle.newPiece({
  104. left: horizontalSequence.previousComplement(),
  105. up: verticalSequence.previousComplement(),
  106. right: horizontalSequence.current(this.width),
  107. down: verticalSequence.current(this.height)
  108. });
  109. }
  110. }
  111. class Positioner {
  112. /**
  113. *
  114. * @param {Puzzle} puzzle
  115. * @param {Anchor} headAnchor
  116. */
  117. constructor(puzzle, headAnchor) {
  118. this.puzzle = puzzle;
  119. this.initializeOffset(headAnchor);
  120. }
  121. /**
  122. * @param {Anchor} headAnchor
  123. */
  124. initializeOffset(headAnchor) {
  125. if (headAnchor) {
  126. /** @type {import('./vector').Vector} */
  127. this.offset = headAnchor.asVector();
  128. }
  129. else {
  130. this.offset = this.pieceDiameter;
  131. }
  132. }
  133. get pieceDiameter() {
  134. return this.puzzle.pieceDiameter;
  135. }
  136. /**
  137. * @param {number} x
  138. * @param {number} y
  139. */
  140. naturalAnchor(x, y) {
  141. return anchor(
  142. x * this.pieceDiameter.x + this.offset.x,
  143. y * this.pieceDiameter.y + this.offset.y);
  144. }
  145. }
  146. module.exports = Manufacturer;