🧩 🤯 Headbreaker
Jigsaw Puzzles Framework for JavaScript
headbreaker
- a Spanish pun for rompecabezas - is a JavaScript framework for building all kind of jigsaw puzzles.
☑️ Features
- 100% pure JavaScript
- Headless support domain-model
- Highly tested
- Customizable data-model
- Zero-dependencies - although Konva.js is used a a rendering backed, it is an optional dependency which can be replaced with custom code
📦 Installing
npm install --save headbreaker
# optional: manually add konva to your project if you want to use
# it as rendering backend
npm install --save konva
⏳ TL;DR sample
If you just want to see a - very basic - 2x2 puzzle in your web-browser, then create an HTML file with the following contents 😁:
<script src="https://flbulgarelli.github.io/headbreaker/js/headbreaker.js"></script>
<body>
<div id="puzzle"></div>
<script>
const autogen = new headbreaker.Canvas('puzzle', {
width: 800, height: 650,
pieceSize: 100, proximity: 20,
borderFill: 10, strokeWidth: 2, lineSoftness: 0.18,
});
autogen.autogenerate({
horizontalPiecesCount: 2,
verticalPiecesCount: 2,
metadata: [
{color: '#B83361'},
{color: '#B87D32'},
{color: '#A4C234'},
{color: '#37AB8C'}
]
});
autogen.draw();
</script>
</body>
And voilà! 🎊
However, there is a lot more that headbreaker
can do for you. These are some of its coolest features:
- Customizable pieces outlines
- Irregular pieces
- Image support
- Sound support
- Event system
- Automatic validation
- Data import and export
🏁 Quick start
headbreaker
is a library which solves two different - but-related - problems:
- It implements a jigsaw-like data-structure, which can be used in tasks like modelling, traversing, importing and exporting and - of course - rendering. This data-structure is 100% pure JavaScript, with no dependencies, and can be used both in browsers and headless environments.
- It implements a simple and generic rendering system for the Web.
headbreaker
also ships a 100% functional Konva.js-based implementation, but you may want to develop and use your own implementation.
headbreaker
is designed to be included and compiled in your project as a node
module, but you can also import it directly in your static pages from https://flbulgarelli.github.io/headbreaker/js/headbreaker.js.
HTML Puzzle
<!-- just add a div with an id... -->
<div id="my-canvas">
</div>
<script>
// ...and a script with the following code:
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>
Canvas
is a visual representation of a Puzzle
and as such, it mirrors many of the most
common Puzzle
methods - see next section for more details. However, if you need to access
the associated Puzzle
object, you can call puzzle
accessor anytime:
// create and configure the canvas
const canvas = new headbreaker.Canvas(...);
// ...
// now you can access and interact with the puzzle object
const puzzle = canvas.puzzle;
Headless Puzzle
Headbreaker provides a Puzzle
object which allows to fully manipulate its model and its individual Piece
s. Since Puzzle
s are not coupled
to any visual representation, it can be even loaded in headless environments, like in a node
server:
const headbreaker = require('headbreaker');
// Create a puzzle
const puzzle = new headbreaker.Puzzle();
puzzle
.newPiece({right: Tab})
.locateAt(0, 0);
puzzle
.newPiece({left: Slot, right: Tab})
.locateAt(3, 0);
puzzle
.newPiece({left: Slot, right: Tab, down: Slot})
.locateAt(6, 0);
puzzle
.newPiece({up: Tab})
.locateAt(6, 3);
// Connect puzzle's nearby pieces
puzzle.autoconnect();
// Translate puzzle
puzzle.translate(10, 10);
// Shuffle pieces
puzzle.shuffle(100, 100);
// Relocate pieces to fit into a bounding box
// while preserving their relative positions, if possible
puzzle.reframe(vector(0, 0), vector(20, 20));
// Directly manipulate pieces
const [a, b, c, d] = puzzle.pieces;
// Drag a piece 10 steps right and 5 steps down
a.drag(10, 5);
// Connect two pieces (if possible)
a.tryConnectWith(b);
// Add custom metadata to pieces
a.metadata.flavour = "chocolate";
a.metadata.sugar = true;
b.metadata.flavour = "chocolate";
b.metadata.sugar = false;
c.metadata.flavour = "vainilla";
c.metadata.sugar = false;
d.metadata.flavour = "vainilla";
d.metadata.sugar = true;
// Require pieces to match a given condition in
// order to be connected
puzzle.attachConnectionRequirement((one, other) => one.metadata.flavour === other.metadata.flavour);
// Alternatively, set individual requirements for horizontal
// and vertical connections
puzzle.attachVerticalConnectionRequirement((one, other) => one.metadata.flavour === other.metadata.flavour);
puzzle.attachHorizontalConnectionRequirement((one, other) => one.metadata.sugar !== other.metadata.sugar);
// Remove all - vertical and horizontal - connection requirements
puzzle.clearConnectionRequirements();
// Export and import puzzle
const dump = puzzle.export();
const otherPuzzle = headbreaker.Puzzle.import(dump);
React Puzzle
Check also https://github.com/flbulgarelli/headbreaker-react-sample
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() // <-- this is important. See https://github.com/flbulgarelli/headbreaker/issues/51
});
canvas.autogenerate({
horizontalPiecesCount: 2,
verticalPiecesCount: 2,
metadata: [
{ color: '#B83361' },
{ color: '#B87D32' },
{ color: '#A4C234' },
{ color: '#37AB8C' }
]
});
canvas.draw();
}, [])
return <div ref={puzzleRef} id={id}></div>
}
export default function Home() {
return (
<main>
<h1>Headbreaker From React</h1>
<DemoPuzzle id="puzzle" />
</main>
)
}
Vue Puzzle
Check also https://github.com/flbulgarelli/headbreaker-vue-sample
<template>
<div id="app">
<div>Headbreaker from Vue</div>
<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>
👀 Demo and API Docs
See flbulgarelli.github.io/headbreaker
❓ Questions
Do you have any questions or doubts? Please feel free to check the existing discussions or open a new one 🙋.
🏗 Develop
# install project
$ npm install
# run tests
$ npm run test
# build whole project
$ npm run all
# start docs site locally
# requires mkdocs
$ mkdocs serve