Skip to content

Instantly share code, notes, and snippets.

@nevernormal1
Last active April 17, 2024 07:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save nevernormal1/f808cffb897c63a8dd4e to your computer and use it in GitHub Desktop.
Save nevernormal1/f808cffb897c63a8dd4e to your computer and use it in GitHub Desktop.
SVG Jigsaw Puzzle Generator
license: MIT
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://d3js.org/d3-path.v0.1.min.js"></script>
<script src="https://d3js.org/d3-shape.v0.5.min.js"></script>
<style>
body {
font: 13px sans-serif;
}
rect,
circle,
path {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
circle {
fill: #fff;
fill-opacity: .2;
}
</style>
<body>
<form>
<label for="rowCount">Rows:</label>
<input type="number" id="rowCount" min="1" max="10" value="10"/>
<label for="columnCount">Columns:</label>
<input type="number" id="columnCount" min="1" max="10" value="10"/>
</form>
<div id="container">
</div>
</body>
<script>
var width = 450, height = 450;
// Returns 6 points representing the shape of one edge of a puzzle piece.
// Point coordinates are expressed as percentage distances across the width
// and height of the piece.
var edgeDistributions = function() {
var randomBetween = function(min, max) {
return Math.random() * (max - min) + min;
};
var baselineOffsets = {
xMin: 51,
xMax: 62,
yMin: -15,
yMax: 5
};
var upperOffsets = {
xMin: 20,
xMax: 30,
yMin: 20,
yMax: 44
};
var point1 = [0, 0];
var point2 = [
randomBetween(baselineOffsets.xMin, baselineOffsets.xMax),
randomBetween(baselineOffsets.yMin, baselineOffsets.yMax)
];
var point3 = [
randomBetween(upperOffsets.xMin, upperOffsets.xMax),
randomBetween(upperOffsets.yMin, upperOffsets.yMax)
];
var point4 = [
randomBetween(100-upperOffsets.xMax, 100-upperOffsets.xMin),
randomBetween(upperOffsets.yMin, upperOffsets.yMax)
];
var point5 = [
randomBetween(100-baselineOffsets.xMax, 100-baselineOffsets.xMin),
randomBetween(baselineOffsets.yMin, baselineOffsets.yMax)
];
var point6 = [100, 0];
var sign = Math.random() < 0.5 ? -1 : 1;
return [point1, point2, point3, point4, point5, point6].map(function(p) {
return [p[0] / 100, p[1] * sign / 100];
});
};
// Builds an m + 1 x n matrix of edge shapes. The first and last rows
// are straight edges.
var buildDistributions = function(m, n) {
var lineGroups = [];
var lines = [];
var points, i, j;
for (j = 0; j < n; j++) {
lines.push([[0, 0], [1,0]]);
}
lineGroups.push(lines);
for (i = 1; i < m; i++) {
lines = [];
for (j = 0; j < n; j++) {
lines.push(edgeDistributions());
}
lineGroups.push(lines);
}
lines = [];
for (j = 0; j < n; j++) {
lines.push([[0, 0], [1,0]]);
}
lineGroups.push(lines);
return lineGroups;
};
var transposePoint = function(point) {
return [point[1], point[0]];
};
var offsetPoint = function(point, columnIndex, rowIndex, columnWidth, rowHeight) {
var offsetColumnPosition = function(percent, columnWidth, columnIndex) {
var columnOffset = columnWidth * columnIndex;
return percent * columnWidth + columnOffset;
};
var offsetRowPosition = function(percent, rowHeight, rowIndex) {
var rowOffset = rowHeight * rowIndex;
return percent * rowHeight + rowOffset;
};
var x = offsetColumnPosition(point[0], columnWidth, columnIndex);
var y = offsetRowPosition(point[1], rowHeight, rowIndex);
return [x, y];
};
var offsetPoints = function(lineGroups, offsetter) {
for (var i=0; i<lineGroups.length; i++) {
var lines = lineGroups[i];
for (var j=0; j<lines.length; j++) {
lines[j] = lines[j].map(function(point) {
return offsetter(point, j, i);
});
}
}
};
var buildPieces = function(rowCount, columnCount) {
var rowHeight = height / rowCount;
var columnWidth = width / columnCount;
var pieces = [];
var rows = buildDistributions(rowCount, columnCount);
offsetPoints(rows, function(point, j, i) {
return offsetPoint(point, j, i, columnWidth, rowHeight);
});
var columns = buildDistributions(columnCount, rowCount);
offsetPoints(columns, function(point, j, i) {
return offsetPoint(transposePoint(point), i, j, columnWidth, rowHeight);
});
for (var rowIndex = 1; rowIndex <= rowCount; rowIndex++) {
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++) {
var edges = [];
edges.push(rows[rowIndex - 1][columnIndex]);
edges.push(columns[columnIndex + 1][rowIndex - 1]);
edges.push(rows[rowIndex][columnIndex].slice().reverse());
edges.push(columns[columnIndex][rowIndex - 1].slice().reverse());
pieces.push(edges);
}
}
return pieces;
};
var d3CurvedLine = d3_shape.line().curve(d3_shape.curveBasis);
var piecePathData = function(piece) {
return piece.map(function(edge) {
return d3CurvedLine(edge);
}).join(" ");
};
var buildPiecePaths = function(pieces) {
return pieces.map(function(piece) {
return svg.path(piecePathData(piece));
});
};
// SVG helpers
var svg = {
openTag: '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="' + width + '" height="' + height + '">',
closeTag: '</svg>',
path: function(pathData) {
return '<path vector-effect="non-scaling-stroke" d="' + pathData + '"/>';
}
};
$(function() {
var generate = function() {
var rowCount = parseInt($("#rowCount").val(), 10);
var columnCount = parseInt($("#columnCount").val(), 10);
var pieces = buildPieces(rowCount, columnCount);
var piecePaths = buildPiecePaths(pieces).join("");
var svgNode = [
svg.openTag,
piecePaths,
svg.closeTag
].join("");
$("#container").empty().append($(svgNode));
}
$('input').change(generate);
generate();
});
</script>
@aartek
Copy link

aartek commented Mar 14, 2018

Is it possible to make the jigsaw area clickable? It works for me only for paths. I'd like to do something like $('#container > svg > path:nth-child(2)').on('click',()=>{console.log('jigsaw clicked')})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment