Samples

Basic

Code

const basic = new headbreaker.Canvas('basic-canvas', { width: 500, height: 300, pieceSize: 50, proximity: 10 });
basic.sketchPiece({
  structure: { right: headbreaker.Tab, down: headbreaker.Tab, left: headbreaker.Slot },
  metadata: { id: 'a', currentPosition: { x: 50, y: 50 }, color: '#B87D32' }
});
basic.sketchPiece({
  structure: { up: headbreaker.Slot, right: headbreaker.Tab, down: headbreaker.Tab, left: headbreaker.Slot },
  metadata: { id: 'b', currentPosition: { x: 100, y: 50 }, color: '#B83361' }
});
basic.sketchPiece({
  structure: { up: headbreaker.Slot, right: headbreaker.Tab, down: headbreaker.Slot, left: headbreaker.Tab },
  metadata: { id: 'g', currentPosition: { x: 100, y: 230 } }
});
// ... more pieces ...
basic.draw();.

Demo

Soft lines

Code

const soft = new headbreaker.Canvas('soft-canvas', {
  width: 500, height: 300,
  pieceSize: 50, proximity: 10,
  lineSoftness: 0.2
});
soft.sketchPiece({
  structure: { right: headbreaker.Tab, down: headbreaker.Tab, left: headbreaker.Slot },
  metadata: { id: 'a', currentPosition: { x: 50, y: 50 }, color: '#B87D32' }
});
// ... more pieces ...
soft.draw();

Demo

Rounded lines

Code

const rounded = new headbreaker.Canvas('rounded-canvas', {
  width: 500, height: 300,
  pieceSize: 50,
  outline: new headbreaker.outline.Rounded()
});
rounded.sketchPiece({
  structure: { right: headbreaker.Tab, down: headbreaker.Tab, left: headbreaker.Slot },
  metadata: { id: 'a', currentPosition: { x: 50, y: 50 }, color: '#B87D32' }
});
// ... more pieces ...
rounded.draw();

Demo

Perfect match

Code

const perfect = new headbreaker.Canvas('perfect-canvas', {
  width: 800, height: 300,
  pieceSize: 100, proximity: 20,
  borderFill: 10,
  strokeWidth: 2, strokeColor: '#00200B',
  lineSoftness: 0.0
});

perfect.sketchPiece({
  structure: { right: headbreaker.Tab, down: headbreaker.Slot },
  metadata: { id: 'a', targetPosition: { x: 100, y: 100 }, color: '#0EC430' }
});
perfect.sketchPiece({
  structure: { right: headbreaker.Slot, left: headbreaker.Slot },
  metadata: { id: 'b', targetPosition: { x: 200, y: 100 }, color: '#098520' }
});
perfect.sketchPiece({
  structure: { down: headbreaker.Tab, left: headbreaker.Tab },
  metadata: { id: 'c', targetPosition: { x: 330, y: 80 }, color: '#04380D' }
});
// ... more pieces ...
perfect.draw();

Demo

Irregular

Code

const irregular = new headbreaker.Canvas('irregular-canvas', {
  proximity: 25,
  width: 500, height: 300,
  outline: new headbreaker.outline.Rounded() });
irregular.sketchPiece({
  structure: { right: headbreaker.Slot, left: headbreaker.Slot },
  metadata: { id: 'a', color: '#B87D32' },
  size: headbreaker.diameter({x: 50, y: 50})
});
// ... more pieces ...
irregular.sketchPiece({
  structure: { up: headbreaker.Slot, right: headbreaker.Tab, down: headbreaker.Slot, left: headbreaker.Tab },
  metadata: { id: 'd', color: '#A4C234' },
  size: headbreaker.diameter({x: 100, y: 50})
});
// ... more pieces ...
irregular.sketchPiece({
  structure: { up: headbreaker.Slot, right: headbreaker.Slot, down: headbreaker.Tab, left: headbreaker.Tab },
  metadata: { id: 'g', color: '#B83361' },
  size: headbreaker.diameter({x: 50, y: 100})
});

irregular.draw();

Demo

Background

Code

let vangogh = new Image();
vangogh.src = 'static/vangogh.jpg';
vangogh.onload = () => {
  const background = new headbreaker.Canvas('background-canvas', {
    width: 800, height: 650,
    pieceSize: 100, proximity: 20,
    borderFill: 10, strokeWidth: 2,
    lineSoftness: 0.12, image: vangogh,
    // optional, but it must be set in order to activate image scaling
    maxPiecesCount: {x: 5, y: 5}
  });

  background.adjustImagesToPuzzleHeight();
  background.sketchPiece({
    structure: 'TS--',
    metadata: { id: 'a', targetPosition: { x: 100, y: 100 } },
  });
  background.sketchPiece({
    structure: 'SSS-',
    metadata: { id: 'b', targetPosition: { x: 200, y: 100 } },
  });
  // ... more pieces ...
  background.sketchPiece({
    structure: '--TT',
    metadata: { id: 'y', targetPosition: { x: 500, y: 500 }, currentPosition: { x: 570, y: 560 } }
  });
  background.draw();
}

Demo

Autogenerated

Code

let xul = new Image();
xul.src = 'static/xul.jpg';
xul.onload = () => {
  const autogen = new headbreaker.Canvas('autogen-canvas', {
    width: 800, height: 650,
    pieceSize: 100, proximity: 20,
    borderFill: 10, strokeWidth: 1.5,
    lineSoftness: 0.18, image: xul,
  });

  autogen.adjustImagesToPuzzleHeight();
  autogen.autogenerate({
    horizontalPiecesCount: 6,
    verticalPiecesCount: 5
  });
  autogen.draw();
}

Demo

Offstage drag prevention

Code

let malharro = new Image();
malharro.src = 'static/malharro.jpg';
malharro.onload = () => {
  const offstage = new headbreaker.Canvas('offstage-canvas', {
    width: 400, height: 400, image: malharro,
    // ... more configs ...
    preventOffstageDrag: true,
    fixed: true
  });

  offstage.adjustImagesToPuzzleHeight();
  offstage.autogenerate({
    horizontalPiecesCount: 3,
    verticalPiecesCount: 3
  });
  offstage.shuffleGrid();
  offstage.draw();
}

Demo

Randomized positions

Code

let dali = new Image();
dali.src = 'static/dali.jpg';
dali.onload = () => {
  const randomized = new headbreaker.Canvas('randomized-canvas', {
    width: 800, height: 650,
    pieceSize: 100, proximity: 20,
    borderFill: 10, strokeWidth: 2,
    lineSoftness: 0.12, image: dali
  });
  randomized.autogenerate({
    insertsGenerator: headbreaker.generators.flipflop
  });
  randomized.shuffle(0.7);
  randomized.draw();
}

Demo

Labels

Code

const labels = new headbreaker.Canvas('labels-canvas', {
  width: 400, height: 400,
  pieceSize: 80, proximity: 25,
  borderFill: 10, strokeWidth: 2,
  lineSoftness: 0.18,
});

labels.sketchPiece({
  structure: { right: headbreaker.Tab },
  metadata: {
    id: 'tree-kanji',
    color: '#23599E',
    strokeColor: '#18396B',
    label: { text: '木', fontSize: 70, x: -5, y: 5 }
  }
});

labels.sketchPiece({
  structure: { left: headbreaker.Slot },
  metadata: {
    id: 'tree-emoji',
    color: '#EBB34B',
    strokeColor: '#695024',
    label: { text: '🌳', fontSize: 70, x: 5, y: 0 }
  }
});

// ... more pieces ...
labels.shuffle(0.6);
labels.draw();
labels.attachConnectionRequirement(
  (one, other) => one.metadata.id.replace('-kanji', '') == other.metadata.id.replace('-emoji', '')
);

Demo

Sound and visual feedback

Code

var audio = new Audio('static/connect.wav');
let berni = new Image();
berni.src = 'static/berni.jpg';
berni.onload = () => {
  const sound = new headbreaker.Canvas('sound-canvas', {
    width: 800, height: 650,
    pieceSize: 100, proximity: 20,
    borderFill: 10, strokeWidth: 1.5,
    lineSoftness: 0.18, image: berni,
    strokeColor: 'black'
  });

  sound.adjustImagesToPuzzleHeight();
  sound.autogenerate({
    horizontalPiecesCount: 6,
    insertsGenerator: headbreaker.generators.random
  });

  sound.draw();

  sound.onConnect((_piece, figure, _target, targetFigure) => {
    // play sound
    audio.play();

    // paint borders on click
    // of conecting and conected figures
    figure.shape.stroke('yellow');
    targetFigure.shape.stroke('yellow');
    sound.redraw();

    setTimeout(() => {
      // restore border colors
      // later
      figure.shape.stroke('black');
      targetFigure.shape.stroke('black');
      sound.redraw();
    }, 200);
  });

  sound.onDisconnect((it) => {
    audio.play();
  });
}

Demo

Keyboard gestures

Code

let amaral = new Image();
amaral.src = 'static/amaral.jpg';
amaral.onload = () => {
  const keyboard = new headbreaker.Canvas('keyboard-canvas', {
    width: 800, height: 650, pieceSize: 60,
    image: amaral, strokeWidth: 2.5, strokeColor: '#F0F0F0',
    outline: new headbreaker.outline.Rounded()
  });

  keyboard.adjustImagesToPuzzleWidth();
  keyboard.autogenerate({
    horizontalPiecesCount: 6,
    verticalPiecesCount: 7,
    insertsGenerator: headbreaker.generators.random
  });

  // make canvas focusable and listen
  // to ctrl and shift keys in order to force
  // pieces to be dragged individually or as a whole,
  // respectively
  keyboard.registerKeyboardGestures();
  keyboard.draw();

  registerButtons('keyboard', keyboard);
}

Demo

Try dragging pieces from the borders or the center while pressing shift and ctrl keys

Dynamic

Code

function updateLabel(piece, figure, delta) {
  piece.metadata.label.text = Number(piece.metadata.label.text) + delta;
  figure.label.text(piece.metadata.label.text);
}

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.defineTemplate('B', {
  structure: 'TTTT', metadata: { label: { text: '0', x: 22 }, color: '#438D8F' }
});
dynamic.defineTemplate('C', {
  structure: 'SSSS', metadata: { label: { text: '0', x: 22 }, color: '#DBC967' }
});
// ... more templates ...

dynamic.sketchPieceUsingTemplate('a', 'A');
dynamic.sketchPieceUsingTemplate('b', 'A');
dynamic.sketchPieceUsingTemplate('c', 'B');
dynamic.sketchPieceUsingTemplate('d', 'C');
dynamic.sketchPieceUsingTemplate('e', 'C');
dynamic.sketchPieceUsingTemplate('f', 'D');
dynamic.sketchPieceUsingTemplate('g', 'E');
dynamic.shuffle(0.7);
dynamic.onConnect((piece, figure, target, targetFigure) => {
  updateLabel(piece, figure, 1);
  updateLabel(target, targetFigure, 1);
  dynamic.redraw();
});
dynamic.onDisconnect((piece, figure, target, targetFigure) => {
  updateLabel(piece, figure, -1);
  updateLabel(target, targetFigure, -1);
  dynamic.redraw();
});
dynamic.draw();

Demo

Persistent

Code

const exportArea = document.getElementById('export-area');

function readDump() {
  return JSON.parse(exportArea.value);
}

function writeDump(dump) {
  exportArea.value = JSON.stringify(dump, null, 2);
}

const persistent = new headbreaker.Canvas('persistent-canvas', { width: 500, height: 400, strokeWidth: 0, borderFill: 4 });
persistent.autogenerate({metadata: [
  {color: '#6F04C7'}, {color: '#0498D1'}, {color: '#16BA0D'}, {color: '#D1A704'}, {color: '#C72C07'},
  {color: '#000000'}, {color: '#6F04C7'}, {color: '#0498D1'}, {color: '#16BA0D'}, {color: '#D1A704'},
  {color: '#C72C07'}, {color: '#000000'}, {color: '#6F04C7'}, {color: '#0498D1'}, {color: '#16BA0D'},
  {color: '#D1A704'}, {color: '#C72C07'}, {color: '#000000'}, {color: '#6F04C7'}, {color: '#0498D1'},
  {color: '#16BA0D'}, {color: '#D1A704'}, {color: '#C72C07'}, {color: '#000000'}, {color: '#6F04C7'}
]});
persistent.draw();

document.getElementById('persistent-import').addEventListener('click', function() {
  persistent.clear();
  persistent.renderPuzzle(headbreaker.Puzzle.import(readDump()));
  persistent.draw();
});

document.getElementById('persistent-export').addEventListener('click', function() {
  writeDump(persistent.puzzle.export({compact: true}));
});

Validated

Code

let pettoruti = new Image();
pettoruti.src = 'static/pettoruti.jpg';
pettoruti.onload = () => {
  const validated = new headbreaker.Canvas('validated-canvas', {
    width: 800, height: 900,
    pieceSize: 80, proximity: 18,
    borderFill: 8, strokeWidth: 1.5,
    lineSoftness: 0.18, image: pettoruti,
    // used to stick canvas to its current position
    fixed: true
  });
  validated.autogenerate({
    horizontalPiecesCount: 5,
    verticalPiecesCount: 8
  });
  validated.puzzle.pieces[4].translate(63, -56);
  validated.draw();
  validated.attachSolvedValidator();
  validated.onValid(() => {
    setTimeout(() => {
      document.getElementById('validated-canvas-overlay').setAttribute("class", "active");
    }, 1500);
  })
}

Responsive

Code

const initialWidth = 800;
const responsive = new headbreaker.Canvas('responsive-canvas', {
  width: 800, height: 650,
  pieceSize: 100, proximity: 20,
  borderFill: 10, strokeWidth: 1.5,
  lineSoftness: 0.18,
});

responsive.autogenerate({
  horizontalPiecesCount: 3,
  verticalPiecesCount: 3,
  metadata: [
    {color: '#6F04C7'}, {color: '#0498D1'}, {color: '#16BA0D'},
    {color: '#000000'}, {color: '#6F04C7'}, {color: '#0498D1'},
    {color: '#16BA0D'}, {color: '#000000'}, {color: '#6F04C7'},
  ]
});
responsive.draw();

['resize', 'DOMContentLoaded'].forEach((event) => {
  window.addEventListener(event, () => {
    var container = document.getElementById('responsive-canvas');
    responsive.resize(container.offsetWidth, container.scrollHeight);
    responsive.scale(container.offsetWidth / initialWidth);
    responsive.redraw();
  });
});

Demo

Rectangular

Code

let quinquela = new Image();
quinquela.src = 'static/quinquela.jpg';
quinquela.onload = () => {
  const rectangular = new headbreaker.Canvas('rectangular-canvas', {
    width: 800, height: 650,
    pieceSize: {x: 200, y: 120}, proximity: 20,
    borderFill: {x: 20, y: 12}, strokeWidth: 1.5,
    lineSoftness: 0.18, image: quinquela
  });

  rectangular.adjustImagesToPuzzleWidth();
  rectangular.autogenerate({
    horizontalPiecesCount: 3,
    verticalPiecesCount: 3
  });
  rectangular.draw();
  registerButtons('rectangular', rectangular);
}

Demo