Hi, I have a puzzle game and I would like the actual image to be shown first for 30 secs after choosing the image file before being broken into grids.
When the game ends, the picture will be shown, is it possible to make that at the start of the game too?
Here’s the demo: https://kuochye.github.io/puzzlegame/
index.html
<!doctype html>
<html manifest="manifest.appcache">
<head>
<meta charset="UTF-8" />
<title>Sliding Puzzle</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
html, body {
margin: 0;
padding: 0;
background: black;
font-family: sans-serif;
color: #f6f6f6;
}
h1 {
font-size: 2rem;
font-weight: bold;
letter-spacing: -0.05em;
width: 9em;
margin-right: auto;
margin-left: auto;
padding: 0 1em 0 0;
margin: 1rem auto 0 auto;
}
h1 span {
background: #333;
color: #FFF;
padding: 0.2em;
display: inline-block;
position: relative;
border: 2px solid #111;
}
span.s1 {
background: #444;
padding-right: 2.2em;
}
span.s1::after {
border-left: 2px solid #111;
content: " ";
background: #666;
padding: 0;
width: 2em;
height: 100%;
display: block;
position: absolute;
top: 0;
right: 0;
overflow: hidden;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
padding: 0.2em;
}
span.s2 {
position: relative;
top: -2px;
padding-left: 2.2em;
margin-left: 1.6em;
}
span.s2::before {
border-right: 2px solid #111;
content: " ";
background: #555;
padding: 0;
width: 2em;
height: 100%;
display: block;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
padding: 0.2em;
}
.tile {
position: absolute;
-webkit-transition: -webkit-transform 0.3s ease-out;
transition: transform 0.3s ease-out;
transform: translateX(0) translateY(0);
}
.tile.peek {
-webkit-transition: -webkit-transform .1s ease;
transition: transform .1s ease;
}
body, #gui, #paused, #container, #finished {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#gui, #paused, #finished {
background: #111;
z-index: 42;
text-align: center;
}
#paused {
background: rgba(28, 28, 28, 0.85);
display: none;
}
h2 {
font-size: 2.3rem;
margin: 1rem;
}
dl {
margin-top: 2rem;
}
dt, dd {
display: inline-block;
width: 50%;
margin: 0;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
padding: 0 0.5em;
}
dt {
text-align: right;
}
dd {
text-align: left;
}
button, input {
padding: 1.8rem;
margin: 1rem 0.1rem;
}
label {
margin-top: 2rem;
display: block;
}
.hidden {
display: none !important;
}
#gui p {
margin: 1rem auto;
padding: 0;
}
#reminder {
font-size: 75%;
width: 100%;
position: absolute;
bottom: 1rem;
}
#reminder p {
max-width: 15rem;
margin: 0 auto;
}
</style>
<script src="sliding.js"></script>
<script>
window.onload = init;
</script>
</head>
<body>
<form id="gui" class="hidden">
<h1><span class="s1">Sliding</span><span class="s2">Puzzle</span></h1>
<p>
<label for="file" id="file_label">Choose an image</label>
<input type="file" id="file" size="10" />
</p>
<div id="reminder">
<p>(When playing, touch the empty space to pause and look at game statistics)</p>
</div>
</form>
<div id="paused">
<div class="infos">
<h2>Game Paused</h2>
<p>Touch the screen to resume</p>
<dl style="display: none;">
<dt>Time elapsed:</dt><dd><span id="elapsed">x</span> seconds </dd>
</dl>
<dl>
<dt>Moves used:</dt><dd><span id="moves">x</span></dd>
</dl>
</div>
<div class="controls">
<button id="newgame">New Game</button>
<button id="restartgame">Restart Game</button>
</div>
</div>
</body>
</html>
sliding.js
"use strict";
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var puzzle = {
infos: {
tilesCount: null,
difficulty: null,
solved: null,
timeStarted: null,
timePaused: null,
timeSpentPausing: null,
movesCount: null
},
tilesElements: null,
tiles: null,
height: null,
width: null,
img: null,
canvas: null,
redrawCallback: null,
};
function init() {
var elm = document.getElementById('file');
var label = document.getElementById('file_label');
var paused = document.getElementById('paused');
var newgame = document.getElementById('newgame');
var restartgame = document.getElementById('restartgame');
if ('MozActivity' in window) {
/* For browsers with Web Activities, i.e. Firefox OS, we show the
Web Activities "pick", restricting to images. */
elm.type = "button";
elm.value = label.textContent;
elm.addEventListener('click', function(e) {
e.preventDefault();
var pick = new MozActivity({
name: "pick",
data: {
type: ["image/png", "image/jpg", "image/jpeg"]
}
});
pick.onsuccess = function() {
// Create image and set the returned blob as the src
puzzle.init(this.result.blob);
};
});
label.style.display = 'none';
} else {
/* The other browsers get the standard <input type=file> */
elm.addEventListener('change', function() {
if (this.files.length) {
puzzle.init(this.files[0]);
}
});
}
window.addEventListener('beforeunload', function() {
puzzle.showPauseScreen();
});
paused.addEventListener('mousedown', puzzle.hidePauseScreen);
paused.addEventListener('touchstart', puzzle.hidePauseScreen);
newgame.addEventListener('mousedown', puzzle.newGame);
newgame.addEventListener('touchstart', puzzle.newGame);
restartgame.addEventListener('mousedown', puzzle.restartGame);
restartgame.addEventListener('touchstart', puzzle.restartGame);
if (typeof localStorage['puzzleInfos'] !== 'undefined' &&
typeof localStorage['puzzleSource'] !== 'undefined' &&
typeof localStorage['puzzleTiles'] !== 'undefined') {
puzzle.resumeGame();
} else {
showGui();
}
window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache.
// Swap it in and reload the page to get the new hotness.
window.applicationCache.swapCache();
if (confirm('A new version is available, load it immediately ? (Game progress isn\'t lost)')) {
window.location.reload();
}
} else {
// Manifest didn't change. Nothing new to serve.
}
}, false);
}
function showGui() {
document.getElementById('gui').classList.remove('hidden');
}
function hideGui() {
document.getElementById('gui').classList.add('hidden');
}
function Tile(x, y) {
this.x = x;
this.y = y;
this.originalX = x;
this.originalY = y;
this.id = "c_" + (x * puzzle.infos.tilesCount + y);
}
Tile.prototype.isEmpty = function() {
// FIXME: use everywhere
return (this === puzzle.tiles.empty);
};
Tile.prototype.eventHandler = function(e) {
if (!document.body.classList.contains('moving')) {
this.move(true);
}
e.preventDefault();
e.stopPropagation();
};
Tile.prototype.move = function(incrementCount) {
var empty = puzzle.tiles.empty;
if (incrementCount !== false) {
incrementCount = true;
}
if (this.canMove()) {
if (incrementCount) {
puzzle.infos.movesCount++;
}
document.body.classList.add('moving');
this.originalX = this.x;
this.originalY = this.y;
this.x = empty.x;
this.y = empty.y;
empty.x = this.originalX;
empty.y = this.originalY;
this.reposition();
} else if (this.canLineMove('x')) {
if (incrementCount) {
puzzle.infos.movesCount++;
}
this.lineMove('x', 'y');
} else if (this.canLineMove('y')) {
if (incrementCount) {
puzzle.infos.movesCount++;
}
this.lineMove('y', 'x');
}
localStorage['puzzleTiles'] = JSON.stringify(puzzle.tiles);
};
Tile.prototype.lineMove = function(mainAxis, secondaryAxis) {
function sortTiles(a, b) {
if (this[secondaryAxis] > puzzle.tiles.empty[secondaryAxis]) {
return (a[secondaryAxis] - b[secondaryAxis]);
} else {
return (b[secondaryAxis] - a[secondaryAxis]);
}
}
var tiles = [];
for (var i = 0; i < puzzle.tiles.length; i++) {
for (var j = 0; j < puzzle.tiles[i].length; j++) {
var tile = puzzle.tiles[i][j];
if (tile.isEmpty()) {
continue;
}
if (tile[mainAxis] === this[mainAxis]) {
if (this[secondaryAxis] > puzzle.tiles.empty[secondaryAxis]) {
if (tile[secondaryAxis] > puzzle.tiles.empty[secondaryAxis]
&& tile[secondaryAxis] <= this[secondaryAxis]) {
tiles.push(tile);
}
} else {
if (tile[secondaryAxis] < puzzle.tiles.empty[secondaryAxis]
&& tile[secondaryAxis] >= this[secondaryAxis]) {
tiles.push(tile);
}
}
}
}
}
tiles.sort(sortTiles.bind(this));
puzzle.moving = tiles.length;
tiles.forEach(function(item) {
item.move(false);
});
};
Tile.prototype.canLineMove = function(property) {
var empty = puzzle.tiles.empty;
return (this !== puzzle.tiles.empty /* don't consider empty tile as moveable */
&& empty[property] === this[property]);
};
Tile.prototype.canMove = function() {
var empty = puzzle.tiles.empty;
return (this !== puzzle.tiles.empty /* don't consider empty tile as moveable */
&& Math.abs(empty.x - this.x) + Math.abs(empty.y - this.y) === 1);
};
Tile.prototype.reposition = function() {
var x, y, oldX, oldY, peekX, peekY, elm;
function realReposition(e) {
e.stopPropagation();
elm.classList.remove('peek');
elm.style.webkitTransform = 'translateX(' + x + 'px) translateY(' + y + 'px)';
elm.style.transform = 'translateX(' + x + 'px) translateY(' + y + 'px)';
elm.removeEventListener('transitionend', realReposition);
elm.removeEventListener('webkitTransitionEnd', realReposition);
}
if (this.isEmpty()) {
return;
}
elm = puzzle.tilesElements[this.id];
x = this.x * (puzzle.width / puzzle.infos.tilesCount);
y = this.y * (puzzle.height / puzzle.infos.tilesCount);
// move 1% in the right direction before doing the real move, to avoid
// ugly flickering with Firefox OS
elm.addEventListener('transitionend', realReposition);
elm.addEventListener('webkitTransitionEnd', realReposition);
oldX = this.originalX * (puzzle.width / puzzle.infos.tilesCount);
oldY = this.originalY * (puzzle.height / puzzle.infos.tilesCount);
peekX = Math.round(oldX + (x - oldX) / 100);
peekY = Math.round(oldY + (y - oldY) / 100);
elm.classList.add('peek');
elm.offsetLeft; // Force browser to acknowledge it needs to make a transition
elm.style.webkitTransform = 'translateX(' + peekX + 'px) translateY(' + peekY + 'px)';
elm.style.transform = 'translateX(' + peekX + 'px) translateY(' + peekY + 'px)';
};
puzzle.init = function(file) {
var fileReader = new FileReader();
fileReader.onload = function(e) {
// Store file in localStorage to be able to resume game later.
// FIXME: refuse files too big ?
localStorage['puzzleSource'] = e.target.result;
};
fileReader.readAsDataURL(file);
puzzle.initVars();
puzzle.initElements(window.URL.createObjectURL(file), file.type, function() {
hideGui();
puzzle.shuffle();
});
};
puzzle.initVars = function() {
puzzle.infos.solved = false;
puzzle.infos.tilesCount = 3;
puzzle.infos.difficulty = 42;
puzzle.infos.movesCount = 0;
puzzle.infos.timePaused = null;
puzzle.infos.timeSpentPausing = 0;
puzzle.infos.timeStarted = (new Date()).getTime();
};
puzzle.initElements = function(fileURL, fileType, callback) {
var container = document.createElement('div');
container.id = 'container';
document.body.appendChild(container);
puzzle.height = window.innerHeight;
puzzle.width = window.innerWidth;
puzzle.createTiles();
if (fileType.split('/')[0] === 'video') {
puzzle.img = document.createElement('video');
puzzle.img.addEventListener("loadeddata", function() {
puzzle.img.width = puzzle.img.videoWidth;
puzzle.img.width = puzzle.img.videoWidth;
puzzle.initialDraw();
callback();
});
puzzle.img.autoplay = true;
puzzle.img.volume = 0; // muted doesn't seem to be enough for chrome
puzzle.img.muted = true;
puzzle.img.loop = true;
puzzle.redrawCallback = function() {
requestAnimationFrame(puzzle.redraw.bind(puzzle, false));
};
puzzle.img.src = fileURL;
} else {
puzzle.redrawCallback = null;
puzzle.img = new Image();
puzzle.img.onload = function() {
puzzle.initialDraw();
callback();
};
puzzle.img.src = fileURL;
}
container.addEventListener('mousedown', puzzle.showPauseScreen);
container.addEventListener('touchstart', puzzle.showPauseScreen);
};
puzzle.resumeGame = function() {
var puzzleInfos = JSON.parse(localStorage['puzzleInfos']);
var puzzleTiles = JSON.parse(localStorage['puzzleTiles']);
var start, end, fileType, fileURL;
// FIXME: refactor w/ init()
puzzle.initVars();
fileURL = localStorage['puzzleSource']
start = fileURL.indexOf(':') + 1;
end = fileURL.indexOf(';');
fileType = fileURL.slice(start, end);
puzzle.initElements(fileURL, fileType, function() {
puzzle.infos = puzzleInfos;
puzzle.infos.timeSpentPausing += (new Date()).getTime() - puzzle.infos.timePaused;
for (var i = 0; i < puzzleTiles.length; i++) {
for (var j = 0; j < puzzleTiles[i].length; j++) {
puzzle.tiles[i][j].x = puzzleTiles[i][j].x;
puzzle.tiles[i][j].y = puzzleTiles[i][j].y;
puzzle.tiles[i][j].reposition();
}
}
hideGui();
});
};
puzzle.showPauseScreen = function(e) {
var t;
puzzle.infos.timePaused = (new Date()).getTime();
t = puzzle.infos.timePaused - puzzle.infos.timeStarted - puzzle.infos.timeSpentPausing;
if (typeof puzzle.img.pause !== 'undefined') {
puzzle.img.pause();
}
localStorage['puzzleInfos'] = JSON.stringify(puzzle.infos);
document.getElementById('elapsed').textContent = Math.round(t / 1000);
document.getElementById('moves').textContent = puzzle.infos.movesCount;
document.getElementById('paused').style.display = 'block';
e.preventDefault();
e.stopPropagation();
};
puzzle.hidePauseScreen = function(e) {
puzzle.infos.timeSpentPausing += (new Date()).getTime() - puzzle.infos.timePaused;
if (typeof puzzle.img.play !== 'undefined') {
puzzle.img.play();
}
localStorage['puzzleInfos'] = JSON.stringify(puzzle.infos);
document.getElementById('paused').style.display = 'none';
e.preventDefault();
e.stopPropagation();
};
puzzle.initialDraw = function() {
function transitionEnd(e) {
if (e.target.tagName.toLowerCase() !== 'canvas') {
return;
}
if (puzzle.moving > 0) {
puzzle.moving--;
}
if (!puzzle.moving) {
document.body.classList.remove('moving');
puzzle.checkSolved();
}
}
puzzle.initCanvas();
puzzle.redraw(true);
puzzle.moving = 0;
document.body.addEventListener('transitionend', transitionEnd);
document.body.addEventListener('webkitTransitionEnd', transitionEnd);
document.defaultView.addEventListener('resize', function() {
puzzle.redraw(true);
});
// FIXME: is "resize" enough ?
//document.defaultView.addEventListener("deviceorientation", puzzle.redraw, true);
};
puzzle.initCanvas = function() {
if (puzzle.canvas) {
return puzzle.canvas;
}
puzzle.canvas = document.createElement('canvas');
puzzle.canvas.className = 'hidden';
puzzle.canvas.id = 'finished';
puzzle.canvas.addEventListener('mousedown', puzzle.newGame);
puzzle.canvas.addEventListener('touchstart', puzzle.newGame);
document.body.appendChild(puzzle.canvas);
return puzzle.canvas;
};
puzzle.redraw = function(reposition) {
document.body.classList.add('moving');
var img = puzzle.img;
var ctx = null, puzzleOrientation = null, imageOrientation = null;
var canvas = puzzle.canvas;
puzzle.height = window.innerHeight;
puzzle.width = window.innerWidth;
canvas.width = puzzle.width;
canvas.height = puzzle.height;
ctx = canvas.getContext('2d');
if (puzzle.width !== puzzle.height) {
puzzleOrientation = (puzzle.width / puzzle.height) > 1;
}
if (img.width !== img.height) {
imageOrientation = (img.width / img.height) > 1;
}
if (puzzleOrientation !== null && imageOrientation !== null &&
imageOrientation !== puzzleOrientation) {
// If image orientation and puzzle orientation differ - and we are not
// drawing to/from a square - we need to rotate the canvas 90 deg to
// display the image.
ctx.rotate(90 * Math.PI / 180);
ctx.translate(0, -canvas.width);
ctx.drawImage(img, 0, 0, canvas.height, canvas.width);
} else {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
for (var i = 0; i < puzzle.tiles.length; i++) {
for (var j = 0; j < puzzle.tiles[i].length; j++) {
if (i === puzzle.tiles.length -1 && j === puzzle.tiles[i].length -1) {
continue;
}
var tile = puzzle.tiles[i][j];
var elm = puzzle.tilesElements[tile.id];
elm.width = puzzle.width / puzzle.infos.tilesCount;
elm.height = puzzle.height / puzzle.infos.tilesCount;
var sx = i * puzzle.width / puzzle.infos.tilesCount;
var sy = j * puzzle.height / puzzle.infos.tilesCount;
var sw = canvas.width / puzzle.infos.tilesCount;
var sh = canvas.height / puzzle.infos.tilesCount;
var dx = 0;
var dy = 0;
var dw = elm.width;
var dh = elm.height;
ctx = elm.getContext('2d');
ctx.drawImage(canvas, sx, sy, sw, sh, dx, dy, dw, dh);
ctx.strokeRect(0, 0, dw, dh);
if (reposition) {
tile.reposition();
}
}
}
document.body.classList.remove('moving');
if (puzzle.redrawCallback) {
puzzle.redrawCallback();
}
};
puzzle.shuffle = function(difficulty) {
var current = null;
var movesToMake = difficulty || puzzle.infos.difficulty;
function getAvailableTiles() {
var availableTiles = [];
for (var i = 0; i < puzzle.tiles.length; i++) {
for (var j = 0; j < puzzle.tiles[i].length; j++) {
var tile = puzzle.tiles[i][j];
if (tile.canMove() && tile !== current) {
availableTiles.push(tile);
}
}
}
return availableTiles;
}
for (var d = 0; d < movesToMake; d++) {
var available = getAvailableTiles();
var i = Math.round(Math.random() * (available.length - 1));
current = available[i];
current.move(false);
}
localStorage['puzzleInfos'] = JSON.stringify(puzzle.infos);
};
puzzle.checkSolved = function() {
if (puzzle.infos.solved) {
return;
}
var solved = true;
for (var i = 0; i < puzzle.tiles.length; i++) {
for (var j = 0; j < puzzle.tiles[i].length; j++) {
var tile = puzzle.tiles[i][j];
if (i !== tile.x || j !== tile.y) {
solved = false;
break;
}
}
}
if (solved) {
puzzle.resetStorage();
document.getElementById('finished').classList.remove('hidden');
alert('Congratulations!');
puzzle.infos.solved = solved;
}
};
puzzle.resetStorage = function() {
delete localStorage['puzzleInfos'];
delete localStorage['puzzleSource'];
delete localStorage['puzzleTiles'];
};
puzzle.newGame = function(e) {
puzzle.resetStorage();
var container = document.getElementById('container');
var paused = document.getElementById('paused');
paused.style.display = 'none';
container.parentNode.removeChild(container, true);
puzzle.canvas.classList.add('hidden');
if (typeof puzzle.img.pause !== 'undefined') {
puzzle.img.pause();
}
e.preventDefault();
e.stopPropagation();
showGui();
};
puzzle.restartGame = function(e) {
var paused = document.getElementById('paused');
paused.style.display = 'none';
for (var i = 0; i < puzzle.tiles.length; i++) {
for (var j = 0; j < puzzle.tiles[i].length; j++) {
var tile = puzzle.tiles[i][j];
tile.x = i;
tile.y = j;
}
}
puzzle.initVars();
puzzle.initialDraw();
puzzle.shuffle();
e.preventDefault();
e.stopPropagation();
};
puzzle.createTiles = function() {
var container = document.getElementById('container');
var tile, elm;
puzzle.tiles = new Array(puzzle.infos.tilesCount);
puzzle.tilesElements = {};
puzzle.tiles.empty = new Tile(puzzle.infos.tilesCount - 1, puzzle.infos.tilesCount - 1);
for (var i = 0; i < puzzle.infos.tilesCount; i++) {
puzzle.tiles[i] = new Array(puzzle.infos.tilesCount);
for (var j = 0; j < puzzle.infos.tilesCount; j++) {
if (i === puzzle.tiles.empty.x && j === puzzle.tiles.empty.y) {
puzzle.tiles[i][j] = puzzle.tiles.empty;
} else {
tile = new Tile(i, j);
elm = document.createElement('canvas');
elm.className = 'tile';
elm.id = tile.id;
elm.addEventListener('touchstart', tile.eventHandler.bind(tile));
elm.addEventListener('mousedown', tile.eventHandler.bind(tile));
container.appendChild(elm);
puzzle.tiles[i][j] = tile
puzzle.tilesElements[elm.id] = elm;
}
}
}
};