Skip to content

Instantly share code, notes, and snippets.

@steve-kasica
Last active January 19, 2018 14:26
Show Gist options
  • Save steve-kasica/8049ced71fd92ad02463a85c3e30ec4d to your computer and use it in GitHub Desktop.
Save steve-kasica/8049ced71fd92ad02463a85c3e30ec4d to your computer and use it in GitHub Desktop.
Unsplash Photos of the Year Puzzle
license: gpl-3.0

Click and drag a tile to swap it with another tile in the picture until all the tiles are in their correct positions. To peek at the complete image, right click and hold with your mouse or trackpad--left click, if you're left handed.

Warnings

This game has only been smoke-tested in Chrome and Firefox. There seems a funny caching issue with requesting random images from Unsplash that affects Safari. I offer no guarantees about IE.

Acknowledgements

I made this simple game to learn the HTML Drag and Drop API. This code snippet builds off of Bill Weinman's excellent tutorial on lynda.com, HTM5: Drag and Drop. Images are randomly chosen on page load from Unsplash's 2017 Photos of the Year. The Fisher-Yates shuffle algorithm permutes images tiles. The randomInt function was taken from the Mozilla Developer Network documentation on Math.random.

<!DOCTYPE html>
<html>
<head>
<style>
body > div {
position: absolute;
top: 0;
bottom: 0;
}
#puzzle {
display: flex;
flex-wrap: wrap;
}
.tile {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
outline: 1px solid #fff;
}
.tile.hovered {
outline: 2px dotted #999;
}
.tile:active,
.tile.dragging {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
.overlay {
text-align: center;
width: inherit;
height: 260px;
padding-top: 240px;
background: #000;
color: #fff;
position: absolute;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="puzzle"><!-- Hey, no cheating now --></div>
<div id="peek" style="display:none;"></div>
<script type="text/javascript">
"use strict";
var draggingElement, droppingElement;
var stats = { moves: 0, peeks: 0 };
var container = document.getElementById('puzzle');
window.onload = init;
function detectDragAndDrop() {
if (navigator.appName == 'Microsoft Internet Explorer') {
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null) {
var rv = parseFloat( RegExp.$1 );
if(rv >= 6.0) return true;
}
return false;
}
if ('draggable' in document.createElement('span')) return true;
return false;
}
function init() {
if (!detectDragAndDrop()) {
alert("Your browser doesn't support drag and drop");
return;
}
var difficulty = 5;
var img_width = 960;
var img_height = 500;
var img_src = 'https://source.unsplash.com/collection/1521669/' + img_width + 'x' + img_height;
var block = {
width: img_width / difficulty,
height: img_height / difficulty
};
container.style.width = img_width + 'px';
container.style.height = img_height + 'px';
assemblePuzzle(block, img_width, img_height, img_src, container);
initializePeek(img_width, img_height, img_src);
}
function initializePeek(width, height, img) {
var peek = document.getElementById('peek');
var peekKey = 'alt';
peek.style.backgroundImage = 'url(' + img + ')';
peek.style.width = width + 'px';
peek.style.height = height + 'px';
window.addEventListener('mousedown', function(ev) {
if (ev.button == 2 && peek.style.display === 'none') {
peek.style.display = 'block';
stats.peeks++;
}
});
window.addEventListener('mouseup', function(ev) {
if (ev.button === 2 && peek.style.display === 'block') {
peek.style.display = 'none';
}
});
window.addEventListener('contextmenu', function(ev) {
ev.preventDefault();
return false;
});
}
function assemblePuzzle(b, img_w, img_h, src, parent) {
var blocks = [];
var tile;
for (var h = 0, i = 0; h < img_h; h += b.height) {
for (var w = 0; w < img_w; w += b.width, i++) {
tile = document.createElement('DIV');
tile.style.backgroundImage = 'url(' + src + ')';
tile.style.backgroundPositionX = (-1 * w) + 'px';
tile.style.backgroundPositionY = (-1 * h) + 'px';
tile.id = i;
tile.className = 'tile';
tile.style.height = b.height + 'px';
tile.style.width = b.width + 'px';
tile.draggable = true;
tile.addEventListener('dragstart', handleDragStart, false);
tile.addEventListener('dragend', handleDragEnd, false);
tile.addEventListener('dragover', handleDragOver, false);
tile.addEventListener('dragenter', handleDragEnter, false);
tile.addEventListener('dragleave', handleDragLeave, false);
tile.addEventListener('drop', handleDrop, false);
blocks.push(tile);
}
}
shuffle(blocks).forEach(function(node) {
parent.append(node);
});
}
function handleDragStart(event) {
draggingElement = this;
draggingElement.className += ' dragging';
event.dataTransfer.setData('text/plain', this.id);
}
function handleDragEnd() {
draggingElement.className = 'tile';
draggingElement.blur();
draggingElement = undefined;
}
function handleDragEnter() {
if (this === draggingElement) return;
this.className += ' hovered';
}
function handleDragLeave() {
if (this === draggingElement) return;
this.className = 'tile';
}
function handleDrop(event) {
if (event.stopPropagation) event.stopPropagation(); // Stops some browsers from redirecting.
if (event.preventDefault) event.preventDefault();
if (this !== draggingElement) {
swap(draggingElement, this);
this.className = 'tile';
stats.moves++;
isWinner();
}
}
function handleDragOver(event) {
if (event.preventDefault()) event.preventDefault();
return false; // some browsers may need this to prevent default
}
function swap(a, b) {
var tmpX = b.style.backgroundPositionX;
var tmpY = b.style.backgroundPositionY;
var tmpid = b.id;
b.style.backgroundPositionX = a.style.backgroundPositionX;
b.style.backgroundPositionY = a.style.backgroundPositionY;
b.id = a.id;
a.style.backgroundPositionX = tmpX;
a.style.backgroundPositionY = tmpY;
a.id = tmpid;
}
function isWinner() {
var img;
var delay = 100;
for (var i = 0; i < container.children.length; i++) {
img = container.children[i];
if (+img.id !== i) return;
}
var overlay = document.createElement('DIV');
overlay.className = 'overlay';
overlay.innerHTML = '<h1>You solved the puzzle in ' + stats.moves + ' moves and peeked ' + stats.peeks +' times</h1>';
container.appendChild(overlay);
}
function randomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function shuffle(arr) {
for (var j, n = arr.length - 1; n > 0; n--) {
j = randomInt(n);
exchange(n,j);
}
return arr;
function exchange(a,b) {
var tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment