JSFiddle - React, Tailwind, and code Playground

by Eminence

HTML

<button id="solve">Solve</button>
<button id="reset">Reset</button>
<div id="game">
  <div class="peg" data-x="0">
    <div class="stem"></div>
    <div class="root"></div>
  </div>
  <div class="peg" data-x="1">
    <div class="stem"></div>
    <div class="root"></div>
  </div>
  <div class="peg" data-x="2">
    <div class="stem"></div>
    <div class="root"></div>
  </div>
  <div class="disk" data-size="0" data-x="0" data-y="0"></div>
  <div class="disk" data-size="1" data-x="0" data-y="1"></div>
  <div class="disk" data-size="2" data-x="0" data-y="2"></div>
  <div class="disk top" data-size="3" data-x="0" data-y="3"></div>
</div>

<div id="instr"><b>Instructions</b>: The goal is to get all of the disks from the first peg to the last one, stacked from the largest disk at the bottom to the smallest disk at the top. However, you can only move a disk to another peg if the disk is not under some other
  disks, and if the peg is empty or there is a disk already on the peg that is larger than the one you are attempting to place there.</div>
<br>
<br>
<h2>1. Number of Steps</h2>
<div id="intro">
  <p>The least number of steps you need to complete the game above is 15. For the same game with three disks, you will need at least 7 steps, and for 2 disks you need just 3. You may have noticed by now that these numbers correspond directly to the number
    of disks. More specifically, the minimum number of steps for a game with <i>n</i> disks is 2^<i>n</i> - 1. But, how do we know this? And why is it this specific number?</p>
</div>
<br>
<h2>2. Proof</h2>
<div class="proof">
  <h3>2.1. Base Case</h3>
  <p>We can easily show why we need at least 2^<i>n</i> - 1 steps for a game with n disks by using a proof method called "<a href="https://en.wikipedia.org/wiki/Mathematical_induction">Induction</a>", which is essentially proving it for one case (called
    the "base case"), and showing how this proof applies for all other cases.</p>

  <p>We will take the case where the number...

CSS

body {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-family: sans-serif;
}

#game {
  position: absolute;
  width: 500px;
  height: 300px;
  top: 50px;
  background: #eee;
}

#instr {
  margin-top: 340px;
}

.imagePair {
  margin-left: auto;
  margin-right: auto;
}

.peg {
  position: absolute;
  width: 150px;
  height: 125px;
  top: 75px;
}

.peg .hover {
  background: #ddd;
}

.peg .stem {
  position: relative;
  left: 70px;
  width: 10px;
  height: 100%;
  background: tan;
}

.peg .root {
  position: relative;
  bottom: 0;
  width: 100%;
  height: 10px;
  background: tan;
}


/* 15 + (150 + 10) * i */

[data-x='0'] {
  left: 15px;
}

[data-x='1'] {
  left: 175px;
}

[data-x='2'] {
  left: 335px;
}

.solving .disk {
  transition-duration: 0.5s;
}

.disk {
  position: absolute;
  height: 25px;
  border-radius: 4px;
}

.disk.top {
  cursor: pointer;
}

.disk.top:hover {
  transform: rotate(2deg);
}

.disk[data-size='0'] {
  width: 150px;
  background: red;
}

.disk[data-size='1'] {
  width: 120px;
  margin-left: 15px;
  background: green;
}

.disk[data-size='2'] {
  width: 90px;
  margin-left: 30px;
  background: blue;
}

.disk[data-size='3'] {
  width: 60px;
  margin-left: 45px;
  background: orange;
}


/* 75 + 125 - (25 + 1) * (i + 1) */

.disk[data-y='0'] {
  top: 174px;
}

.disk[data-y='1'] {
  top: 148px;
}

.disk[data-y='2'] {
  top: 122px;
}

.disk[data-y='3'] {
  top: 96px;
}

JavaScript

var isSolving = false;

$('.disk').draggable({
  stack: '.disk',
  start: function() {
    return $(this).hasClass('top') && !$('#game').hasClass('solving');
  },
  // o and e are irrelavent to the function
  stop: function(o, e) {
    var $disk = $(this),
        pegs = $('.peg').sort(byDistanceFromX(getCenterX($disk)));
    for (var i = 0; i < pegs.length; i++) {
      var $peg = $(pegs[i]),
          pegX = getX($peg),
          rowY = whichRow($disk, pegX);
      if (rowY !== false) {
        $disk.attr({
          'data-x': pegX,
          'data-y': rowY
        });
        break;
      }
    }
    $disk.attr('style', '');
    resetTops();
  }
});

$('#reset').click(reset);
$('#solve').click(solve);

// call to detect which disks should have the "top" attribute
function resetTops() {
  $('.top.disk').removeClass('top');
  for (var i = 0; i < 3; i++) {
    var discs = $('.disk[data-x="' + i + '"').sort(bySize);
    if (discs.size() > 0) {
      $(discs[0]).addClass('top');
    }
  }
}

function getCenterX($disk) {
  var a = $disk.position().left,
    	b = parseInt($disk.css('margin-left').replace('px', ''), 10),
    	c = $disk.width() / 2;
	return a + b + c;
}

function whichRow($disk, pegX) {
  var $topDisk = $($('.disk[data-x="' + pegX + '"').not($disk).sort(bySize).get(0));
  if ($topDisk.size() !== 0) {
  	if (getSize($disk) < getSize($topDisk)) {
    	return false;
    }
    return getY($topDisk) + 1;
  }
  return 0;
}

// sort descending
function bySize(A, B) {
  var a = getSize($(A)),
      b = getSize($(B));
  if (a < b) {
  	return 1;
  }
  return a > b ? -1 : 0;
}

function byDistanceFromX(x) {
  return function(A, B) {
    var a = Math.abs(getCenterX($(A)) - x),
        b = Math.abs(getCenterX($(B)) - x);
    if (a < b) {
    	return -1;
    } 
   	return a > b ? 1 : 0;
  };
}

function getSize($d) {
  return parseInt($d.attr('data-size'), 10);
}

function getX($d) {
  return parseInt($d.attr('data-x'), 10);
}

function getY($d) {
  return...