<!DOCTYPE html>
<html>
<head>
<!--
Fifteen Puzzle
Version: 0.101
Date: 2012-02-25
Author: Alberto Burgos <[email protected]>
An online version can be found at:
http://albertoonsoftware.com/fifteenpuzzle
-->
<meta charset="utf-8" />
<title>Fifteen puzzle</title>
<style type="text/css">
body {
font-family: Arial, Serif;
margin: 0;
}
.container {
margin: 20px auto 35px;
width: 500px;
}
#board {
margin: 0 auto;
font-size: 20px;
}
.infoPanel {
margin-top: 50px;
}
.odd, .even {
float: left;
width: 60px;
height: 60px;
margin: 2px;
line-height: 60px;
text-align: center;
border: 1px solid black;
-webkit-border-radius: 7px;
-moz-border-radius: 7px;
border-radius: 7px;
}
.even {
background-color: #0E00D3;
}
.odd {
background-color: #F8CD0B;
}
#times {
width: 500px;
max-width: 500px;
min-height: 80px;
}
#state {
color: #007700;
}
#state, #time, #movs {
font-weight: bold;
}
.simpleTable {
border-spacing: 0;
border-collapse: collapse;
}
.simpleTable td {
padding: 0 10px 0 0;
}
.right {
float: right;
}
.left {
float: left;
}
.clear {
clear: both;
}
</style>
<script type="text/javascript">
var puzzle;
var tiles;
var labels;
var solves;
function init(side)
{
Array.prototype.avg = function() {
for(var i=0, sum=0; i < this.length; sum += this[i++]);
return sum/this.length;
}
labels =
{
state: document.getElementById('state'),
movs: document.getElementById('movs'),
time: document.getElementById('time'),
times: document.getElementById('times'),
best: document.getElementById('best'),
worst: document.getElementById('worst')
};
document.addEventListener('keydown', keyHandler);
reset(side);
}
function reset(side)
{
puzzle =
{
side: parseInt(side),
lastTile: side*side,
gap: side*side,
state: 'solved',
};
labels.state.textContent = 'solved';
labels.state.style.color = '#007700';
solves =
{
// Last solve data
movs: -1,
iniTime: -1,
endTime: -1,
// All solves data
best: null,
worst: null,
times: new Array()
};
var board = document.getElementById('board');
board.innerHTML = '';
board.style.width = puzzle.side*66+'px';
board.style.height = puzzle.side*66+'px';
tiles = new Array();
var tile;
for(var i = 1; i <= puzzle.lastTile; i++)
{
tiles[i] = document.createElement('div');
tiles[i].className = i % 2 != 0 ? 'odd' : 'even';
tiles[i].id = 't'+i;
tiles[i].textContent = i;
board.appendChild(tiles[i]);
}
tiles[puzzle.lastTile].style.visibility = 'hidden';
}
function solve()
{
for(var i=1; i <= puzzle.lastTile; i++)
{
tiles[i].textContent = i;
tiles[i].className = i % 2 != 0 ? 'odd' : 'even';
tiles[i].style.visibility = 'visible';
}
tiles[puzzle.lastTile].style.visibility = 'hidden';
puzzle.gap = puzzle.lastTile;
puzzle.state = 'solved';
labels.state.textContent = 'solved';
labels.state.style.color = '#007700';
}
function keyHandler(e)
{
var keyCode = e.keyCode;
if (keyCode > 36 && keyCode < 41 || keyCode == 32)
e.preventDefault();
if (keyCode == 37 || keyCode == 65)
move(2);
else if (keyCode == 38 || keyCode == 87)
move(3);
else if (keyCode == 39 || keyCode == 68)
move(0);
else if (keyCode == 40 || keyCode == 83)
move(1);
else if (keyCode == 32 && puzzle.state == 'solved')
scramble();
else if (keyCode == 27)
solve();
}
function scramble()
{
var max = Math.pow(puzzle.side, 4);
var count = 0, movement, forbidden=0;
while (count < max)
{
movement = Math.floor(Math.random()*4);
if (movement != forbidden && move(movement) == true)
{
count++;
forbidden = (movement+2) % 4;
}
}
puzzle.state = 'scrambled';
labels.state.textContent = 'scrambled';
labels.state.style.color = '#0055dd';
}
function move(m)
{
if(m == 0) // Left
{
if (puzzle.gap % puzzle.side == 0) return false;
else swapTiles(puzzle.gap+1, puzzle.gap);
}
else if (m == 1) // Up
{
if (puzzle.gap > puzzle.side*(puzzle.side-1)) return false;
else swapTiles(puzzle.gap+puzzle.side, puzzle.gap);
}
else if (m == 2) // Right
{
if ((puzzle.gap+puzzle.side-1) % puzzle.side == 0) return false;
else swapTiles(puzzle.gap-1, puzzle.gap);
}
else if (m == 3) // Down
{
if (puzzle.gap < puzzle.side+1) return false;
else swapTiles(puzzle.gap-puzzle.side, puzzle.gap);
}
if(puzzle.state == 'scrambled')
{
puzzle.state = 'solving';
labels.state.textContent = 'solving';
labels.state.style.color = "#aa0000";
solves.movs = 0;
solves.iniTime = new Date();
}
if(puzzle.state == 'solving')
{
solves.movs++;
if (isSolved())
{
solves.endTime = new Date();
puzzle.state = 'solved';
var newTime = (solves.endTime.getTime() - solves.iniTime.getTime())/1000;
solves.times.push(newTime);
if(solves.best == null)
labels.best.textContent = labels.worst.textContent
= solves.best = solves.worst = newTime;
else if(solves.best > newTime)
labels.best.textContent = solves.best = newTime;
else if(solves.worst < newTime)
labels.worst.textContent = solves.worst = newTime;
labels.state.textContent = 'solved';
labels.state.style.color = '#00ff00';
labels.movs.textContent = solves.movs;
labels.time.textContent = newTime.toFixed(3);
labels.times.textContent = solves.times.toString();
document.getElementById('average').textContent = solves.times.avg().toFixed(3);
}
}
return true;
}
function isSolved()
{
for (var i = 1; i < puzzle.lastTile; i++)
if (i != tiles[i].textContent)
return false;
return true;
}
function swapTiles(a, b)
{
var aux = tiles[a].textContent;
tiles[a].textContent = tiles[b].textContent;
tiles[b].textContent = aux;
tiles[a].style.visibility = 'hidden';
tiles[b].style.visibility = 'visible';
tiles[a].className = tiles[a].textContent % 2 != 0 ? 'odd' : 'even';
tiles[b].className = tiles[b].textContent % 2 != 0 ? 'odd' : 'even';
puzzle.gap = a;
}
</script>
</head>
<body onload="init(4)">
<div class="container">
<div id="board"></div>
<div class="infoPanel">
<div class="right">
Puzzle side:
<select onchange="reset(this.value)">
<option value="2">2</option>
<option value="3">3</option>
<option value="4" selected="selected">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</div>
<table class="simpleTable left">
<tr>
<td>State:</td>
<td id="state" style="width: 120px">solved</td>
</tr>
<tr>
<td>Time:</td>
<td id="time"></td>
</tr>
<tr>
<td>Movs:</td>
<td id="movs"></td>
</tr>
</table>
<table class="simpleTable left">
<tr>
<td>Best:</td>
<td id="best"></td>
</tr>
<tr>
<td>Worst:</td>
<td id="worst"></td>
</tr>
<tr>
<td>Mean:</td>
<td id="average"></td>
</tr>
</table>
<div class="clear"></div>
<br/>
Times:<br/><textarea id="times" readonly="readonly" cols="1" rows="1"></textarea>
<br/>
<br/>
<b>Controls</b>
<table class="simpleTable">
<tr>
<td>Move tile:</td>
<td>Arrows or WASD</td>
</tr>
<tr>
<td>Scramble:</td>
<td>Space</td>
</tr>
<tr>
<td>Solve:</td>
<td>Escape</td>
</tr>
</table>
</div> <!-- End of infoPanel -->
</div> <!-- End of container -->
</body>
</html>