JSFiddle - React, Tailwind, and code Playground

HTML

← → ↑ ↓

CSS

div {
  margin-top: 20px;
  width: 456px;
  border: solid 1px transparent;
}

div > div {
  display: inline-block;
  width: 100px;
  height: 100px;
  border: solid 2px gray;
  margin: 5px;
  text-align: center;
  font: bold 24px/100px Tahoma,sans;
  border-radius: 10px;
  background-color: whitesmoke;
}

JavaScript

let box = document.body.appendChild(document.createElement('div'));
let fifteen = {
	// задаем слагаемое для вычисления нового индекса перемещаемого элемента 
  Move: { up: -4, left: -1, down: 4, right: 1 },
  // индекс пустого элемента
  hole: 15,
  // задаем массив элементов пятнашек, сортируем случайным образом, добавляем 0 (пустая клетка)
  order: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
  				.sort(() => Math.random()-0.5)
          .concat(0),
  // проверка на завершенность комбинации
  // пробуем найти хоть один элемент, индекс которого(+1) не совпадает с его значением
  // если есть хоть один элемент, стоящий не на месте, то комбинация не решена
  // иначе пятнашки собраны
  isCompleted() { 
  	return !this.order.some((item, i) => item > 0 && item-1 !== i); 
  },
  go(move) {
    let index = this.hole + move;
    if (!this.order[index]) {
    	return false;
    }
    if (move == fifteen.Move.left || move == fifteen.Move.right) {
      if (Math.floor(this.hole/4) !== Math.floor(index/4)) {
      	return false;
      }
    }
    this.swap(index, this.hole);
    this.hole = index;
    return true; 
  },
  // меняем местами элементы с индексами i1 и i2
  swap(i1, i2) { 
  	let t = this.order[i1]; 
    this.order[i1] = this.order[i2]; 
    this.order[i2] = t; 
  },
  // проверка расклада пятнашек в массиве а на решаемость
  // считаем количество пар чисел, в которых предыдущее число больше последующего
  // это Параметр беспорядка, если он четный - комбинация решаема
  solvable(a) {
    let kDiff = 0;
    let len = a.length-1;
    for (let i = 1; i < len; i++) {
      for (let j = i-1; j >= 0; j--) {
      	if (a[j] > a[i]) {
      		kDiff++;
        }
      }
    }
    return !(kDiff % 2); 
  } 
};

// если комбинация не решаема меняем местами первый и второй элементы
// таким образом параметр беспорядка изменится на 1 и станет четным
if (!fifteen.solvable(fifteen.order)) {
	fifteen.swap(0, 1);
}

// добавляем на страницу 16 блоков
for (var i = 0;...