Note: Bot Factory KoTH (2.0) is now posted; you're probably looking for that one instead!

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


Average number of characters collected per game (20k rounds)

[1484.5] The Forsaken Farmer
[1432.9] The Replicator
[957.9] Safe Player V2
[739.1] Hunter
[101.65] First
[9.75] IDKWID

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 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.


Bots have a score, which is initially -floor(sqrt(LEN)), 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.


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 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.


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


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.

The winner is the bot which, after some number of rounds, has collected (or had workers collect) the most characters.


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

Controller: https://gist.github.com/Radvylf/31408373bae12843bfbf482188653a42

Edit: Fixed the controller. Made a few changes before putting it in the gist to clean up some debugging and deleted a few things by mistake. Sorry about that, should be up and running!

Due Date: Challenge completed

Prize: Bounty (100 reputation)

Chatroom: https://chat.stackexchange.com/rooms/105613/bot-factory-koth


6 Answers 6


Safe Player V2


Finds the closest character that it can get to first and goes towards it. If no such character exists, it repeats the process but this time ignoring bots with a lower score than itself. Goes to the center if it still can't lock onto any character.


The Forsaken Farmer

This entry finds the nearest character and then proceeds to repeatedly drop it and pick it up, netting a total of 1 score per two moves... except it doesn't, because the spec was updated to account for that. No matter, though - winning is dependent on character pick up count, not score.



Short for "I don't know what I'm doing".


Greedily tries to get the closest character to it. I think. A couple of minutes of thinking have led me to believe that this is not the case. It does something stupid instead. Often loses to Example Bot.

  • \$\begingroup\$ Did you manage to get the rounds to draw something? \$\endgroup\$
    – RGS
    Commented Mar 15, 2020 at 17:11
  • \$\begingroup\$ @RGS Nope! It's the main reason why this entry is called what it is! \$\endgroup\$
    – Alion
    Commented Mar 15, 2020 at 17:12
  • \$\begingroup\$ That won't work. distTo works on position arrays. The function in the sort would have to be (a,b)=>distTo(a.pos)-distTo(b.pos). Will clarify, was trying to keep the specification compact. (-: \$\endgroup\$ Commented Mar 15, 2020 at 21:17
  • \$\begingroup\$ @RGS That should be fixed now. Deleted a few things I shouldn't have when removing some debugging code...tested and everything works fine. \$\endgroup\$ Commented Mar 15, 2020 at 21:44

The Replicator

Presumably replicates itself... very slowly. Probably replicates itself, but if I'm understanding the situation correctly, then a replication attempt currently crashes the runner (hasChars is not defined at runTurn (<anonymous>:205:33)). Rudimentarily code-golfed.

_=>{d=distTo;c=s=>s.reduce((a,v)=>({...a,[v]:(a[v]|0)+1}),{});s=self();a=c(s.chars);b=c((s=s.source).split``);for(v in b)if(!(b[v]<=a[v]))return dirTo(chars().sort((a,b)=>d(a.pos)-d(b.pos))[0].pos);return build(s)}



Just go towards the first char. This has an initial score of -4, much better than most.



This bot will go towards the closest object that is a char or a bot with less score than it.


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