Headbreaker Jigsaw Puzzles Library

repository·master·Indexed 41 days ago

https://github.com/flbulgarelli/headbreaker

A JavaScript framework for building jigsaw puzzles featuring a dual-purpose architecture: a headless data model (Puzzle) for managing logic and a visual rendering system (Canvas) for the web. Version 3.0.0 supports automatic puzzle generation from images, manual piece sketching, custom connection requirements, and integration with React and Vue using the Konva.js rendering backend.

Tokens
16.6K
Snippets
60
Records
69
Agent score
51%

What's inside Headbreaker

  1. How Canvas and Puzzle abstractions work together

    master

    Headbreaker separates the jigsaw data model from its visual representation:

    1. Puzzle: A headless, pure JavaScript data structure that manages the jigsaw model, including individual Piece objects, their connections, metadata, and spatial transformations (translation, shuffling, etc.). It can run in any environment, including Node.js.
    2. Canvas: A visual representation of a Puzzle. It mirrors many of the common Puzzle methods but is responsible for rendering the pieces to a web container.

    You can access the underlying Puzzle object from a Canvas instance at any time using the .puzzle accessor.

    // create and configure the canvas
    const canvas = new headbreaker.Canvas(...);
    
    // now you can access and interact with the puzzle object
    const puzzle = canvas.puzzle;
  2. Integrate Headbreaker with Vue

    master

    In Vue, initialize the Canvas within the mounted lifecycle hook. Similar to React, you must specify a painter (e.g., new headbreaker.painters.Konva()) for visual rendering.

    <template>
      <div id="app">
        <div id="puzzle"></div>
      </div>
    </template>
    
    <script>
    import headbreaker from 'headbreaker';
    
    const drawPuzzle = () => {
      const autogen = new headbreaker.Canvas('puzzle', {
        width: 800,
        height: 650,
        pieceSize: 100,
        proximity: 20,
        borderFill: 10,
        strokeWidth: 2,
        lineSoftness: 0.18,
        painter: new headbreaker.painters.Konva(),
      });
    
      autogen.autogenerate({
        horizontalPiecesCount: 2,
        verticalPiecesCount: 2,
        metadata: [
          { color: '#B83361' },
          { color: '#B87D32' },
          { color: '#A4C234' },
          { color: '#37AB8C' },
        ],
      });
    
      autogen.draw();
    };
    
    export default {
      mounted() {
        drawPuzzle()
      },
    };
    </script>
  3. Create a visual puzzle using Canvas

    master

    To render a puzzle in a web browser, use the headbreaker.Canvas class. You can provide an image to be used as the puzzle content. The Canvas class provides methods like autogenerate(), shuffle(), and draw() to manage the lifecycle of the visual puzzle.

    <!-- just add a div with an id... -->
    <div id="my-canvas"></div>
    
    <script>
      let dali = new Image();
      dali.src = 'static/dali.jpg';
      dali.onload = () => {
        const canvas = new headbreaker.Canvas('my-canvas', {
          width: 800, 
          height: 800, 
          image: dali
        });
        canvas.autogenerate();
        canvas.shuffle(0.7);
        canvas.draw();
      }
    </script>
  4. Install headbreaker

    master

    Install the core headbreaker package via npm. If you intend to use the built-in Konva.js rendering backend, you must also manually install konva as a dependency.

    npm install --save headbreaker
    
    # optional: manually add konva to your project if you want to use
    # it as rendering backend
    npm install --save konva
  5. Create dynamic puzzles with custom templates and event listeners

    master

    You can create highly interactive puzzles by defining custom templates using defineTemplate and responding to connection/disconnection events.

    1. Define Templates: Use defineTemplate(name, config) where config includes a structure (e.g., 'TTSS') and metadata (e.g., colors or labels).
    2. Sketch Pieces: Use sketchPieceUsingTemplate(id, templateName) to assign a template to a specific piece.
    3. Handle Events: Use onConnect and onDisconnect to trigger logic (like updating labels or scores) whenever two pieces are joined or separated. Call redraw() to update the visual state.
    4. Render: Call draw() to initialize the canvas.
    const dynamic = new headbreaker.Canvas('dynamic-canvas', {
      width: 700, 
      height: 700, 
      pieceSize: 100, 
      proximity: 20,  
      borderFill: 10, 
      lineSoftness: 0.2, 
      strokeWidth: 0 
    });
    
    dynamic.defineTemplate('A', {
      structure: 'TTSS', 
      metadata: { label: { text: '0', x: 22 }, color: '#DB7BBF' }
    });
    
    dynamic.sketchPieceUsingTemplate('a', 'A');
    
    dynamic.onConnect((piece, figure, target, targetFigure) => {
      // logic for connection
      dynamic.redraw();
    });
    
    dynamic.draw();
  6. Make the puzzle canvas responsive

    master

    To make a headbreaker canvas resize with its container, use the resize(width, height) and scale(factor) methods.

    When the window or container resizes, calculate the new dimensions and the scale factor relative to your initial dimensions, then call redraw() to update the rendering.

    const initialWidth = 800;
    const responsive = new headbreaker.Canvas('responsive-canvas', { width: 800, height: 650 });
    
    window.addEventListener('resize', () => {
      const container = document.getElementById('responsive-canvas');
      responsive.resize(container.offsetWidth, container.scrollHeight);
      responsive.scale(container.offsetWidth / initialWidth);
      responsive.redraw();
    });
  7. Create rectangular-shaped piece puzzles

    master

    For puzzles where pieces are not square, you can pass objects for pieceSize and borderFill in the Canvas constructor to specify {x, y} dimensions.

    Additionally, use adjustImagesToPuzzleWidth() to automatically scale the background image to fit the generated puzzle width.

    const rectangular = new headbreaker.Canvas('rectangular-canvas', {
      width: 800, 
      height: 650,
      pieceSize: {x: 200, y: 120}, 
      borderFill: {x: 20, y: 12},
      image: myImage
    });
    
    rectangular.adjustImagesToPuzzleWidth();
    rectangular.autogenerate({ horizontalPiecesCount: 3, verticalPiecesCount: 3 });
    rectangular.draw();
  8. Add image backgrounds and validation to puzzles

    master

    You can create image-based puzzles by providing an HTMLImageElement to the Canvas constructor.

    • Image Background: Pass an image property in the Canvas options. Ensure the image is fully loaded before initializing the canvas.
    • Validation: Use attachSolvedValidator() to enable automatic checking of whether the puzzle is solved. You can listen for the solution event using onValid(callback).
    let img = new Image();
    img.src = 'path/to/image.jpg';
    img.onload = () => {
      const canvas = new headbreaker.Canvas('canvas-id', {
        width: 800, 
        height: 900,
        image: img,
        fixed: true // keeps canvas stuck to its current position
      });
      
      canvas.autogenerate({ horizontalPiecesCount: 5, verticalPiecesCount: 8 });
      canvas.draw();
      canvas.attachSolvedValidator();
      
      canvas.onValid(() => {
        console.log('Puzzle solved!');
      });
    };
  9. Autogenerate a puzzle from an image

    master

    To automatically create a puzzle from an image, use autogenerate().

    1. Load an image and pass it to the Canvas constructor.
    2. Call adjustImagesToPuzzleHeight() or adjustImagesToPuzzleWidth() to scale the image to the puzzle dimensions.
    3. Call autogenerate() with horizontalPiecesCount and verticalPiecesCount.

    You can also provide an insertsGenerator (like headbreaker.generators.random or headbreaker.generators.flipflop) to control how tabs and slots are distributed.

    let img = new Image();
    img.src = 'path/to/image.jpg';
    img.onload = () => {
      const canvas = new headbreaker.Canvas('canvas-id', {
        width: 800, height: 650,
        image: img,
        pieceSize: 100
      });
    
      canvas.adjustImagesToPuzzleHeight();
      canvas.autogenerate({
        horizontalPiecesCount: 6,
        verticalPiecesCount: 5,
        insertsGenerator: headbreaker.generators.random
      });
      canvas.draw();
    };
  10. Integrate Headbreaker with React

    master

    When using Headbreaker in React, ensure you pass a painter to the Canvas constructor (e.g., new painters.Konva()) to enable rendering. Use useRef to provide a DOM element for the canvas and useEffect to initialize the puzzle instance.

    import { Canvas, painters } from 'headbreaker';
    import { useEffect, useRef } from 'react';
    
    function DemoPuzzle({ id }) {
      const puzzleRef = useRef(null)
    
      useEffect(() => {
        const puzzle = puzzleRef.current
        const canvas = new Canvas(puzzle.id, {
          width: 800, height: 650,
          pieceSize: 100, proximity: 20, 
          borderFill: 10, strokeWidth: 2, lineSoftness: 0.18,
          painter: new painters.Konva() 
        });
    
        canvas.autogenerate({
          horizontalPiecesCount: 2,
          verticalPiecesCount: 2,
          metadata: [{ color: '#B83361' }, { color: '#B87D32' }, { color: '#A4C234' }, { color: '#37AB8C' }]
        });
    
        canvas.draw();
      }, [])
    
      return <div ref={puzzleRef} id={id}></div>
    }
  11. Implement a custom rendering backend using the Painter class

    master

    The Painter class serves as an interface for creating custom rendering backends for Headbreaker. To implement a custom UI representation of a puzzle (e.g., using Konva, SVG, or a custom WebGL engine), you must extend the Painter class and implement its lifecycle and drawing methods.

    Key lifecycle stages include:

    1. initialize(canvas, id): Set up the rendering environment.
    2. resize(canvas, width, height): Handle canvas dimension changes.
    3. draw(canvas): Execute the actual rendering of all figures on the canvas.
    4. reinitialize(canvas): Clear and reset the backend state.

    When adding pieces to the puzzle, the backend uses sketch to prepare the piece's geometry, fill to apply colors/textures, and label to render text labels.

    class MyCustomPainter extends Painter {
      initialize(canvas, id) {
        // Setup your rendering engine (e.g., Konva Stage)
      }
    
      draw(canvas) {
        // Render the current state of the canvas
      }
    
      sketch(canvas, piece, figure, outline) {
        // Define the geometry for a piece
      }
    }
  12. Initialize a Canvas

    master

    The Canvas class provides a high-level graphical area for rendering puzzles and pieces. It delegates actual drawing to a Painter object (defaulting to Konva if available in the global headbreaker namespace).

    const canvas = new Canvas('canvas-id', {
      width: 800,
      height: 600,
      pieceSize: 50,
      proximity: 10,
      borderFill: 0,
      strokeWidth: 3,
      strokeColor: 'black',
      lineSoftness: 0,
      preventOffstageDrag: false,
      image: null, // Optional background image
      fixed: false,
      painter: null, // Optional custom Painter
      puzzleDiameter: null,
      maxPiecesCount: null,
      outline: null
    });