JSFiddle - React, Tailwind, and code Playground

HTML

<h2>Click a cell to distribute players</h2>
<table id="heat">

</table>

CSS

body {
  font-family: sans-serif;
}

h2 {
  margin: 1ex 0;
}

td {
  border: 1px solid #0af;
  padding: 0.5ex;
  font-family: monospace;
  font-size: 10px;
  max-width: 4em;
  height: 4em;
  overflow: hidden;
  text-overflow: ellipsis;
}

td.target {
  border-color: #f80;
}

td.player {
  border-color: black;
}

td.player::after {
  font-family: sans-serif;
  content: "player here";
  position: absolute;
  color: white;
  background-color: rgba(0, 0, 0, 0.5);
  font-weight: bold;
  padding: 2px;
}

JavaScript

var numPlayers = 5;
var numTargets = numPlayers;
var gridSize = numPlayers * 4;
var minDistance = 4;

var targetPositions = [];
for (var i = 0; i < numTargets; i++) {
	// TODO: Make sure targets don't get too close
  targetPositions[i] = randomPos();
}

var heatMap = [];
for (var i = 0; i < gridSize; i++) {
  heatMap[i] = [];
  for (var j = 0; j < gridSize; j++) {
    heatMap[i][j] = heat(i, j);
  }
}
printHeat();

function heat(x, y) {
  var result = 0;
  for (var i in targetPositions) {
    var pos = targetPositions[i];
    result += 1 / distance(x - pos.x, y - pos.y); // XXX: What about zero division?
  }
  return result;
}

function distance(l1, l2) {
  // manhattan distance
  return Math.abs(l1) + Math.abs(l2);
}

function randomPos() {
  return {
    x: random(gridSize),
    y: random(gridSize),
    toString: function() {
      return this.x + '/' + this.y
    }
  };

  function random(max) {
    return Math.floor(Math.random() * max);
  }
}

function printHeat() {
  for (var i = 0; i < gridSize; i++) {
    var tr = $('<tr>');
    $('#heat').append(tr);
    for (var j = 0; j < gridSize; j++) {
      var heatVal = heatMap[i][j];
      var td = $('<td> ' + heatVal + ' </td>');
      if (heatVal > numTargets) // hack
        td.addClass('target');
      td.attr('data-x', i).attr('data-y', j);
      td.css('background-color', 'rgb(' + Math.floor(heatVal * 255) + ',160,80)');
      tr.append(td);
    }
  }
}

var cellsSorted = $('td').sort(function(a, b) {
  return numOfCell(a) > numOfCell(b);
}).toArray();
$('td').click(function() {
  $('.player').removeClass('player');
  var index = cellsSorted.indexOf(this);
  // TODO: Don't just search downwards, but in both directions with lowest difference
  for (var k = 0; k < numPlayers; k++) {
    var newIndex = index - k; // XXX Check against outOfBounds
    var cell = cellsSorted[newIndex];
    if (!validPlayerCell(cell)) {
      // skip one
      k--;
      index--;
      continue;
    }
   ...