14
\$\begingroup\$

In this challenge, bots (consisting of JS functions) move around an infinite factory (playing field), collecting characters. These characters can be used to build worker bots, or dropped for other bots to pick up.

This challenge is a modified redo of Bot Factory KoTH, which was broken by a mistake in the spec.

Results

[39599.65] Ghost
[12251.15] Shy Guy
[8565.70]  Camper
[8339.95]  Honnold
[2862.50]  True Neutral
[51.575]   Rabbit
[10.65]    Centrist
[-8.85]    The Luggage
[-66.075]  The Caveman

The bounty went to @Razetime, as their bots were consistently in the lead until Shy Guy showed up.

Feel free to keep posting bots, but the scoring might not get updated.

The Factory

All bots start out randomly placed around the center of the factory, [0, 0], where coordinates are arrays [x, y]. North is -Y, and west is -X. Each round consists of up to 100000 turns. A bot may move one space in any cardinal direction (or build a worker, or drop a character) per turn.

Bots can move by returning north(), east(), south(), or west().

Characters

Characters will be randomly distributed, centered around [0, 0]. There are always up to a total of 4 characters per bot at any given time (excluding those from dead bots).

Character's positions may overlap, and their character value (such as a or () is selected from a pool of the source code of every bot and worker currently in the factory.

Bots collect characters by moving into the same position in the factory, and these collected characters are stored within the bot.

Bots may also drop a character they have previously collected in any of the four cardinal directions. This character is then removed from the bot's collected characters array.

Bots drop characters by returning drop.north(c), drop.east(c), drop.south(c), or drop.west(c), where c is a string.

Score

Bots have a score, which is initially -floor(LEN / 2), where LEN is the length of the bot's source code in characters. Worker bots start with a score of 0 regardless of their length.

Whenever a character is collected, this score value increments; when a character is dropped, it decrements.

Collisions

When two or more bots collide (occupy the same position), whichever has the highest score survives (if there is a tie, none survive). All bots that die "drop" the characters they have collected, which are randomly distributed centered around the position they collided in.

Bots can switch places by moving into each others' previously occupied positions without colliding. Bots can collide with their own workers.

Workers

Bots can use the characters they have collected to build a worker bot. A worker bot's source code (provided as a function or arrow function) must be entirely made up of characters its owner has collected, which are then removed. Half of the length of the worker's source code is deducted from the owner's score.

Worker bots are placed randomly around the position of their owner, using the same system as characters, and are not immune to collision. Worker bots can also build worker bots, whose owner would be the worker.

Workers can be built by returning the result of the function build(source), where source is a string. If the bot cannot be built (such as not having all necessary characters), nothing happens.

Functions

All bots are functions. They are provided a single argument, an object that can be used for storage, and can access information about other bots and characters using the following functions:

  • bots(): Returns an array of bots (as objects)
  • chars(): Returns an array of characters (as objects)
  • self(): Returns the bot that called it (as an object)
  • owner(): Returns the owner of the bot that called it (as an object, null if owner is dead, or self() if no owner)

A bot object has the following properties:

  • uid: An integer ID unique to each bot, randomly selected
  • owner: The UID of the bot's owner
  • score: The score of the bot
  • pos: The position of the bot formatted as [x, y]

A bot object also has the following properties if it is the owner or worker of the bot:

  • chars: An array of characters the bot has collected
  • source: A string with the bot's source code

A character object has the following properties:

  • char: A string
  • pos: The position of the character formatted as [x, y]

There are also the following library functions:

  • center(): Get average of bots' positions weighted by score
  • turn(): Get current turn (starts at 0)
  • at(pos): Returns an object:
    • bot: The uid of the bot at that location, or null if there is none
    • chars: An array of characters at that location
  • dir(from, to): Get direction from from to to, which should be arrays formatted [x, y]
  • dirTo(pos): Same as dir, uses self().pos as from
  • dist(from, to): Get taxicab distance from from to to, which should be arrays formatted [x, y]
  • distTo(pos): Same as dist, uses self().pos as from

Winning

Once all surviving bots are direct or indirect workers of the same initial bot, characters will no longer generate. Games automatically end when all characters are collected.

At the end of a game, all bots' scores (including their workers) are added up for each round with the highest winning.

Rules

  • Bots may not use global variables in a way which produces side effects other than intended (such as to sabotage other bots or the controller, or communicate with any other bots)
  • Bots which error will be killed and drop their characters
  • Bots may only attempt to build workers which are valid functions
  • Bots may not take an unnecessarily long time to run (no hard limitation, but be reasonable)
  • Bots may not team with other which do not originate from the same owner (directly or not)

Technical Notes

  • Character and bot spawning radius is based on a geometric distribution (p=0.1), with a random angle. The resulting coordinates are truncated to integers. Repeated until a position is found with no bots within a taxicab distance of 4
  • For dead bots' drops, p=0.2 and there is no distance requirement
  • For worker bot spawning, minimum taxicab distance is 3 for the owner
  • Every time a character is picked up, one attempt is made to generate another. An attempt will only fail if there are already 4 * botCount characters not dropped by a bot (or a bots' death), where botCount is the total number of alive bots (including workers). 4 * botCount characters are generated at the start.

Example Bot

ExampleBot uses an arrow function to keep its starting score higher, but still isn't well golfed. It likes characters which are in the middle of the action, which it finds by sorting the chars() array and using the dist and center library functions. It will not be included in the competition game.

() => dirTo(chars().sort((a, b) => dist(center(), a.pos) - dist(center(), b.pos))[0].pos)

Other

Chat: https://chat.stackexchange.com/rooms/118871/bot-factory-koth-2-0

Controller: https://gist.github.com/Radvylf/4d9915632fed1b4d7a7e9407e2a38edf

\$\endgroup\$
6
  • \$\begingroup\$ Minor point: by north is -Y do you mean north is postive Y (as you're just indicating the axis) or minus Y and South is +Y? \$\endgroup\$
    – Noodle9
    Jan 25, 2021 at 12:19
  • \$\begingroup\$ @Noodle9 North is minus Y and south is +Y. \$\endgroup\$ Jan 25, 2021 at 13:30
  • \$\begingroup\$ Is returning north(), south(), etc. the only way to move your bot? The example bot doesn't use any of these. I feel like I'm missing something... \$\endgroup\$
    – qwatry
    Jan 27, 2021 at 19:46
  • \$\begingroup\$ Resolved my question. I thought that dirTo returned an array showing the vector towards pos. dirTo() instead returns north(), south(), etc. \$\endgroup\$
    – qwatry
    Jan 27, 2021 at 20:09
  • \$\begingroup\$ If your bot is destroyed does it still keep it's score from the characters it has collected? Also, if a worker is destroyed is the worker's score still added to your's at the end? \$\endgroup\$
    – qwatry
    Jan 29, 2021 at 14:40

9 Answers 9

5
\$\begingroup\$

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>

\$\endgroup\$
5
+200
\$\begingroup\$

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>

\$\endgroup\$
3
+500
\$\begingroup\$

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);

\$\endgroup\$
2
  • \$\begingroup\$ This bot never manages to find any characters, because dir(center(),chars()[0].pos) makes it keep going super far in some random direction. \$\endgroup\$ Jan 28, 2021 at 16:19
  • \$\begingroup\$ hmm, let's discuss in chat. \$\endgroup\$
    – Razetime
    Jan 28, 2021 at 16:19
3
\$\begingroup\$

The Luggage

{
    name: "The Luggage",
    color: "#8B4513",
    run:()=>dirTo(bots()[0].pos)
}

Bonus points for guessing the name origin

var botData = [
{
    name: "ExampleBot",
    color: "#aaaaaa",
    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)
},
{
    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: "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: "Centrist",
    color: "#666666",
    run:_=>dirTo(center())
},
{
name: "Rabbit",
    color: "#FFC0CB",
    run:_=>turn()%1e3?dirTo(chars()[0].pos):build(self().source)
},
{
name: "The Luggage",
color: "#8B4513",
run:()=>dirTo(bots()[0].pos)
}
];

// _=>{var r,d,e,l,I,N,P,D=distTo,s=self(),p=s[P="pos"],w="_=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)",n=w.split``;s.chars.map(c=>n[n[N="indexOf"](c)]="");if(!n.find(_=>_))return build(w);r=chars().filter(c=>!bots().find(b=>b.uid!=s.uid&&dist(b[P],c[P])-D(c[P])<1));if(r.length)return dirTo(r.sort((a,b)=>D(a[P])-D(b[P])+(n[I="includes"](b)-n[I](a))*10)[0][P]);l={n:-p[1],e:p[0],s:p[1],w:-p[0]};d="nesw".split``.sort((a,b)=>l[a]-l[b]);e=bots().filter(b=>b.uid!=s.uid&&b.score>=s.score&&D(b[P])<5).map(b=>d[d[N](dirTo(b[P])[0][0])]="");return[north,east,south,west]["nesw"[N](d.find(x=>x))]()}

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 =*/ 60,/* zoom =*/ 50);
<body></body>

\$\endgroup\$
2
  • \$\begingroup\$ I think you're using the old version of the controller. you need to switch to the new version so the thing isn't frozen. \$\endgroup\$
    – Razetime
    Jan 29, 2021 at 9:28
  • \$\begingroup\$ @Razetime fixed. This just kills off half the bots at the start.... \$\endgroup\$
    – Dion
    Jan 29, 2021 at 9:42
3
\$\begingroup\$

Shy Guy

{
    name: "Shy Guy",
    color: "#dd0044",
    run: _=>{var Z="pos",E=self(),P=E[Z],D=distTo,T=bots().filter(b=>D(b[Z])==2&&b.score>E.score),H=(d,n)=>!T.find(t=>t[Z][d]*n<P[d]*n),N=[north,east,south,west],x=P[0],y=P[1],l={[N[0]]:-y,[N[1]]:x,[N[2]]:y,[N[3]]:-x},S=[H(1,1),H(0,-1),H(1,-1),H(0,1)],U=bots().find(b=>D(b[Z])==1&&b.score>E.score),r=chars().filter(c=>!bots().find(b=>b.uid!=E.uid&&dist(b[Z],c[Z])-D(c[Z])<1));return r.length?(dirTo(r.sort((a,b)=>D(a[Z])-D(b[Z]))[0][Z])):T.length?U?(N.filter((n,i)=>S[i]).sort((a,b)=>l[a]-l[b])[0]||(_=>_))():0:(turn()%20||U?dirTo("00"):build(E.source))}
}

Almost unkillable. Avoids dangerous bots, and any characters it knows it can't get. Reproduces insanely fast. Its priorities:

  1. If there is a character closer to it than any other bot, go toward it (there is no way this can kill it)
  2. If there is a dangerous bot two spaces away, but none one space away, stop for one turn (this makes it so that the dangerous bot will never be able to kill Shy Guy, preventing it from being locked in place)
  3. If there is still a dangerous bot but it wouldn't be safe to stop, move in a safe direction, preferably toward [0, 0]
  4. If it's safe to stop for a turn, and the turn number is divisible by twenty, attempt to create a new worker
  5. Go toward [0, 0]
\$\endgroup\$
2
  • \$\begingroup\$ After staring for 15 minutes at 100k turns with Shy Guy, I concluded that this is the bot that has the best chances of winning \$\endgroup\$
    – Dion
    Jan 31, 2021 at 5:38
  • \$\begingroup\$ @Dion It's definitely possible to optimize it, and its workers to manage to die quite often. There's a few days left on the bounty, if you're interested in trying to beat it! \$\endgroup\$ Jan 31, 2021 at 6:12
3
\$\begingroup\$

Camper

{
name: "Camper",
color: "#00FF00",
run:_=>dirTo(chars().sort((a,b)=>dist("00",a.pos)-dist("00",b.pos))[0].pos)
}

Camps the center, and collects the characters closest to it, since the center has a higher density of those.

var botData = [
{
    name: "ExampleBot",
    color: "#aaaaaa",
    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)
},
{
    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: "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: "Centrist",
    color: "#666666",
    run:_=>dirTo(center())
},
{
name: "Rabbit",
    color: "#FFC0CB",
    run:_=>turn()%1e3?dirTo(chars()[0].pos):build(self().source)
},{
    name: "Shy Guy",
    color: "#dd0044",
    run: _=>{var Z="pos",E=self(),P=E[Z],D=distTo,T=bots().filter(b=>D(b[Z])==2&&b.score>E.score),H=(d,n)=>!T.find(t=>t[Z][d]*n<P[d]*n),N=[north,east,south,west],x=P[0],y=P[1],l={[N[0]]:-y,[N[1]]:x,[N[2]]:y,[N[3]]:-x},S=[H(1,1),H(0,-1),H(1,-1),H(0,1)],U=bots().find(b=>D(b[Z])==1&&b.score>E.score),r=chars().filter(c=>!bots().find(b=>b.uid!=E.uid&&dist(b[Z],c[Z])-D(c[Z])<1));return r.length?(dirTo(r.sort((a,b)=>D(a[Z])-D(b[Z]))[0][Z])):T.length?U?(N.filter((n,i)=>S[i]).sort((a,b)=>l[a]-l[b])[0]||(_=>_))():0:(turn()%20||U?dirTo("00"):build(E.source))}
},
{
name: "The Luggage",
color: "#8B4513",
run:()=>dirTo(bots()[0].pos)
},
{
name: "Camper",
color: "#00FF00",
run:_=>dirTo(chars().sort((a,b)=>dist("00",a.pos)-dist("00",b.pos))[0].pos)
}
];

// _=>{var r,d,e,l,I,N,P,D=distTo,s=self(),p=s[P="pos"],w="_=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)",n=w.split``;s.chars.map(c=>n[n[N="indexOf"](c)]="");if(!n.find(_=>_))return build(w);r=chars().filter(c=>!bots().find(b=>b.uid!=s.uid&&dist(b[P],c[P])-D(c[P])<1));if(r.length)return dirTo(r.sort((a,b)=>D(a[P])-D(b[P])+(n[I="includes"](b)-n[I](a))*10)[0][P]);l={n:-p[1],e:p[0],s:p[1],w:-p[0]};d="nesw".split``.sort((a,b)=>l[a]-l[b]);e=bots().filter(b=>b.uid!=s.uid&&b.score>=s.score&&D(b[P])<5).map(b=>d[d[N](dirTo(b[P])[0][0])]="");return[north,east,south,west]["nesw"[N](d.find(x=>x))]()}

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 =*/ 240,/* zoom =*/ 50);
<body></body>

\$\endgroup\$
3
\$\begingroup\$

Ghost

{
    name: "Ghost",
    color: "#5599dd",
    run: _=>{var f="filter",d=distTo,n=dist,e=self(),p=e.pos,h=bots()[f](b=>b.uid!=e.uid),r=chars()[f](c=>!h.find(b=>n(b.pos,c.pos)<=d(c.pos))),t=h[f](b=>b.score>=e.score),o=(l,i)=>!t.find(t=>n(t.pos,[p[0]+l,p[1]+i])<2),s=(l,i)=>n([0,0],[p[0]+l,p[1]+i]),T=[o(0,0),o(0,-1),o(1,0),o(0,1),o(-1,0)],L=[s(0,0),s(0,-1),s(1,0),s(0,1),s(-1,0)];return r[0]?turn()%50||!o(0,0)?dirTo(r.sort((a,b)=>d(a.pos)-d(b.pos))[0].pos):build(e.source):[_=>_,north,east,south,west][[0,1,2,2+1,5-1][f](i=>T[i]).sort((a,b)=>L[a]-L[b])[0]||0]()}
}

A bot loosely inspired by Shy Guy, but with so many optimizations it's almost guaranteed to be unbeatable.

Ghost is similar to Shy Guy in that it's almost unkillable. Except it's way, way smarter. It's shorter. Its source is optimized to use more common characters. It uses much simpler logic.

Here's how it works:

  1. If there are any characters closer to Ghost than any other bot, go toward the nearest one
  2. Otherwise, stay still or go in any direction which is 100% guaranteed not to kill Ghost (preferring ones which bring it closer to [0, 0])
  3. Finally, if there is no safe direction to go, stay still
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Answering your own question again, lol! :) Ghost's behavior is interesting because if there are enough workers, they'll line up in checkerboard patterns. I think that the only way a submission could beat ghost is if it replicates faster or has a more efficient character finding algorithm. That's going to be difficult to do since Ghost is already well optimized. \$\endgroup\$
    – qwatry
    Feb 2, 2021 at 13:42
2
\$\begingroup\$

True Neutral

{
    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)
}

Goes for the characters which are:

  • Close to itself
  • Close to [0, 0] (where the most characters are)
\$\endgroup\$
2
\$\begingroup\$

Centrist, 18 bytes(-9 points)

{
    name: "Centrist",
    color: "#666666",
    run:_=>dirTo(center())
}

Even dumber than our friendly neighbourhood copycat. Just follows the crowd and hopes that he can live. Even his color is dull.

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)
}
 
];


{
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);

\$\endgroup\$
2
  • \$\begingroup\$ I was just experimenting with a bot exactly like this :p \$\endgroup\$ Jan 28, 2021 at 16:00
  • 1
    \$\begingroup\$ This one produces really funny results for how boring it is. \$\endgroup\$
    – Razetime
    Jan 28, 2021 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.