Bot Factory KoTH (2.0)

The Caveman (151 bytes)

This is Caveman. Very primitive. Hunt if strong. Eat if weak. Ooga-Booga.

{
  name: "The Caveman",
  color: "#FF0000",
  run:()=>{w=bots().sort((a,b)=>a.score-b.score)[0];return self().score<=w.score?dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos):dirTo(w.pos)}
}

Try against a bunch of Honnolds:

var botData = [
  {
name: "Honnold",
color: "#0000FF",
run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)
  },
  {
name: "Honnold",
color: "#0000FF",
run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)
  },
  {
name: "Honnold",
color: "#0000FF",
run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)
  },

  {
    name: "The Caveman",
    color: "#FF0000",
    run:()=>{w=bots().sort((a,b)=>a.score-b.score)[0];return self().score<=w.score?dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos):dirTo(w.pos)}
  },

  {
    name: "The Caveman",
    color: "#FF0000",
    run:()=>{w=bots().sort((a,b)=>a.score-b.score)[0];return self().score<=w.score?dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos):dirTo(w.pos)}
  },

  {
    name: "The Caveman",
    color: "#FF0000",
    run:()=>{w=bots().sort((a,b)=>a.score-b.score)[0];return self().score<=w.score?dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos):dirTo(w.pos)}
  }
];


{
var game = {
  randPos: (center, any = !1, uid = 0, owner = 0, p = 0.1) => {
      var theta, radius, pos;
      
      do {
          theta = Math.random() * Math.PI * 2;
          radius = 0;
          
          while (Math.random() > p)
              radius++;
          
          pos = [Math.trunc(center[0] + Math.cos(theta) * radius), Math.trunc(center[1] + Math.sin(theta) * radius)];
      } while (!any && game.bots.find(a => a && a.uid != uid && Math.abs(a.pos[0] - pos[0]) + Math.abs(a.pos[1] - pos[1]) < (a.uid == owner ? 3 : 4)));
      
      return pos;
  },
  debug: function(){},
  log: 0 // 0 = NONE, 1 = SUMMARY, 2 = ALL
};

var north = () => ["north"];
var east = () => ["east"];
var south = () => ["south"];
var west = () => ["west"];

var build = code => ["worker", code];

var drop = {
  north: char => ["drop.north", char],
  east: char => ["drop.east", char],
  south: char => ["drop.south", char],
  west: char => ["drop.west", char]
};

var bots = () => game.bots.map(a => ({
  uid: a.uid,
  owner: a.owner,
  original: a.original,
  score: a.score,
  pos: [...a.pos],
  chars: game.uid == a.uid ? [...a.chars] : undefined,
  source: game.uid == a.uid ? a.source : undefined
})).sort((a, b) => a.uid - b.uid);

var chars = () => game.chars.map(a => ({
  char: a.char,
  pos: [...a.pos]
}));

var self = () => {
  var bot = game.bots.find(a => a.uid == game.uid);
  return bot ? {
      uid: bot.uid,
      owner: bot.owner,
      original: bot.original,
      score: bot.score,
      pos: [...bot.pos],
      chars: [...bot.chars],
      source: bot.source
  } : null;
};

var owner = () => {
  var bot = game.bots.find(a => a.uid == game.bots.find(b => b.uid == game.uid).owner);
  return bot ? {
      uid: bot.uid,
      owner: bot.owner,
      original: bot.original,
      score: bot.score,
      pos: [...bot.pos],
      chars: [...bot.chars],
      source: bot.source
  } : null;
};

var center = () => game.center;
var turn = () => game.turns;

var at = pos => ({
  bot: (game.bots.find(b => b.pos[0] == pos[0] && b.pos[1] == pos[1]) || {uid: null}).uid,
  chars: chars().filter(c => c.pos[0] == pos[0] && c.pos[1] == pos[1])
});

var dir = (posFrom, pos) => {
  if (Math.abs(posFrom[0] - pos[0]) <= Math.abs(posFrom[1] - pos[1]))
      return posFrom[1] < pos[1] ? ["north"] : ["south"];
  else
      return posFrom[0] < pos[0] ? ["west"] : ["east"];
};

var dirTo = pos => {
  var bot = game.bots.find(a => a.uid == game.uid);
  if (Math.abs(pos[0] - bot.pos[0]) <= Math.abs(pos[1] - bot.pos[1]))
      return pos[1] < bot.pos[1] ? ["north"] : ["south"];
  else
      return pos[0] < bot.pos[0] ? ["west"] : ["east"];
};

var dist = (posFrom, pos) => {
  return Math.abs(posFrom[0] - pos[0]) + Math.abs(posFrom[1] - pos[1]);
};

var distTo = pos => {
  var bot = game.bots.find(a => a.uid == game.uid);
  return Math.abs(pos[0] - bot.pos[0]) + Math.abs(pos[1] - bot.pos[1]);
};

async function runRound(turns = 100000) {
  var uids = [];
  
  game.perf = performance.now();
  
  for (let i = 1; i <= botData.length; i++)
      uids[i - 1] = i;
  
  for (let j, i = uids.length - 1; i > 0; i--) {
      j = Math.floor(Math.random() * (i + 1));
      [uids[i], uids[j]] = [uids[j], uids[i]];
  }
  
  game.bots = [];
  game.chars = [];
  game.records = game.records || [];
  game.uids = [];
  
  for (let i = 0; i < botData.length; i++) {
      game.bots[i] = {
          uid: uids[i],
          owner: uids[i],
          original: uids[i],
          score: Math.floor(botData[i].run.toString().length * -1 / 2),
          chars: [],
          pos: game.randPos([0, 0]),
          source: botData[i].run.toString(),
          run: botData[i].run,
          storage: {},
          name: botData[i].name || "Bot",
          color: botData[i].color || "#000000"
      };
      
      game.uids[uids[i]] = i;
      game.records[i] = game.records[i] || 0;
  }
  
  game.center = [
      game.bots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0),
      game.bots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0)
  ];
  
  game.charPool = game.bots.map(a => a.source).join("");
  
  for (let i = 0; i < botData.length * 4; i++)
      game.chars.push({
          char: game.charPool[Math.random() * game.charPool.length | 0],
          pos: game.randPos([0, 0]),
          game: !0
      });
  
  game.cuid = botData.length + 1;
  game.turns = 0;
  
  if (!game.fps) {
      while (game.chars.length && game.bots.length && game.turns < turns) {
          runTurn();
          
          game.turns++;
      }
  } else {
      game.debug();
      
      while (game.chars.length && game.bots.length && game.turns < turns) {
          await new Promise(function(resolve) {
              setTimeout(resolve, 1000 / game.fps);
          });
          
          if (!game.pause) {
              runTurn();
              
              game.debug();
              game.turns++;
          }
      }
  }
  bots().map(b => game.records[game.uids[b.original]] += b.score);
  
  if (game.log)
      console.log("Round Completed (" + ((performance.now() - game.perf) / 1000).toFixed(3) + "s):\n" + game.bots.map(a => a).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join("\n"));
}

function runTurn() {
  var cbots = [];
  var npos = [];
  var nposl = [];
  var nbots = [];
  
  for (let b, p, m, i = 0; i < game.bots.length; i++) {
      b = game.bots[i];
      
      game.uid = b.uid;
      
      try {
          m = b.run(b.storage);
      } catch(e) {
          m = ["dead"];
          
          if (game.log == 2)
              console.warn("[" + game.turns + "] Error: " + b.name + "\n" + (e.stack || e.message));
          
          for (let j = 0; j < b.chars.length; j++)
              game.chars.push({
                  char: b.chars[j],
                  pos: game.randPos(b.pos, !0, 0, 0, 0.2),
                  game: !1
              });
      }
      
      if (!Array.isArray(m))
          m = [];
      
      if (m[0] == "north")
          p = [b.pos[0], b.pos[1] - 1];
      else if (m[0] == "east")
          p = [b.pos[0] + 1, b.pos[1]];
      else if (m[0] == "south")
          p = [b.pos[0], b.pos[1] + 1];
      else if (m[0] == "west")
          p = [b.pos[0] - 1, b.pos[1]];
      else
          p = [...b.pos];
      
      if (m[0] != "dead")
          npos.push({
              bot: b.uid,
              pos: p
          });
      
      if (m[0] == "worker" && m[1].split("").reduce((c, d, e) => d && (e = c.indexOf(d)) != -1 ? c.filter((f, g) => g != e) : null, [...b.chars])) {
          p = game.randPos(b.pos, !1, 0, b.uid);
          
          try {
              cbots.push({
                  uid: game.cuid,
                  owner: b.uid,
                  original: b.original,
                  score: 0,
                  chars: [],
                  pos: p,
                  source: m[1],
                  run: eval(m[1]),
                  storage: {},
                  name: b.name + "*",
                  color: b.color
              });
              
              npos.push({
                  bot: game.cuid++,
                  pos: p
              });
              
              b.score -= Math.floor(m[1].length / 2);
              
              for (let n, j = 0; j < m[1].length; j++) {
                  n = b.chars.indexOf(m[1][j]);
                  
                  b.chars = b.chars.slice(0, n).concat(b.chars.slice(n + 1));
              }
              
              if (game.log == 2)
                  console.log("[" + game.turns + "] New Worker: " + b.name);
          } catch(e) {
              if (game.log == 2)
                  console.warn("[" + game.turns + "] Invalid Worker: " + b.name + "\n" + (e.stack || e.message));
          }
      }
      
      if (typeof m[0] == "string" && m[0].match(/^drop.(north|east|south|west)$/) && b.chars.includes(m[1])) {
          b.score--;
          
          for (let j = 0; j < b.chars.length; j++) {
              if (b.chars[j] == m[1]) {
                  b.chars = b.chars.slice(0, j) + b.chars.slice(j + 1);
                  
                  break;
              }
          }
          
          if (m[0] == "drop.north")
              p = [b.pos[0], b.pos[1] - 1];
          else if (m[0] == "drop.east")
              p = [b.pos[0] + 1, b.pos[1]];
          else if (m[0] == "drop.south")
              p = [b.pos[0], b.pos[1] + 1];
          else if (m[0] == "drop.west")
              p = [b.pos[0] - 1, b.pos[1]];
          
          game.chars.push({
              char: m[1],
              pos: p,
              game: !1
          });
      }
  }
  
  game.bots.push(...cbots);
  
  for (let f, i = 0; i < npos.length; i++) {
      if (!(f = nposl.find(a => a.pos[0] == npos[i].pos[0] && a.pos[1] == npos[i].pos[1])))
          nposl.push(f = {
              pos: [...npos[i].pos],
              bots: []
          });
      
      f.bots.push(npos[i].bot);
  }
  
  for (let n, m, b, i = 0; i < nposl.length; i++) {
      n = nposl[i];
      
      if (n.bots.length > 1) {
          m = Math.max(...n.bots.map(a => game.bots.find(b => b.uid == a).score));
          
          if (game.bots.filter(a => n.bots.includes(a.uid) && a.score == m).length > 1) {
              m += 1;
              
              if (game.log == 2)
                  console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", "));
          } else {
              if (game.log == 2)
                  console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", "));
          }
          
          for (let j = 0; j < n.bots.length; j++) {
              b = game.bots.find(a => a.uid == n.bots[j]);
              
              if (b.score < m)
                  for (let k = 0; k < b.chars.length; k++)
                      game.chars.push({
                          char: b.chars[k],
                          pos: game.randPos(b.pos, !0, 0, 0, 0.2),
                          game: !1
                      });
              else
                  nbots.push({
                      uid: b.uid,
                      owner: b.owner,
                      original: b.original,
                      score: b.score,
                      chars: [...b.chars],
                      pos: n.pos,
                      source: b.source,
                      run: b.run,
                      storage: b.storage,
                      name: b.name,
                      color: b.color
                  });
          }
      } else {
          b = game.bots.find(a => a.uid == n.bots[0]);
          
          nbots.push({
              uid: b.uid,
              owner: b.owner,
              original: b.original,
              score: b.score,
              chars: [...b.chars],
              pos: n.pos,
              source: b.source,
              run: b.run,
              storage: b.storage,
              name: b.name,
              color: b.color
          });
      }
  }
  
  game.center = [
      nbots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0),
      nbots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0)
  ];
  
  game.charPool = nbots.map(a => a.source).join("");
  
  for (let b, c, i = 0; i < game.chars.length; i++) {
      c = game.chars[i];
      
      if (b = nbots.find(a => a.pos[0] == c.pos[0] && a.pos[1] == c.pos[1])) {
          b.score++;
          
          b.chars.push(c.char);
          
          if (c.game && game.chars.filter(a => a && a.game).length < nbots.length * 4 && game.bots.map(a => a.original).reduce((a, b) => a.includes(b) ? a : a.concat(b), []).length > 1)
              game.chars.push({
                  char: game.charPool[Math.random() * game.charPool.length | 0],
                  pos: game.randPos([0, 0]),
                  game: !0
              });
          
          game.chars[i] = null;
      }
  }
  
  game.chars = game.chars.filter(a => a);
  game.bots = nbots;
};

function drawRound(turns = 100000, log = 2, fps = 60, zoom = 50){
  var c, ctx, wdim, scale;
  
  document.body.innerHTML = "<canvas></canvas>";
  
  c = document.body.firstChild;
  c.style.position = "absolute";
  c.style.top = "0";
  c.style.left = "0";
  c.style.zIndex = "2";
  ctx = c.getContext("2d");
  
  game.records = new Array(botData.length).fill(0);
  game.log = log;
  game.pause = !1;
  game.fps = fps;
  
  (window.onresize = function() {
      wdim = [window.innerWidth || 600, window.innerHeight || 400];
      scale = Math.ceil(wdim[1] / zoom);
      c.width = wdim[0];
      c.height = wdim[1];
  })();
  
  window.onkeydown = function() {
      var key = event.code;
      if (key == "Escape")
          game.pause = !game.pause;
      if (key == "ArrowLeft" && game.fps > 1)
          game.fps -= 1;
      if (key == "ArrowRight")
          game.fps += 1;
  };
  
  game.debug = function() {
      ctx.clearRect(0, 0, wdim[0], wdim[1]);
      ctx.textBaseline = "middle";
      ctx.textAlign = "center";
      ctx.font = Math.floor(scale * 0.6) + "px monospace";
      for (let x = -Math.ceil(wdim[0] / 2 / scale), i = wdim[0] / 2 - (Math.ceil(wdim[0] / 2 / scale) - 0.5) * scale; i <= wdim[0]; i += scale, x++) {
          for (let b, c, y = -Math.ceil(wdim[1] / 2 / scale), j = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; j <= wdim[1]; j += scale, y++) {
              if ((c = game.chars.filter(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))).length) {
                  for (let k = 0; k < c.length; k++)
                      ctx.fillText(JSON.stringify(c[k].char).slice(1, -1).replace(/\\"/, "\"").replace(/\\\\/, "\\").replace(/ /, "_"), i + scale / 2, j + scale / 2);
              }
              if (b = game.bots.find(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))) {
                  ctx.fillStyle = b.color;
                  ctx.fillRect(i, j, scale, scale);
                  ctx.fillStyle = "#000000";
              }
          }
          ctx.lineWidth = 0.1;
          ctx.beginPath();
          ctx.moveTo(i, 0);
          ctx.lineTo(i, wdim[1]);
          ctx.stroke();
      }
      for (let i = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; i <= wdim[1]; i += scale) {
          ctx.beginPath();
          ctx.moveTo(0, i);
          ctx.lineTo(wdim[0], i);
          ctx.stroke();
      }
      ctx.fillRect(wdim[0] / 2 - 3, wdim[1] / 2 - 3, 7, 7);
  };
  
  runRound(turns);
}

function runGame(rounds = 1, turns = 100000, log = 0) {
  game.records = new Array(botData.length).fill(0);
  game.log = log;
  
  for (let i = 0; i < rounds; i++)
      runRound(turns, 0);
  
  console.log("Game Conclusion:\n" + botData.map((a, b) => [a.name, game.records[b]]).sort((a, b) => b[1] - a[1]).map(a => "[" + a[1] + "] " + a[0]).join("\n"));
}

};

drawRound(/*turns =*/ 1000000, /*log =*/ 2, /*fps =*/ 600,/* zoom =*/ 50);
<body></body>


Honnold (66 bytes, -33 points)

{
    name: "Honnold",
    color: "#0000FF",
    run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)
}

Bonus points for guessing the name's origin.

Try it:

var botData = [
  {
      name: "ExampleBot",
      color: "#999999",
      run: () => dirTo(chars().sort((a, b) => dist(center(), a.pos) - dist(center(), b.pos))[0].pos)
  },
  {
      name: "ExampleBot",
      color: "#999999",
      run: () => dirTo(chars().sort((a, b) => dist(center(), a.pos) - dist(center(), b.pos))[0].pos)
  },
  {
      name: "ExampleBot",
      color: "#999999",
      run: () => dirTo(chars().sort((a, b) => dist(center(), a.pos) - dist(center(), b.pos))[0].pos)
  },
  {
    name: "Honnold",
    color: "#0000FF",
    run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)
  }
];


{
var game = {
  randPos: (center, any = !1, uid = 0, owner = 0, p = 0.1) => {
      var theta, radius, pos;
      
      do {
          theta = Math.random() * Math.PI * 2;
          radius = 0;
          
          while (Math.random() > p)
              radius++;
          
          pos = [Math.trunc(center[0] + Math.cos(theta) * radius), Math.trunc(center[1] + Math.sin(theta) * radius)];
      } while (!any && game.bots.find(a => a && a.uid != uid && Math.abs(a.pos[0] - pos[0]) + Math.abs(a.pos[1] - pos[1]) < (a.uid == owner ? 3 : 4)));
      
      return pos;
  },
  debug: function(){},
  log: 0 // 0 = NONE, 1 = SUMMARY, 2 = ALL
};

var north = () => ["north"];
var east = () => ["east"];
var south = () => ["south"];
var west = () => ["west"];

var build = code => ["worker", code];

var drop = {
  north: char => ["drop.north", char],
  east: char => ["drop.east", char],
  south: char => ["drop.south", char],
  west: char => ["drop.west", char]
};

var bots = () => game.bots.map(a => ({
  uid: a.uid,
  owner: a.owner,
  original: a.original,
  score: a.score,
  pos: [...a.pos],
  chars: game.uid == a.uid ? [...a.chars] : undefined,
  source: game.uid == a.uid ? a.source : undefined
})).sort((a, b) => a.uid - b.uid);

var chars = () => game.chars.map(a => ({
  char: a.char,
  pos: [...a.pos]
}));

var self = () => {
  var bot = game.bots.find(a => a.uid == game.uid);
  return bot ? {
      uid: bot.uid,
      owner: bot.owner,
      original: bot.original,
      score: bot.score,
      pos: [...bot.pos],
      chars: [...bot.chars],
      source: bot.source
  } : null;
};

var owner = () => {
  var bot = game.bots.find(a => a.uid == game.bots.find(b => b.uid == game.uid).owner);
  return bot ? {
      uid: bot.uid,
      owner: bot.owner,
      original: bot.original,
      score: bot.score,
      pos: [...bot.pos],
      chars: [...bot.chars],
      source: bot.source
  } : null;
};

var center = () => game.center;
var turn = () => game.turns;

var at = pos => ({
  bot: (game.bots.find(b => b.pos[0] == pos[0] && b.pos[1] == pos[1]) || {uid: null}).uid,
  chars: chars().filter(c => c.pos[0] == pos[0] && c.pos[1] == pos[1])
});

var dir = (posFrom, pos) => {
  if (Math.abs(posFrom[0] - pos[0]) <= Math.abs(posFrom[1] - pos[1]))
      return posFrom[1] < pos[1] ? ["north"] : ["south"];
  else
      return posFrom[0] < pos[0] ? ["west"] : ["east"];
};

var dirTo = pos => {
  var bot = game.bots.find(a => a.uid == game.uid);
  if (Math.abs(pos[0] - bot.pos[0]) <= Math.abs(pos[1] - bot.pos[1]))
      return pos[1] < bot.pos[1] ? ["north"] : ["south"];
  else
      return pos[0] < bot.pos[0] ? ["west"] : ["east"];
};

var dist = (posFrom, pos) => {
  return Math.abs(posFrom[0] - pos[0]) + Math.abs(posFrom[1] - pos[1]);
};

var distTo = pos => {
  var bot = game.bots.find(a => a.uid == game.uid);
  return Math.abs(pos[0] - bot.pos[0]) + Math.abs(pos[1] - bot.pos[1]);
};

async function runRound(turns = 100000) {
  var uids = [];
  
  game.perf = performance.now();
  
  for (let i = 1; i <= botData.length; i++)
      uids[i - 1] = i;
  
  for (let j, i = uids.length - 1; i > 0; i--) {
      j = Math.floor(Math.random() * (i + 1));
      [uids[i], uids[j]] = [uids[j], uids[i]];
  }
  
  game.bots = [];
  game.chars = [];
  game.records = game.records || [];
  game.uids = [];
  
  for (let i = 0; i < botData.length; i++) {
      game.bots[i] = {
          uid: uids[i],
          owner: uids[i],
          original: uids[i],
          score: Math.floor(botData[i].run.toString().length * -1 / 2),
          chars: [],
          pos: game.randPos([0, 0]),
          source: botData[i].run.toString(),
          run: botData[i].run,
          storage: {},
          name: botData[i].name || "Bot",
          color: botData[i].color || "#000000"
      };
      
      game.uids[uids[i]] = i;
      game.records[i] = game.records[i] || 0;
  }
  
  game.center = [
      game.bots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0),
      game.bots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0)
  ];
  
  game.charPool = game.bots.map(a => a.source).join("");
  
  for (let i = 0; i < botData.length * 4; i++)
      game.chars.push({
          char: game.charPool[Math.random() * game.charPool.length | 0],
          pos: game.randPos([0, 0]),
          game: !0
      });
  
  game.cuid = botData.length + 1;
  game.turns = 0;
  
  if (!game.fps) {
      while (game.chars.length && game.bots.length && game.turns < turns) {
          runTurn();
          
          game.turns++;
      }
  } else {
      game.debug();
      
      while (game.chars.length && game.bots.length && game.turns < turns) {
          await new Promise(function(resolve) {
              setTimeout(resolve, 1000 / game.fps);
          });
          
          if (!game.pause) {
              runTurn();
              
              game.debug();
              game.turns++;
          }
      }
  }
  bots().map(b => game.records[game.uids[b.original]] += b.score);
  
  if (game.log)
      console.log("Round Completed (" + ((performance.now() - game.perf) / 1000).toFixed(3) + "s):\n" + game.bots.map(a => a).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join("\n"));
}

function runTurn() {
  var cbots = [];
  var npos = [];
  var nposl = [];
  var nbots = [];
  
  for (let b, p, m, i = 0; i < game.bots.length; i++) {
      b = game.bots[i];
      
      game.uid = b.uid;
      
      try {
          m = b.run(b.storage);
      } catch(e) {
          m = ["dead"];
          
          if (game.log == 2)
              console.warn("[" + game.turns + "] Error: " + b.name + "\n" + (e.stack || e.message));
          
          for (let j = 0; j < b.chars.length; j++)
              game.chars.push({
                  char: b.chars[j],
                  pos: game.randPos(b.pos, !0, 0, 0, 0.2),
                  game: !1
              });
      }
      
      if (!Array.isArray(m))
          m = [];
      
      if (m[0] == "north")
          p = [b.pos[0], b.pos[1] - 1];
      else if (m[0] == "east")
          p = [b.pos[0] + 1, b.pos[1]];
      else if (m[0] == "south")
          p = [b.pos[0], b.pos[1] + 1];
      else if (m[0] == "west")
          p = [b.pos[0] - 1, b.pos[1]];
      else
          p = [...b.pos];
      
      if (m[0] != "dead")
          npos.push({
              bot: b.uid,
              pos: p
          });
      
      if (m[0] == "worker" && m[1].split("").reduce((c, d, e) => d && (e = c.indexOf(d)) != -1 ? c.filter((f, g) => g != e) : null, [...b.chars])) {
          p = game.randPos(b.pos, !1, 0, b.uid);
          
          try {
              cbots.push({
                  uid: game.cuid,
                  owner: b.uid,
                  original: b.original,
                  score: 0,
                  chars: [],
                  pos: p,
                  source: m[1],
                  run: eval(m[1]),
                  storage: {},
                  name: b.name + "*",
                  color: b.color
              });
              
              npos.push({
                  bot: game.cuid++,
                  pos: p
              });
              
              b.score -= Math.floor(m[1].length / 2);
              
              for (let n, j = 0; j < m[1].length; j++) {
                  n = b.chars.indexOf(m[1][j]);
                  
                  b.chars = b.chars.slice(0, n).concat(b.chars.slice(n + 1));
              }
              
              if (game.log == 2)
                  console.log("[" + game.turns + "] New Worker: " + b.name);
          } catch(e) {
              if (game.log == 2)
                  console.warn("[" + game.turns + "] Invalid Worker: " + b.name + "\n" + (e.stack || e.message));
          }
      }
      
      if (typeof m[0] == "string" && m[0].match(/^drop.(north|east|south|west)$/) && b.chars.includes(m[1])) {
          b.score--;
          
          for (let j = 0; j < b.chars.length; j++) {
              if (b.chars[j] == m[1]) {
                  b.chars = b.chars.slice(0, j) + b.chars.slice(j + 1);
                  
                  break;
              }
          }
          
          if (m[0] == "drop.north")
              p = [b.pos[0], b.pos[1] - 1];
          else if (m[0] == "drop.east")
              p = [b.pos[0] + 1, b.pos[1]];
          else if (m[0] == "drop.south")
              p = [b.pos[0], b.pos[1] + 1];
          else if (m[0] == "drop.west")
              p = [b.pos[0] - 1, b.pos[1]];
          
          game.chars.push({
              char: m[1],
              pos: p,
              game: !1
          });
      }
  }
  
  game.bots.push(...cbots);
  
  for (let f, i = 0; i < npos.length; i++) {
      if (!(f = nposl.find(a => a.pos[0] == npos[i].pos[0] && a.pos[1] == npos[i].pos[1])))
          nposl.push(f = {
              pos: [...npos[i].pos],
              bots: []
          });
      
      f.bots.push(npos[i].bot);
  }
  
  for (let n, m, b, i = 0; i < nposl.length; i++) {
      n = nposl[i];
      
      if (n.bots.length > 1) {
          m = Math.max(...n.bots.map(a => game.bots.find(b => b.uid == a).score));
          
          if (game.bots.filter(a => n.bots.includes(a.uid) && a.score == m).length > 1) {
              m += 1;
              
              if (game.log == 2)
                  console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", "));
          } else {
              if (game.log == 2)
                  console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", "));
          }
          
          for (let j = 0; j < n.bots.length; j++) {
              b = game.bots.find(a => a.uid == n.bots[j]);
              
              if (b.score < m)
                  for (let k = 0; k < b.chars.length; k++)
                      game.chars.push({
                          char: b.chars[k],
                          pos: game.randPos(b.pos, !0, 0, 0, 0.2),
                          game: !1
                      });
              else
                  nbots.push({
                      uid: b.uid,
                      owner: b.owner,
                      original: b.original,
                      score: b.score,
                      chars: [...b.chars],
                      pos: n.pos,
                      source: b.source,
                      run: b.run,
                      storage: b.storage,
                      name: b.name,
                      color: b.color
                  });
          }
      } else {
          b = game.bots.find(a => a.uid == n.bots[0]);
          
          nbots.push({
              uid: b.uid,
              owner: b.owner,
              original: b.original,
              score: b.score,
              chars: [...b.chars],
              pos: n.pos,
              source: b.source,
              run: b.run,
              storage: b.storage,
              name: b.name,
              color: b.color
          });
      }
  }
  
  game.center = [
      nbots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0),
      nbots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0)
  ];
  
  game.charPool = nbots.map(a => a.source).join("");
  
  for (let b, c, i = 0; i < game.chars.length; i++) {
      c = game.chars[i];
      
      if (b = nbots.find(a => a.pos[0] == c.pos[0] && a.pos[1] == c.pos[1])) {
          b.score++;
          
          b.chars.push(c.char);
          
          if (c.game && game.chars.filter(a => a && a.game).length < nbots.length * 4 && game.bots.map(a => a.original).reduce((a, b) => a.includes(b) ? a : a.concat(b), []).length > 1)
              game.chars.push({
                  char: game.charPool[Math.random() * game.charPool.length | 0],
                  pos: game.randPos([0, 0]),
                  game: !0
              });
          
          game.chars[i] = null;
      }
  }
  
  game.chars = game.chars.filter(a => a);
  game.bots = nbots;
};

function drawRound(turns = 100000, log = 2, fps = 5, zoom = 50){
  var c, ctx, wdim, scale;
  
  document.body.innerHTML = "<canvas></canvas>";
  
  c = document.body.firstChild;
  c.style.position = "absolute";
  c.style.top = "0";
  c.style.left = "0";
  c.style.zIndex = "2";
  ctx = c.getContext("2d");
  
  game.records = new Array(botData.length).fill(0);
  game.log = log;
  game.pause = !1;
  game.fps = fps;
  
  (window.onresize = function() {
      wdim = [window.innerWidth || 600, window.innerHeight || 400];
      scale = Math.ceil(wdim[1] / zoom);
      c.width = wdim[0];
      c.height = wdim[1];
  })();
  
  window.onkeydown = function() {
      var key = event.code;
      if (key == "Escape")
          game.pause = !game.pause;
      if (key == "ArrowLeft" && game.fps > 1)
          game.fps -= 1;
      if (key == "ArrowRight")
          game.fps += 1;
  };
  
  game.debug = function() {
      ctx.clearRect(0, 0, wdim[0], wdim[1]);
      ctx.textBaseline = "middle";
      ctx.textAlign = "center";
      ctx.font = Math.floor(scale * 0.6) + "px monospace";
      for (let x = -Math.ceil(wdim[0] / 2 / scale), i = wdim[0] / 2 - (Math.ceil(wdim[0] / 2 / scale) - 0.5) * scale; i <= wdim[0]; i += scale, x++) {
          for (let b, c, y = -Math.ceil(wdim[1] / 2 / scale), j = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; j <= wdim[1]; j += scale, y++) {
              if ((c = game.chars.filter(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))).length) {
                  for (let k = 0; k < c.length; k++)
                      ctx.fillText(JSON.stringify(c[k].char).slice(1, -1).replace(/\\"/, "\"").replace(/\\\\/, "\\").replace(/ /, "_"), i + scale / 2, j + scale / 2);
              }
              if (b = game.bots.find(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))) {
                  ctx.fillStyle = b.color;
                  ctx.fillRect(i, j, scale, scale);
                  ctx.fillStyle = "#000000";
              }
          }
          ctx.lineWidth = 0.1;
          ctx.beginPath();
          ctx.moveTo(i, 0);
          ctx.lineTo(i, wdim[1]);
          ctx.stroke();
      }
      for (let i = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; i <= wdim[1]; i += scale) {
          ctx.beginPath();
          ctx.moveTo(0, i);
          ctx.lineTo(wdim[0], i);
          ctx.stroke();
      }
      ctx.fillRect(wdim[0] / 2 - 3, wdim[1] / 2 - 3, 7, 7);
  };
  
  runRound(turns);
}

function runGame(rounds = 1, turns = 100000, log = 0) {
  game.records = new Array(botData.length).fill(0);
  game.log = log;
  
  for (let i = 0; i < rounds; i++)
      runRound(turns, 0);
  
  console.log("Game Conclusion:\n" + botData.map((a, b) => [a.name, game.records[b]]).sort((a, b) => b[1] - a[1]).map(a => "[" + a[1] + "] " + a[0]).join("\n"));
}

};

drawRound(/*turns =*/ 1000, /*log =*/ 2, /*fps =*/ 10,/* zoom =*/ 50);
<body></body>


Rabbit, 56 bytes(-28 points)

{
    name: "Rabbit",
    color: "#FFC0CB",
    run:_=>turn()%1e3?dirTo(chars()[0].pos):build(self().source)
}

Why rabbit? Because it runs away, forages for food, and then immediately reproduces.

Fixed with some help from Redwolf, and now works after a fix to the controller.

var botData = [
  {
name: "Honnold",
color: "#0000FF",
run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)
  },

  {
    name: "The Caveman",
    color: "#FF0000",
    run:_=>{w=bots().sort((a,b)=>a.score-b.score)[0];return self().score<=w.score?dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos):dirTo(w.pos)}
  },
    {
    name: "Centrist",
    color: "#666666",
    run:_=>dirTo(center())
  },
  {
    name: "True Neutral",
    color: "#400000",
    run: _=>dirTo(chars().sort((a,b)=>dist(a.pos,[0,0])-dist(b.pos,[0,0])+distTo(a.pos)-distTo(b.pos))[0].pos)
},{
    name: "Rabbit",
    color: "#FFC0CB",
    run:_=>turn()%1e3?dirTo(chars()[0].pos):build(self().source)
}
 
];

var game = {
    randPos: (center, any = !1, uid = 0, owner = 0, p = 0.1) => {
        var theta, radius, pos;
        
        do {
            theta = Math.random() * Math.PI * 2;
            radius = 0;
            
            while (Math.random() > p)
                radius++;
            
            pos = [Math.trunc(center[0] + Math.cos(theta) * radius), Math.trunc(center[1] + Math.sin(theta) * radius)];
        } while (!any && game.bots.find(a => a && a.uid != uid && Math.abs(a.pos[0] - pos[0]) + Math.abs(a.pos[1] - pos[1]) < (a.uid == owner ? 3 : 4)));
        
        return pos;
    },
    debug: function(){},
    log: 0 // 0 = NONE, 1 = SUMMARY, 2 = ALL
};

var north = () => ["north"];
var east = () => ["east"];
var south = () => ["south"];
var west = () => ["west"];

var build = code => ["worker", code];

var drop = {
    north: char => ["drop.north", char],
    east: char => ["drop.east", char],
    south: char => ["drop.south", char],
    west: char => ["drop.west", char]
};

var bots = () => game.bots.map(a => ({
    uid: a.uid,
    owner: a.owner,
    original: a.original,
    score: a.score,
    pos: [...a.pos],
    chars: game.uid == a.uid ? [...a.chars] : undefined,
    source: game.uid == a.uid ? a.source : undefined
})).sort((a, b) => a.uid - b.uid);

var chars = () => game.chars.map(a => ({
    char: a.char,
    pos: [...a.pos]
}));

var self = () => {
    var bot = game.bots.find(a => a.uid == game.uid);
    
    return bot ? {
        uid: bot.uid,
        owner: bot.owner,
        original: bot.original,
        score: bot.score,
        pos: [...bot.pos],
        chars: [...bot.chars],
        source: bot.source
    } : null;
};

var owner = () => {
    var bot = game.bots.find(a => a.uid == game.bots.find(b => b.uid == game.uid).owner);
    
    return bot ? {
        uid: bot.uid,
        owner: bot.owner,
        original: bot.original,
        score: bot.score,
        pos: [...bot.pos],
        chars: [...bot.chars],
        source: bot.source
    } : null;
};

var center = () => game.center;
var turn = () => game.turns;

var at = pos => ({
    bot: (game.bots.find(b => b.pos[0] == pos[0] && b.pos[1] == pos[1]) || {uid: null}).uid,
    chars: chars().filter(c => c.pos[0] == pos[0] && c.pos[1] == pos[1])
});

var dir = (posFrom, pos) => {
    if (Math.abs(posFrom[0] - pos[0]) <= Math.abs(posFrom[1] - pos[1]))
        return posFrom[1] < pos[1] ? ["north"] : ["south"];
    else
        return posFrom[0] < pos[0] ? ["west"] : ["east"];
};

var dirTo = pos => {
    var bot = game.bots.find(a => a.uid == game.uid);
    if (Math.abs(pos[0] - bot.pos[0]) <= Math.abs(pos[1] - bot.pos[1]))
        return pos[1] < bot.pos[1] ? ["north"] : ["south"];
    else
        return pos[0] < bot.pos[0] ? ["west"] : ["east"];
};

var dist = (posFrom, pos) => {
    return Math.abs(posFrom[0] - pos[0]) + Math.abs(posFrom[1] - pos[1]);
};

var distTo = pos => {
    var bot = game.bots.find(a => a.uid == game.uid);
    return Math.abs(pos[0] - bot.pos[0]) + Math.abs(pos[1] - bot.pos[1]);
};

async function runRound(turns = 100000) {
    var uids = [];
    
    game.perf = performance.now();
    
    for (let i = 1; i <= botData.length; i++)
        uids[i - 1] = i;
    
    for (let j, i = uids.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        [uids[i], uids[j]] = [uids[j], uids[i]];
    }
    
    game.bots = [];
    game.chars = [];
    game.records = game.records || [];
    game.uids = [];
    
    for (let i = 0; i < botData.length; i++) {
        game.bots[i] = {
            uid: uids[i],
            owner: uids[i],
            original: uids[i],
            score: Math.floor(botData[i].run.toString().length * -1 / 2),
            chars: [],
            pos: game.randPos([0, 0]),
            source: botData[i].run.toString(),
            run: botData[i].run,
            storage: {},
            name: botData[i].name || "Bot",
            color: botData[i].color || "#000000"
        };
        
        game.uids[uids[i]] = i;
        game.records[i] = game.records[i] || 0;
    }
    
    game.center = [
        game.bots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0),
        game.bots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0)
    ];
    
    game.charPool = game.bots.map(a => a.source).join("");
    
    for (let i = 0; i < botData.length * 4; i++)
        game.chars.push({
            char: game.charPool[Math.random() * game.charPool.length | 0],
            pos: game.randPos([0, 0]),
            game: !0
        });
    
    game.cuid = botData.length + 1;
    game.turns = 0;
    
    if (!game.fps) {
        while (game.chars.length && game.bots.length && game.turns < turns) {
            runTurn();
            
            game.turns++;
        }
    } else {
        game.debug();
        
        while (game.chars.length && game.bots.length && game.turns < turns) {
            await new Promise(function(resolve) {
                setTimeout(resolve, 1000 / game.fps);
            });
            
            if (!game.pause) {
                runTurn();
                
                game.debug();
                game.turns++;
            }
        }
    }
    
    game.bots.map(b => game.records[game.uids[b.original]] += b.score);
    
    if (game.log)
        console.log("Round Completed (" + ((performance.now() - game.perf) / 1000).toFixed(3) + "s):\n" + game.bots.map(a => a).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join("\n"));
}

function runTurn() {
    var cbots = [];
    var npos = [];
    var nposl = [];
    var nbots = [];
    
    for (let b, p, m, i = 0; i < game.bots.length; i++) {
        b = game.bots[i];
        
        game.uid = b.uid;
        
        try {
            m = b.run(b.storage);
        } catch(e) {
            m = ["dead"];
            
            if (game.log == 2)
                console.warn("[" + game.turns + "] Error: " + b.name + "\n" + (e.stack || e.message));
            
            for (let j = 0; j < b.chars.length; j++)
                game.chars.push({
                    char: b.chars[j],
                    pos: game.randPos(b.pos, !0, 0, 0, 0.2),
                    game: !1
                });
        }
        
        if (!Array.isArray(m))
            m = [];
        
        if (m[0] == "north")
            p = [b.pos[0], b.pos[1] - 1];
        else if (m[0] == "east")
            p = [b.pos[0] + 1, b.pos[1]];
        else if (m[0] == "south")
            p = [b.pos[0], b.pos[1] + 1];
        else if (m[0] == "west")
            p = [b.pos[0] - 1, b.pos[1]];
        else
            p = [...b.pos];
        
        if (m[0] != "dead")
            npos.push({
                bot: b.uid,
                pos: p
            });
        
        if (m[0] == "worker" && m[1].split("").reduce((c, d, e) => c && d && (e = c.indexOf(d)) != -1 ? c.filter((f, g) => g != e) : null, [...b.chars])) {
            p = game.randPos(b.pos, !1, 0, b.uid);
            
            try {
                cbots.push({
                    uid: game.cuid,
                    owner: b.uid,
                    original: b.original,
                    score: 0,
                    chars: [],
                    pos: p,
                    source: m[1],
                    run: eval(m[1]),
                    storage: {},
                    name: b.name + "*",
                    color: b.color
                });
                
                npos.push({
                    bot: game.cuid++,
                    pos: p
                });
                
                b.score -= Math.floor(m[1].length / 2);
                
                for (let n, j = 0; j < m[1].length; j++) {
                    n = b.chars.indexOf(m[1][j]);
                    
                    b.chars = b.chars.slice(0, n).concat(b.chars.slice(n + 1));
                }
                
                if (game.log == 2)
                    console.log("[" + game.turns + "] New Worker: " + b.name);
            } catch(e) {
                if (game.log == 2)
                    console.warn("[" + game.turns + "] Invalid Worker: " + b.name + "\n" + (e.stack || e.message));
            }
        }
        
        if (typeof m[0] == "string" && m[0].match(/^drop.(north|east|south|west)$/) && b.chars.includes(m[1])) {
            b.score--;
            
            for (let j = 0; j < b.chars.length; j++) {
                if (b.chars[j] == m[1]) {
                    b.chars = b.chars.slice(0, j) + b.chars.slice(j + 1);
                    
                    break;
                }
            }
            
            if (m[0] == "drop.north")
                p = [b.pos[0], b.pos[1] - 1];
            else if (m[0] == "drop.east")
                p = [b.pos[0] + 1, b.pos[1]];
            else if (m[0] == "drop.south")
                p = [b.pos[0], b.pos[1] + 1];
            else if (m[0] == "drop.west")
                p = [b.pos[0] - 1, b.pos[1]];
            
            game.chars.push({
                char: m[1],
                pos: p,
                game: !1
            });
        }
    }
    
    game.bots.push(...cbots);
    
    for (let f, i = 0; i < npos.length; i++) {
        if (!(f = nposl.find(a => a.pos[0] == npos[i].pos[0] && a.pos[1] == npos[i].pos[1])))
            nposl.push(f = {
                pos: [...npos[i].pos],
                bots: []
            });
        
        f.bots.push(npos[i].bot);
    }
    
    for (let n, m, b, i = 0; i < nposl.length; i++) {
        n = nposl[i];
        
        if (n.bots.length > 1) {
            m = Math.max(...n.bots.map(a => game.bots.find(b => b.uid == a).score));
            
            if (game.bots.filter(a => n.bots.includes(a.uid) && a.score == m).length > 1) {
                m += 1;
                
                if (game.log == 2)
                    console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", "));
            } else {
                if (game.log == 2)
                    console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", "));
            }
            
            for (let j = 0; j < n.bots.length; j++) {
                b = game.bots.find(a => a.uid == n.bots[j]);
                
                if (b.score < m) {
                    for (let k = 0; k < b.chars.length; k++)
                        game.chars.push({
                            char: b.chars[k],
                            pos: game.randPos(b.pos, !0, 0, 0, 0.2),
                            game: !1
                        });
                    
                    game.records[game.uids[b.original]] += b.score;
                } else {
                    nbots.push({
                        uid: b.uid,
                        owner: b.owner,
                        original: b.original,
                        score: b.score,
                        chars: [...b.chars],
                        pos: n.pos,
                        source: b.source,
                        run: b.run,
                        storage: b.storage,
                        name: b.name,
                        color: b.color
                    });
                }
            }
        } else {
            b = game.bots.find(a => a.uid == n.bots[0]);
            
            nbots.push({
                uid: b.uid,
                owner: b.owner,
                original: b.original,
                score: b.score,
                chars: [...b.chars],
                pos: n.pos,
                source: b.source,
                run: b.run,
                storage: b.storage,
                name: b.name,
                color: b.color
            });
        }
    }
    
    game.center = [
        nbots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0),
        nbots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0)
    ];
    
    game.charPool = nbots.map(a => a.source).join("");
    
    for (let b, c, i = 0; i < game.chars.length; i++) {
        c = game.chars[i];
        
        if (b = nbots.find(a => a.pos[0] == c.pos[0] && a.pos[1] == c.pos[1])) {
            b.score++;
            
            b.chars.push(c.char);
            
            if (c.game && game.chars.filter(a => a && a.game).length < nbots.length * 4 && game.bots.map(a => a.original).reduce((a, b) => a.includes(b) ? a : a.concat(b), []).length > 1)
                game.chars.push({
                    char: game.charPool[Math.random() * game.charPool.length | 0],
                    pos: game.randPos([0, 0]),
                    game: !0
                });
            
            game.chars[i] = null;
        }
    }
    
    game.chars = game.chars.filter(a => a);
    game.bots = nbots;
};

function drawRound(turns = 100000, log = 2, fps = 5, zoom = 50) {
    var c, ctx, wdim, scale;
    
    document.body.innerHTML = "<canvas></canvas>";
    
    c = document.body.firstChild;
    c.style.position = "absolute";
    c.style.top = "0";
    c.style.left = "0";
    c.style.zIndex = "2";
    ctx = c.getContext("2d");
    
    game.records = new Array(botData.length).fill(0);
    game.log = log;
    game.pause = !1;
    game.fps = fps;
    
    (window.onresize = function() {
        wdim = [window.innerWidth || 600, window.innerHeight || 400];
        scale = Math.ceil(wdim[1] / zoom);
        c.width = wdim[0];
        c.height = wdim[1];
    })();
    
    window.onkeydown = function() {
        var key = event.code;
        if (key == "Escape")
            game.pause = !game.pause;
        if (key == "ArrowLeft" && game.fps > 1)
            game.fps -= 1;
        if (key == "ArrowRight")
            game.fps += 1;
    };
    
    game.debug = function() {
        ctx.clearRect(0, 0, wdim[0], wdim[1]);
        ctx.textBaseline = "middle";
        ctx.textAlign = "center";
        ctx.font = Math.floor(scale * 0.6) + "px monospace";
        for (let x = -Math.ceil(wdim[0] / 2 / scale), i = wdim[0] / 2 - (Math.ceil(wdim[0] / 2 / scale) - 0.5) * scale; i <= wdim[0]; i += scale, x++) {
            for (let b, c, y = -Math.ceil(wdim[1] / 2 / scale), j = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; j <= wdim[1]; j += scale, y++) {
                if ((c = game.chars.filter(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))).length) {
                    for (let k = 0; k < c.length; k++)
                        ctx.fillText(JSON.stringify(c[k].char).slice(1, -1).replace(/\\"/, "\"").replace(/\\\\/, "\\").replace(/ /, "_"), i + scale / 2, j + scale / 2);
                }
                if (b = game.bots.find(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))) {
                    ctx.fillStyle = b.color;
                    ctx.fillRect(i, j, scale, scale);
                    ctx.fillStyle = "#000000";
                }
            }
            ctx.beginPath();
            ctx.moveTo(i, 0);
            ctx.lineTo(i, wdim[1]);
            ctx.stroke();
        }
        for (let i = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; i <= wdim[1]; i += scale) {
            ctx.beginPath();
            ctx.moveTo(0, i);
            ctx.lineTo(wdim[0], i);
            ctx.stroke();
        }
        ctx.fillRect(wdim[0] / 2 - 3, wdim[1] / 2 - 3, 7, 7);
    };
    
    runRound(turns);
}

function runGame(rounds = 1, turns = 100000, log = 0) {
    game.records = new Array(botData.length).fill(0);
    game.log = log;
    
    for (let i = 0; i < rounds; i++)
        runRound(turns, 0);
    
    console.log("Game Conclusion:\n" + botData.map((a, b) => [a.name, game.records[b]]).sort((a, b) => b[1] - a[1]).map(a => "[" + a[1] + "] " + a[0]).join("\n"));
}



drawRound(/*turns =*/ 1000000, /*log =*/ 2, /*fps =*/ 600,/* zoom =*/ 50);