title: Art Attack KoTH

text:
# Question
Title: Art Attack KoTH
> **Entries are now closed. Any new entries or edits will not be counted in the final run.**

## Join the chat!

## Challenge

Try to fill the canvas with as much paint as possible. Be careful of other bots that may paint over your hard work though!

***Note:*** In this challenge description, *paint* means to change the colour of the square on the grid and *unpainted* means that the square on the grid has the colour 0 and is not attributed to any of the bots.

## Input

Your function will be given four arguments: yourself, the grid, the position of all bots on the grid and game information.

### Myself

This is a 1D array which denotes your colour and position on the grid: `[id, xpos, ypos]`.

**The top left corner of the grid is the position `(0, 0)`. The position `(1,0)` is to the right of that and the position `(0,1)` is below**

Your id is an integer which is synonymous with your colour (see below to find out how your id affects how you paint the grid). Your ID is unique to your bot.

### Grid

This is a 2D array which contains integers which tell you what colour each cell is. If the number of a grid cell is `0`, that means that the cell is unpainted. If the number of grid cell is an integer `x`, this means that the cell has been painted by the bot with the ID `x`.

To get the colour of the grid at position `(x, y)`, use the array like so: `grid[x][y]`.

### Bots

This is an array which contains information about the position of the bots. Each element of the bots array is an array which describes each bot and looks like: `[id, xpos, ypos]`, where `id` is the ID of the bot, `xpos` is the x position of the bot and `ypos` is the y position of the bot.

This array includes your own bot's position and id. Eliminated bots will not be included in this array.

### Game Information

This is an array containing information about the current game and looks like: `[roundNum, maxRounds]` where `roundNum` is the number of the current round (1-indexed) and `maxRounds` is the number of rounds in the current game.

## Output

The output should be a string returned by your function. This is the movement command.

The movement command determines your next move. The available commands are:

```
up
down
left
right
wait

```

Whenever you move, you paint the square you move to. (see below for more information)

Where `wait` means you do not move. (but you paint the square that you stay on)

If you try to move outside of the grid, your command will be ignored and you will stay in the same place.

## Painting the grid

Whenever you move to a square, you paint it, but there are rules which determine what the colour of that square will be.

If the square is unpainted (0), then you simply paint it the same colour as your own ID. However, if the square has been painted previously (non-zero) then the resulting colour of the square will be found according to the following JavaScript code:

```
[botColour, 0, floorColour][Math.abs(botColour - floorColour)%3]

```

This formula is made so as to allow a bot to move over its own colour without repainting it.

## Elimination

If, after round 5, you have one or fewer squares painted (the number of squares on the grid which are the same colour as you) then you will be eliminated. This means that you will no longer be in the game and will automatically lose.

## Rules

* Your code must a function of the type

```
function(myself, grid, bots, gameInfo) {
    // Code here
    return move;
}

```
* The grid will be a square of side length \\$\text{Number of competing bots} \times 3\\$
* To prevent specific bots from being targeting, the bots' IDs will be randomised.
* When two bots occupy the same space, the colour of that space will be made unpainted.
* Movement is turn-based i.e. during a round, all bots are supplied with identical `grid`, `bots` and `gameInfo` arguments

* You may create a maximum of three bots
* Bots may work together but must not communicate with each other and will not know each others IDs. The wins will be awarded individually rather than as a team.
* You must not create a bot which intentionally targets a single, pre-chosen bot. You may, however, target the tactics of a general class of bots.
* Your bot may store data in `window.localStorage`. Each bot must use their own data object. If a bot is found to be reading another bot's data (accidentally or on purpose) it will be disqualified until the issue is resolved.
* If your bot uses random numbers, please use `Math.random()`

## Controller

The controller can be found here:

https://gist.github.com/beta-decay/10f026b15c3babd63c004db1f937eb14

Or you can run it here: https://beta-decay.github.io/art\_attack

***Note:*** I would advise that you do any testing offline (download the controller from the gist) as the webpage is subject to change at any moment.

When all of the bots have been added, I will run the 10,000 games with the stripped down controller with no graphical interface. You can run it here: https://beta-decay.github.io/art\_attack/fast

## Winning

The player who has filled the most of the canvas wins the game (a game is 2000 rounds). In the event of a draw, all drawn players win.

The player which wins the most out of 10,000 games wins the challenge.

The 10,000 games is estimated to be run next Monday (2018-08-27 at 23:00 UTC+1).

# Answer
> 13 votes
# Jim

```
function([mc, mx, my], grid, bots, [round, maxRound]) {const ID = 2;
  var S = this;
  const botAm = 3;
  function log(...args) {
    //if (round > 1) console.log(ID+" "+args[0], ...args.slice(1));
    return true;
  }
  if (round == 1) {
    var all = new Array(bots.length).fill().map((_,i)=>i+1);
    S.fs = new Array(botAm).fill().map(c =>
      [all.slice(), all.slice(), all.slice(), all.slice()]
    );
    S.doneSetup = false;
    var center = grid.length/2;
    // UL=0; DL=1; DR=2; UR=3
    S.dir = mx<center? (my<center? 0 : 1) : (my<center? 3 : 2);
    S.job = 0;
    S.setupFail = S.finished = false;
    S.tbotc = undefined;
    S.botAm = bots.length;
    S.botEvilness = new Array(bots.length+1).fill(0);
    S.keys = [[1,1,0,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,0,0],
              [0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,0,0,0,1,1],
              [1,0,0,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,0,1,1,1,1,0,1,1,1,0]];
    /*if (ID == 2) */{
      S.chased = 0;
      S.ignore = [];
      S.badMoves = 0;
      S.pastMoves = new Array(100).fill("-1;0");
      S.timer = 0;
      S.jimFn = function([mc, mx, my], grid, bots, [round, maxRound]) { // ---------- BEGIN JIM ---------- \\
        var output;
        var allowRetracing = false;

        var checkSize = 3;
        var eatSize = 5;
        var myScore;
        var scoreboard;

        if (grid[mx][my] == 0 && !bots.some(([col, bx, by])=> col != mc && bx==mx && by==my)) return "wait"; // collect those sweet points

        // rescore every now and then
        if (S.timer > 200) rescore();

        S.pastMoves.push(mx+";"+my);
        S.pastMoves.shift();

        var orth = [[-1,0],[0,-1],[1,0],[0,1]];
        if (S.atTarget
        || S.targetX === undefined || S.targetY === undefined
        || S.targetX === mx && S.targetY === my
        || orth.map(([x,y])=>[mx+x,my+y]).filter(c=>get(c)==0 && inbounds(c)).length > 2) {

          S.atTarget = true;
          var neighbors = orth
            .map(([x,y]) => [x+mx, y+my])
            .filter(inbounds)
            .filter(([x,y]) => !bots.some(([bid, bx, by]) => bx==x && by==y))
            .map(c=>[c,get(c)]);

          let test = (neighbors, f, msg) => {
            return bestOf(neighbors.filter(f).map(c=>c[0])) && log(msg);
          }

          if (test(neighbors, ([,c]) => c===0, "good")) return output;
          if (test(neighbors, ([,c]) => overMap(c, 1) && S.BCs,  "sad")) return output;

          S.atTarget = false;
          S.targetX = S.targetY = undefined;
          let bestScore = 7;
          let bfscore = 0;

          for (let dist = 4; dist < 8; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds([cx, cy]) && grid[cx][cy] === 0  ) {
                  let score = scoreOf(cx, cy, 1, false);
                  if(score>bfscore)bfscore=score;
                  if (score > bestScore) {
                    bestScore = score;
                    S.targetX = cx;
                    S.targetY = cy;
                  }
                }
              }
            }
          }
          if (S.targetX) {
            log("short goto", S.targetX, S.targetY,"(rel",S.targetX-mx, S.targetY-my,") score", bestScore);
            return to([S.targetX, S.targetY]);
          } else log("long goto",bfscore);

          rescore();
          return to([S.targetX, S.targetY]);
        } else log("going to target", S.targetX, S.targetY);

        return to([S.targetX, S.targetY]);

        function myScore() {
          if (!myScore) calculateScoreboard();
          return myScore;
        }
        function calculateScoreboard() {
          scoreboard = grid.map(column=> {
            var arr = new Int16Array(grid.length);
            column.forEach((c, x) => (
              myScore+= c==mc,
              arr[x] = overMap(c, 1, 0, 0, 0, 5)
            ));
            return arr;
          });
          for (let [bc, bx, by] of bots) if (bc != mc) {
            scoreboard[bx][by] = -100;
            if (inbounds([bx-2, by])) scoreboard[bx-2][by] = -50;
            if (inbounds([bx+2, by])) scoreboard[bx+2][by] = -50;
            if (inbounds([bx, by-2])) scoreboard[bx][by-2] = -50;
            if (inbounds([bx, by+2])) scoreboard[bx][by+2] = -50;
          }
        }
        function scoreOf (x, y, size, includeEnemies) {
          if (!scoreboard) calculateScoreboard();
          let score = 0;
          for (let dx = -size; dx <= size; dx++) {
            let cx = dx + x;
            if (cx < 1 || cx >= grid.length-1) continue;
            for (let dy = -size; dy <= size; dy++) {
              let cy = dy + y;
              if (cy < 1 || cy >= grid.length-1) continue;
              let cs = scoreboard[cx][cy];
              if (cs > 0 || includeEnemies) score+= cs;
            }
          }
          return score;
        }
        function rescore() { // heatmap of best scoring places
          //log(JSON.stringify(scoreboard));
          S.bestScore = -Infinity;
          var blur = grid.map((column, x)=>column.map((c, y) => {
            let score = scoreOf(x, y, checkSize, true);
            if (score > S.bestScore) {
              S.bestScore = score;
              S.targetX = x;
              S.targetY = y;
            }
            return score;
          }));
          S.atTarget = false;
          S.timer = 0;
          S.bestScore = scoreOf(S.targetX, S.targetY, eatSize);
          S.badMoves = 0;
          // log("scored to", S.targetX, S.targetY, S.bestScore);
        }
        function over(col) { // 1 if overrides happen, -1 if overrides don't happen, 0 if override = 0
          let res = Math.abs(mc-col) % 3;
          return res==1? 0 : res==0? 1 : -1;
        }
        function overMap(col, best = 0, good = 0, bad = 0, mine = 0, zero = 0) { // best if overrides happen, bad if overrides don't happen, good if override = 0
          let res = Math.abs(mc-col) % 3;
          return col == 0? zero : col == mc? mine : res==1? good : res==0? best : bad;
        }
        function iwin   (col) { return over(col) == 1; }
        function zeroes (col) { return over(col) == 0; }
        function to([x, y]) {
          //debugger
          var LR = x > mx? [mx+1, my] : x < mx? [mx-1, my] : null;
          var UD = y > my? [mx, my+1] : y < my? [mx, my-1] : null;
          if (LR && UD) {
            var LRScore = overMap(LR, 1, 0, 0, 0, 3);
            var UDScore = overMap(UD, 1, 0, 0, 0, 3);
            if (LRScore == UDScore) return toPos([LR, UD][Math.random()>.5? 1 : 0])
            else if (LRScore > UDScore) return toPos(LR);
            else return toPos(UD);
          } else return toPos(LR || UD || [x, y]);
        }
        function toPos([x,y]) {
            if (x > mx) return "right";
            if (x < mx) return "left";
            if (y < my) return "up";
            if (y > my) return "down";
            return 'wait';
        }
        function inbounds([x, y]) {
          // if (x<grid.length && y<grid.length && x>=0 && y>=0) return true;
          if (x<grid.length-1 && y<grid.length-1 && x>=1 && y>=1) return true;
          return false;
        }
        function get([x,y]) {
          if (inbounds([x, y])) return grid[x][y];
          return 0;
        }
        function bestOf (arr) {
          if (arr.length == 0) return false;
          var bestScore = -Infinity;
          var bestPos;
          for (var [x, y] of arr) {
            let score = 0;
            for (var [bcol, bx, by] of bots) {
              let dist = Math.sqrt((x-bx)**2 + (y-by)**2);
              let res = over(bcol);
              let power = res==0? 1 : res==1? 0.4 : 1.4;
              score+= power * dist;
            }
            score-= Math.sqrt((x-S.targetX)**2 + (y-S.targetY)**2);
            if (S.pastMoves.includes(x+";"+y)) score-= 1000000;

            if (score > bestScore) {
              bestScore = score;
              bestPos = [x,y];
            }
          }
          if (bestScore < -500000) {
            if (allowRetracing) log("RETRACING");
            else return false;
          }
          output = to(bestPos);
          return true;
        }
      } // ---------- END JIM ---------- \\
    }
  }
  const dirs = ['up','left','down','right'];

  if (!S.doneSetup && round < 37) { // ---------- HANDSHAKE ---------- \\
    let finished = 0;
    if (round != 1) {
      for (let id = 0; id < botAm; id++) {
        let f = S.fs[id];
        let remaining = f.map(c=>c.length).reduce((a,b)=>a+b);
        if (remaining == 1) {
          finished++;
          continue;
        }
        if (remaining == 0) {
          // mourn the loss of a good friend
          finished++;
          continue;
        }
        for (let dir = 0; dir < 4; dir++) {
          let possible = f[dir];

          for (let i = possible.length-1; i >= 0; i--) {
            let bc = possible[i];
            let curr =       bots.find(c=>c[0]==bc);
            let prev = S.pastBots.find(c=>c[0]==bc);
            if (!curr || !prev) {
              possible.splice(i,1);
              continue;
            }
            let dx = curr[1]-prev[1];
            let dy = curr[2]-prev[2];
            let move;
            if (dy == 0) {
              if (dx == 1) move = 'right';
              else         move =  'left';
            } else {
              if (dy == 1) move =  'down';
              else         move =    'up';
            }
            let omove = rotate(move, dir);
            let expected = ['down','right'][S.keys[id][round-1]];
            // if (id == 0 && dir == S.dir) log();
            if (omove != expected) possible.splice(i,1);
          }
        }
      }
    }
    S.pastBots = bots;
    if (finished == botAm) {
      S.doneSetup = true;
      S.pastBots = undefined;
      S.BCs = new Array(botAm).fill().map((_,i) => (S.fs[i].find(c=>c.length > 0) || [-1])[0]); // AKA idtoc
      S.fighters = S.BCs.slice(0,2);
      S.ctoid = {[S.BCs[0]]:0, [S.BCs[1]]:1, [S.BCs[2]]:2};
      log("identified", S.BCs);
      if (ID == 2) {
        log("can beat", bots.filter(c=>S.fighters.filter(b=>Math.abs(b-c[0])%3 != 2).length > 0).map(c=>c[3]));
      }
    } else {
      // log(ID,S.fs);
      return rotate(['down','right'][S.keys[ID][round]], S.dir);
    }
  }
  if (!S.doneSetup) { // HANDSHAKE FAILED
    S.setupFail = true;
    S.BCs=[];
    S.fighters = [];
    S.ctoid = {};
  }

  if (S.pastGrid) for (let [bc, bx, by] of bots) { // calculate bot evilness
    let prev = S.pastGrid[bx][by];
    let fID = S.BCs.indexOf(prev);
    if (fID === 2) S.botEvilness[bc]+= 10;
    else if (fID !== -1) S.botEvilness[bc]+= 5;
    else {
      let over = Math.abs(bc - prev) % 3;
      if (over === 0) S.botEvilness[bc]+= 1;
      else if (over === 1) S.botEvilness[bc]+= 2;
    }

  }

  S.pastGrid = grid;

  if (ID == 2) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]);

  if (S.setupFail || !bots.find(c=>c[0]==S.fighters[1-ID])) return 'wait'; // for my demise
  // TODO yeah no

  if (round < 50 || !bots.find(c=>c[0]==S.BCs[2])) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]); // if Jim's dead or if it's early game, be Jim so others don't win needlessly/scoreboard becomes more clear

  let tbot = bots.find(c=>c[0] == S.tbotc);

  // ---------- NEW TARGET ---------- \\
  let tried;

  // {
  //   let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
  //   for (let column of grid) for (let item of column) scores[item]++;
  //   log("scores", scores.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  //   log("evilness", S.botEvilness.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  // }

  let makeSureImNotStupidAgain = 0;
  while ((!S.tbotc || !tbot) && !S.finished) {
    makeSureImNotStupidAgain++;
    if (makeSureImNotStupidAgain > 100) {
      console.log("dzaima is stupid");
      S.finished = true;
      break;
    }
    if (!tried) tried = S.BCs.slice();
    S.gotoX = S.gotoY = undefined;
    let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
    for (let column of grid) for (let item of column) scores[item]++;
    var bbc, bbs=-Infinity;
    for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && !tried.includes(i)) {
      bbs = scores[i];
      bbc = i;
    }
    S.tbotc = bbc;
    tbot = bots.find(c=>c[0] == bbc);
    if (!tbot) {
      tried.push(bbc);
    } else {
      S.jobs = [0,0];
      let executers = S.fighters.filter(c=>Math.abs(c-bbc)%3 == 1).concat(S.fighters.filter(c=>Math.abs(c-bbc)%3 == 0));
      if (executers.length > 1) {
        S.jobs[S.ctoid[executers.pop()]] = 1;
        S.jobs[S.ctoid[executers.pop()]] = 2;
        //S.jobs.forEach((c,id) => c==0? S.jobs[id]=2 : 0);
        log("targetting", botName(bbc),"jobs",S.jobs);
      } else {
        // cry
        tried.push(bbc);
        S.tbotc = tbot = undefined;
      }
      S.job = S.jobs[ID];
    }
    if (tried.length >= bots.length) {
      // everyone is dead
      S.job = 0;
      S.jobs = new Array(2).fill(0);
      S.finished = true;
      break;
    }
  }

  if (tbot && !S.finished) {
    let [_, tx, ty] = tbot;

    switch (S.job) {
      case 1: // follow
        return to(tx, ty, S.tbotc);
      break;
      case 2: // erase
        let endingClearing = false;
        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          S.gotoX = undefined;
          var ending = [S.tbotc, ...S.fighters.filter(c=>c != mc)].map(c => bots.find(b=>b[0]==c)).filter(I=>I);
          search: for (let dist = 1; dist < grid.length*2+2; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds(cx, cy)) {
                  if (grid[cx][cy] == S.tbotc && ending.every(([_,bx,by]) => (bx-cx)**2 + (by-cy)**2 > Math.random()*10)) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
          if (S.gotoX === undefined) {
            let available = [];
            grid.forEach((column, x) => column.forEach((c, y) => c==S.tbotc? available.push([x,y]) : 0));
            [S.gotoX, S.gotoY] = available[Math.floor(Math.random()*available.length)];
            endingClearing = true;
          }
        }
        return to(S.gotoX, S.gotoY, endingClearing? undefined : S.tbotc);
      break;
      case 0: // exercise

        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          let scores = new Uint32Array(S.botAm+1);
          for (let column of grid) for (let item of column) scores[item]++;
          var bbc, bbs=-Infinity;
          for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && Math.abs(mc-i)%3 == 0 && !S.BCs.includes(i)) {
            bbs = scores[i];
            bbc = i;
          }
          if (bbc) {
            S.gotoX = undefined;
            search: for (let dist = 1; dist < grid.length*2+2; dist++) {
              for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
                for (let i = 0; i < dist; i++) {
                  let cx = dx*i + dsx*dist + mx;
                  let cy = dy*i + dsy*dist + my;
                  if (inbounds(cx, cy) && grid[cx][cy] == bbc) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
        }
        if (S.gotoX !== undefined) return to(S.gotoX, S.gotoY);
        return dirs[Math.floor(Math.random()*4)];
      break;
    }
  }

  function to (x, y, col) {
    if  (x == mx&&y== my) return 'wait';
    let dx =   x    - mx ;
    let dy =      y - my ;
    let ax = Math.abs(dx);
    let ay = Math.abs(dy);
    var          diag;
    if   (     ax==ay   ) {
      if (col&&ax+ ay==2) {
        let i=[[x, my], [mx, y]].findIndex(c=>grid[c[0]][c[1]]==col);
        if (i<0) diag = Math.random()>=.5;
        else     diag =           i  == 0;
      } else     diag = Math.random()>=.5;
    }
    if (ax==ay?  diag :  ax>ay) {
      if (dx>0) return 'right';
      else      return  'left';
    } else {
      if (dy>0) return  'down';
      else      return    'up';
    }
  }

  function rotate (move, dir) {
    if ((move == 'up' || move == 'down') && (dir && dir<3)) {
      if (move == 'up') return 'down';
      else return 'up';
    }
    if ((move == 'left' || move == 'right') && dir>1) {
      if (move == 'left') return 'right';
      else return 'left';
    }
    return move;
  }
  function botName(id) {
    let bot = bots.find(c=>c[0]==id);
    if (!bot) return id.toString();
    return bot[3] + "/" + id;
  }
  function inbounds(x, y) { return x<grid.length && y<grid.length && x>=0 && y>=0 }
}

```

The simple explanation of the strategy of this bot is as follows:

* win

# Answer
> 12 votes
# Trollbot

Picks the closest bot it can paint over and just follows it. If it can't find a valid bot, go to the nearest empty space. If it can't find an empty space, move randomly.

Note: Lots of good contributions by Simon

```
function(myself, grid, bots, gameInfo) {
    var c = myself[0];
    var x = myself[1];
    var y = myself[2];

    var cd = -1;
    var cx = -1;
    var cy = -1;
    var i;
    for(i = 0; i < bots.length; i++){
        var bc = bots[i][0];
        var bx = bots[i][1];
        var by = bots[i][2];

        if (c != bc && Math.abs(c-bc)%3 == 0) {
            var d = Math.abs(x-bx)+Math.abs(y-by);

            if (d > 0 && (cd == -1 || d<cd)) {
                cd = d;
                cx = bx;
                cy = by;
            }
        }
    }

    if (cd == -1) {
        var j;
        for(i=0; i<grid.length; i++) {
            for(j=0; j<grid.length; j++) {
                if (grid[i][j] == 0) {
                    var d = Math.abs(x-i)+Math.abs(y-j);
                    var sharingWithBot = (i == x && j == y && bots.filter((item) => item[1] == i && item[2] == j).length > 1);
                    if (!sharingWithBot && (cd == -1 || d<cd)) {
                        cd = d;
                        cx = i;
                        cy = j;
                    }
                }
            }
        }
    }

    var move;
    var dx = cx-x;
    var dy = cy-y;
    if (cd == -1) {
        move = ["up","down","left","right"][Math.random() *4 |0];
    } else if (dx == 0 && dy == 0) {
        move = "wait";
    } else if (Math.abs(dx) > Math.abs(dy) || (Math.abs(dx) == Math.abs(dy) && Math.random() * 2 < 1)) {
        if (dx > 0) {
            move = "right";
        } else {
            move = "left";
        }
    } else {
        if (dy > 0) {
            move = "down";
        } else {
            move = "up";
        }
    }
    return move;
}

```

# Answer
> 10 votes
## ¯\\\_(ツ)\_/¯ (Random moves)

```
function(myself, grid, bots, gameInfo) {
    return ["up","down","left","right"][Math.random() *4 |0]
}

```

# Answer
> 10 votes
# The Bot That Paints The Board Constantly But Is Not A Painter

```
function (me, board, painters, info) {
    let id = me[0], meX = me[1], meY = me[2], s = board.length, ss = Math.ceil(s / 3), pl = painters.length, r = info[0], storage, sk = 'jijdfoadofsdfasz', s1, s2, scores = [], i, j;

    let bos = [
        [0, 0, ss - 1, ss - 1], [ss, 0, (ss * 2) - 1, ss - 1], [ss * 2, 0, s - 1, ss - 1], [ss * 2, ss, s - 1, (ss * 2) - 1],
        [ss * 2, ss * 2, s - 1, s - 1], [ss, ss * 2, (ss * 2) - 1, s - 1], [0, ss * 2, ss - 1, s - 1], [0, ss, ss - 1, (ss * 2) - 1],
    ];

    if (r === 1 || typeof this[sk] === 'undefined') {
        let n = ss + painters[0][1];
        s1 = bos[n % 8];
        s2 = bos[(n + 1) % 8];
        storage = this[sk] = {s1: s1, s2: s2, bs: null, c: 0};
    } else {
        storage = this[sk];
        s1 = storage.s1;
        s2 = storage.s2;
    }

    let getDistance = function (x1, y1, x2, y2) {
        return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
    };

    let getColorValue = function (c) {
        if (c === 0) return 2;
        if (c === id) return -1;
        let value = 2 - (Math.abs(id - c) % 3);
        if (value === 1) return 0.1;
        return value;
    };

    let getEnemyValue = function (eId) {
        if (eId === id) return 0;
        let value = 2 - (Math.abs(id - eId) % 3);
        return (value === 1 ? 1.75 : value);
    };

    let isInSection = function (x, y, s) {
        return (x >= s[0] && y >= s[1] && x <= s[2] && y <= s[3]);
    };

    let bs = null;
    if (storage.bs === null || storage.c <= 0) {
        let mysi = null;
        for (i = 0; i < bos.length; i++) {
            if (isInSection(meX, meY, bos[i])) mysi = i;
            if ((bos[i][0] === s1[0] && bos[i][1] === s1[1] && bos[i][2] === s1[2] && bos[i][3] === s1[3]) || (r < 5e2 && bos[i][0] === s2[0] && bos[i][1] === s2[1] && bos[i][2] === s2[2] && bos[i][3] === s2[3])) {
                scores[i] = -100000;
            } else {
                scores[i] = 0;
                for (let bX = Math.max(bos[i][0], 1); bX < Math.min(bos[i][2], s - 1); bX++) for (let bY = Math.max(bos[i][1], 1); bY < Math.min(bos[i][3], s - 1); bY++) scores[i] += getColorValue(board[bX][bY]);
                for (j = 0; j < pl; j++) {
                    let pId = painters[j][0], pX = painters[j][1], pY = painters[j][2];
                    if (pId === id || pX === 0 || pX === s - 1 || pY === 0 || pY === s - 1 || !isInSection(pX, pY, bos[i])) continue;
                    scores[i] -= (getEnemyValue(pId) * ss) * 4;
                }
            }
        }
        let bss = null;
        for (i = 0; i < scores.length; i++) {
            if (bss === null || bss < scores[i]) {
                bss = scores[i];
                bs = bos[i];
            }
        }
        if (mysi !== null && scores[mysi] * 1.1 > bss) bs = bos[mysi];
        storage.bs = bs;
        storage.c = 250;
    } else {
        bs = storage.bs;
        storage.c--;
    }

    let getScore = function (x, y) {
        let score = 0;
        if (!isInSection(x, y, bs)) score -= s * 10;
        for (let bX = bs[0]; bX <= bs[2]; bX++) {
            for (let bY = bs[1]; bY <= bs[3]; bY++) {
                let distance = getDistance(x, y, bX, bY);
                let colorValue = getColorValue(board[bX][bY]);
                let factor = 1;
                if (distance === 1) factor = 3;
                else if (distance === 2) factor = 2;
                score += (colorValue / (distance / 4)) * factor;
                if (x === meX && y === meY && x === bX && y === bY && colorValue < 2) score -= 1000;
            }
        }
        for (let i = 0; i < pl; i++) {
            let pId = painters[i][0], pX = painters[i][1], pY = painters[i][2];
            if (pId === id || pX === 0 || pX === s - 1 || pY === 0 || pY === s - 1) continue;
            let pDistance = getDistance(x, y, pX, pY);
            if (pDistance > 5) continue;
            let pIdValue = getEnemyValue(pId);
            let factor = 4;
            if (pDistance === 1) factor = 8;
            else if (pDistance === 2) factor = 6;
            else score -= (pIdValue / pDistance) * factor;
        }
        return score + (Math.random() * 10);
    };

    if (isInSection(meX, meY, bs)) {
        let possibleMoves = [{x: 0, y: 0, c: 'wait'}];
        if (meX > 1) possibleMoves.push({x: -1, y: 0, c: 'left'});
        if (meY > 1) possibleMoves.push({x: -0, y: -1, c: 'up'});
        if (meX < s - 2) possibleMoves.push({x: 1, y: 0, c: 'right'});
        if (meY < s - 2) possibleMoves.push({x: 0, y: 1, c: 'down'});
        let topCommand, topScore = null;
        for (i = 0; i < possibleMoves.length; i++) {
            let score = getScore(meX + possibleMoves[i].x, meY + possibleMoves[i].y);
            if (topScore === null || score > topScore) {
                topScore = score;
                topCommand = possibleMoves[i].c;
            }
        }
        return topCommand;
    } else {
        let dX = ((bs[0] + bs[2]) / 2) - meX, dY = ((bs[1] + bs[3]) / 2) - meY;
        if (Math.abs(dX) > Math.abs(dY)) return (dX < 0 ? 'left' : 'right');
        else return (dY < 0 ? 'up' : 'down');
    }
}

```

This bot tries to find the best area of the board and moves there. Then tries to make the best decision possible by generating a score for each possible move in that area. The best area keeps getting re-selected on some intervals and the bot moves to a new better area if needed. This bot has a few other details that I might explain later.

# Answer
> 10 votes
# MC

```
function (myself, grid, bots, gameInfo) {

"use strict";

if (this.O == null) this.O = {};
const O = this.O;

// console.log(this);

const MAXBOTS = 60;
const MAXSZ = 3 * MAXBOTS;
const MAXID = MAXBOTS + 1;

if (gameInfo[0] == 1) {
    if (bots.length > MAXBOTS) {
        alert("ASSERTION FAILED: MAXBOTS EXCEEDED (contact @tomsmeding)");
        return 0;
    }

    for (const b of bots) {
        if (b[0] < 0 || b[0] > MAXID) {
            alert("ASSERTION FAILED: MAXID EXCEEDED (contact @tomsmeding)");
            return 0;
        }
    }
}

function from_base64(bs) {
    if (bs.length % 4 != 0) throw new Error("Invalid Base64 string");

    const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    const beta = new Array(256).fill(-1);

    for (let i = 0; i < alpha.length; i++) beta[alpha.charCodeAt(i)] = i;

    const arrbuf = new ArrayBuffer(bs.length / 4 * 3 | 0);
    const buf = new Uint8Array(arrbuf);

    let j = 0;
    for (let i = 0; i < bs.length; i += 4) {
        buf[j++] = (beta[bs.charCodeAt(i+0)] << 2) | (beta[bs.charCodeAt(i+1)] >> 4);
        if (bs[i+2] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+1)] << 4) | (beta[bs.charCodeAt(i+2)] >> 2);
        if (bs[i+3] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+2)] << 6) | (beta[bs.charCodeAt(i+3)] >> 0);
    }

    return new Uint8Array(arrbuf, 0, j);
}

function repeat(str, times) {
    return new Array(times + 1).join(str);
}

function println_func(ptr) {
    let s = "";
    for (; ptr < O.wa_membuf.length; ptr++) {
        if (O.wa_membuf[ptr] == 0) break;
        s += String.fromCharCode(O.wa_membuf[ptr]);
    }
    console.log(s);
}

function print_int_func(value) {
    console.log(value);
}

function seed_random() {
    for (let i = 0; i < O.wa_rand_state.length; i++) {
        O.wa_rand_state[i] = (Math.random() * 256) & 0xff;
    }
}

function transfer_myself(myself) {
    O.wa_my_id[0] = myself[0];
}

function transfer_grid(grid) {
    const W = grid.length, H = grid[0].length;
    O.wa_width[0] = W;
    O.wa_height[0] = H;
    for (let x = 0; x < W; x++) {
        for (let y = 0; y < H; y++) {
            O.wa_grid[W * y + x] = grid[x][y];
        }
    }
}

function transfer_bots(bots) {
    O.wa_nbots[0] = bots.length;
    for (let i = 0; i < bots.length; i++) {
        O.wa_bots[3 * i + 0] = bots[i][0];
        O.wa_bots[3 * i + 1] = bots[i][1];
        O.wa_bots[3 * i + 2] = bots[i][2];
    }
}

function transfer_gameInfo(gameInfo) {
    O.wa_round_idx[0] = gameInfo[0];
    O.wa_total_rounds[0] = gameInfo[1];
}

function stringify(thing) {
    if (Array.isArray(thing)) {
        return "[" + thing.map(stringify).join(",") + "]";
    } else if (thing instanceof Int8Array) {
        return "[" + thing.toString() + "]";
    } else {
        return thing.toString();
    }
}

function mc_calcmove() {
    // console.log("mc_calcmove(" + stringify(myself) + "," + stringify(grid) + "," + stringify(bots) + "," + stringify(gameInfo) + ")");
    transfer_myself(myself);
    transfer_grid(grid);
    transfer_bots(bots);
    transfer_gameInfo(gameInfo);
    return ["right", "down", "left", "up", "wait"][O.wa_mc_calcmove()];
    // return O.wa_mc_calcmove();
}

if (O.wasm_bytes == null) {
    O.wasm_bytes = from_base64(
// INSERT-WASM-HERE
"[encoded data, 17008 characters]"
    );

    // require("fs").writeFileSync("reverse-base64-output.txt", Buffer.from(O.wasm_bytes));

    O.memory = new WebAssembly.Memory({initial: 15});
    // O.importObject = {js: {mem: O.memory}, env: {println: println_func}};
    O.importObject = {
        env: {
            println: println_func,
            print_int: print_int_func,
            __linear_memory: O.memory,
            __indirect_function_table: new WebAssembly.Table({initial: 0, element: "anyfunc"}),
        },
    };

    // let wa_membuf, wa_width, wa_height, wa_nbots, wa_round_idx, wa_total_rounds, wa_my_id, wa_grid, wa_bots, wa_rand_state;

    /*const promise = fetch('../out/main.wasm').then(response =>
        response.arrayBuffer()
    ).then(bytes =>
        WebAssembly.instantiate(bytes, O.importObject)
    );*/
    // const promise = WebAssembly.instantiate(fs.readFileSync("hotpatcher/out.wasm"), O.importObject);
    const promise = WebAssembly.instantiate(O.wasm_bytes, O.importObject);

    promise.then(results => {
        const instance = results.instance;

        // console.log(instance.exports);

        // First set some pointers
        instance.exports.entry_fn(0);

        O.wa_membuf = new Uint8Array(O.memory.buffer);
        const ptrs = new Uint32Array(O.memory.buffer, 0, 9 * 4);

        O.wa_width = new Int32Array(O.memory.buffer, ptrs[0], 1);
        O.wa_height = new Int32Array(O.memory.buffer, ptrs[1], 1);
        O.wa_nbots = new Int32Array(O.memory.buffer, ptrs[2], 1);
        O.wa_round_idx = new Int32Array(O.memory.buffer, ptrs[3], 1);
        O.wa_total_rounds = new Int32Array(O.memory.buffer, ptrs[4], 1);
        O.wa_my_id = new Int32Array(O.memory.buffer, ptrs[5], 1);
        O.wa_grid = new Uint8Array(O.memory.buffer, ptrs[6], MAXSZ * MAXSZ);
        O.wa_bots = new Uint8Array(O.memory.buffer, ptrs[7], MAXBOTS * 3);
        O.wa_rand_state = new Uint8Array(O.memory.buffer, ptrs[8], 5 * 4);

        O.wa_mc_calcmove = function() { return instance.exports.entry_fn(2); }

        seed_random();

        // Signal that we're done setting up, and the wasm code can set itself up
        instance.exports.entry_fn(1);

        O.instantiated = true;
        console.log("MC: Instantiated!");
    }).catch(console.error);
}

if (gameInfo[0] > 5) {
    if (O.instantiated) {
        // const start = new Date();
        const output = mc_calcmove();
        // const end = new Date();

        // if (O.time_sum == null) O.time_sum = 0;
        // O.time_sum += end - start;

        // if (gameInfo[0] % 50 == 0) {
        //     console.log("Average time taken: " + O.time_sum / (gameInfo[0] - 1));
        // }

        return output;
    } else {
        throw new Error("SCREAM FIRE wasm instantiation");
    }
} else {
    console.log("MC: RANDOM MOVE BEFORE INSTANTIATE");
    return ["right", "down", "left", "up"][Math.random() * 4 | 0];
}

}

```

Monte Carlo bot. For each of the five possible moves, it does 100 random playouts, where a "playout" here is 5 random moves from everybody. Only 5 because stuff is unpredictable anyway. At the end of each playout, the bot's score is calculated. The move with the best playout results is taken.

This bot is coded in WebAssembly. See my P bot for a description.

# Answer
> 10 votes
## Leonardo da Pixli

```
function(myself, grid, bots, gameInfo) {
    var ME = this;
    var w='up',a='left',s='down',d='right';
    var ps = [
        [
            // Castlevania Simon Belmont
            16,30,0,11,
            [s,s,d,s,d,d,w,w,d,d,s,a,s,d,d,s,d,s,s,a,s,a,a,s,a,s,s,a,s,s,s,s,s,s,s,a,a,s,s,d,w,d,s,d,d,d,w,w,a,s,a,w,w,d,w,d,w,a,a,s,w,w,w,d,s,d,w,w,a,d,d,w,d,w,d,s,d,s,s,d,s,d,s,d,s,s,s,s,a,s,d,d,d,d,w,a,a,w,d,d,w,a,a,w,d,d,w,a,a,w,d,a,w,a,s,a,w,w,a,d,d,w,w,w,w,a,a,a,a,s,a,w,w,d,d,d,d,d,d,s,w,w,a,a,a,a,a,w,d,d,d,d,w,a,a,a,a,a,a,w,d,d,d,d,d,d,d,w,a,a,a,a,a,a,w,d,d,d,d,d,d,d,d,w,w,s,s,a,a,w,a,a,a,a,a,a,w,w,a,s,a,a,s,a,a,a,d,d,s,d,w,w,d,d,d,d,d,d,d,d,w,d,d,a,w,w,a,s,a,s,a,a,a,w,a,w,d,d,s,d,w,d,w,d,a,a,a,a,a,s,a,a,s,a,a,w,w,w,d,a,w,w,d,w,d,s,d,s,s,d,d,d,w,a,a,w,d,a,w,a]
        ],
        [
            // Final Fantasy White Mage
            17,25,2,10,
            [a,w,a,w,w,w,d,w,d,w,d,w,d,d,w,d,d,w,d,d,d,w,d,d,d,d,d,s,d,s,s,s,s,s,a,a,w,a,w,s,d,s,d,s,s,s,s,a,s,s,a,d,s,d,s,d,s,s,s,s,s,s,s,s,s,a,s,a,a,w,w,s,a,s,a,a,w,w,w,s,a,s,a,w,s,s,a,a,w,s,a,a,w,a,s,w,a,w,w,d,a,a,w,w,w,d,s,w,d,d,d,s,d,s,d,s,w,w,d,a,a,a,w,w,d,w,d,a,a,w,w,d,d,d,w,d,s,d,d,d,s,d,s,s,w,w,a,w,a,a,a,a,a,a,a,s,a,a,a,s,a,s,w,w,w,w,d,w,d,d,w,w,w,d,d,s,s,w,w,d,d,d,d,s,s,s,w,w,d,w,a,w,a,w,a,w,a,s,s,d,a,a,w,w,a,s,a,w,a,s,a,s,d,d,s,a,d,s,s,a]
        ],
        [
            // Megaman
            21,24,11,0,
            [d,s,d,s,s,d,d,s,d,s,s,s,s,w,w,w,w,a,a,s,s,s,w,w,a,a,s,s,a,w,d,w,w,a,w,w,a,w,a,a,s,a,s,d,d,w,s,d,s,a,a,a,a,s,d,d,a,s,a,d,s,s,s,d,a,a,a,s,a,a,s,s,a,s,s,s,d,d,w,a,w,w,d,w,d,s,w,d,w,d,s,d,d,d,d,d,d,d,w,w,a,a,a,a,d,d,d,d,s,d,d,d,s,s,d,s,s,s,a,a,w,d,w,w,a,w,a,s,w,a,a,s,a,a,a,a,a,a,s,d,d,d,d,d,d,s,a,a,a,a,a,a,s,d,d,d,d,d,d,s,a,a,a,a,a,a,s,a,s,a,s,d,s,a,a,a,s,a,a,d,d,d,d,d,d,w,w,w,d,s,w,w,d,s,w,d,d,s,d,s,d,s,s,d,d,d,d,d,d,a,a,w,a,a,a,w,d,w,a,w,a,s,a,w]
        ],
        [
            // Mario mushroom
            16,16,7,0,
            [a,s,a,a,s,a,s,a,s,s,a,s,s,s,s,s,s,d,s,d,s,s,d,s,d,d,d,d,d,d,d,d,d,w,d,w,w,d,w,d,w,w,w,w,w,w,a,w,w,a,w,a,w,a,a,w,a,a,a,s,d,s,a,s,d,d,s,a,a,a,w,s,a,a,a,w,s,d,s,d,a,s,s,s,a,s,a,a,s,d,s,d,w,d,w,d,s,d,s,s,w,w,d,d,d,s,s,w,w,d,w,d,s,d,s,d,w,d,w,a,a,w,a,w,w,w,a,w,d,d,w]
        ],
        [
            // Mario Bullet Bill
            16,14,15,0,
            [a,a,s,a,s,s,d,d,w,d,s,s,s,s,s,s,s,s,s,s,s,a,w,w,w,w,w,w,w,w,w,a,s,s,s,s,s,s,s,s,a,w,w,w,w,w,w,a,w,d,w,a,w,w,a,s,s,s,a,w,w,w,a,s,s,s,s,s,w,a,w,w,w,w,s,a,a,s,d,s,s,a,w,s,s,a,w,s,s,s,s,d,s,d,d,d,d,w,d,d,w,s,s,s,s,a,w,w,a,s,s,a,w,a,s,a,w,a,a,w,a,w,w,w,w,a,s,s,s,s,w,a,w,w,w,a,s,w,w,w,w,d,d,s,w,w,a,d,d,w,a,d,d,w,d,s,w,d,w,d,d,d,d,d]
        ],
        [
            // Pac-Man Ghost
            14,14,2,6,
            [w,a,s,a,s,d,s,a,s,d,s,a,s,d,s,a,s,w,w,d,d,w,w,w,d,s,s,s,s,d,s,d,w,w,a,w,d,w,a,w,d,w,a,d,w,w,w,w,a,w,a,a,a,s,w,d,d,w,d,d,s,w,w,d,s,s,s,s,s,s,s,s,s,s,s,d,w,w,w,w,w,w,d,s,a,s,s,d,s,s,s,s,s,d,w,w,w,w,w,d,s,s,s,s,w,d,d,s,d,s,w,w,w,a,a,w,d,d,w,a,a,a,w,d,d,d,w,a,a,w,d,w,a,w,d,a,w,a,s,w,w,a,s,a,w,w,a,s,s,s,w]
        ],
        [
            // Mario Goomba's shoe
            16,27,2,11,
            [a,s,s,s,s,d,a,a,s,s,s,s,s,s,s,s,d,d,d,w,w,w,a,d,d,w,s,a,s,s,s,a,a,a,s,d,s,d,s,d,d,d,d,d,d,d,d,d,d,d,d,w,d,w,w,w,w,w,a,w,a,a,a,a,a,s,w,d,d,d,w,w,w,a,a,a,a,a,a,a,a,a,s,s,a,d,w,w,d,d,d,d,d,d,d,d,d,d,w,w,w,w,a,w,d,w,d,w,w,w,a,a,a,w,w,d,d,w,d,a,w,a,s,a,w,a,s,a,s,s,s,w,w,w,a,a,a,s,a,d,w,d,d,w,d,d,w,w,w,a,a,a,a,s,a,a,s,a,s,a,s,s,s,d,d,d,s,s,d,a,s,w,a,w,w,a,a,a,s,s,s,s,d,s,d,d,d,d,d,w,d,w,s,s,d,w,s,d,d]
        ],
        [
            // Zelda Triforce
            20,20,5,10,
            [a,s,d,s,d,s,a,a,w,a,s,s,a,s,d,d,w,d,s,d,w,d,s,s,d,s,a,a,w,a,s,a,w,a,s,a,w,a,s,s,a,s,d,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,d,w,a,w,w,a,s,a,w,a,s,a,w,a,s,a,a,w,d,w,w,d,s,d,w,d,s,d,d,w,a,w,w,a,s,a,a,w,d,w,d,w,a,w,w,a,s,a,w,a,s,a,w,a,s,a,w,a,s,a,a,w,d,w,w,d,s,d,w,d,s,d,w,d,s,d,d,w,a,w,w,a,s,a,w,a,s,a,a,w,d,w,w,d,s,d,d,w,a,w,w,a,s]
        ],
        [
            // Final Fantasy Black Mage
            18,26,4,8,
            [a,a,a,w,a,w,w,d,d,d,d,d,d,w,d,d,w,d,d,w,d,w,d,d,w,d,d,d,s,s,s,a,s,s,a,s,s,s,a,d,s,d,s,d,s,d,s,s,a,a,s,s,a,d,s,s,s,s,s,a,s,s,d,s,d,s,s,a,a,a,a,w,w,d,a,s,a,w,a,a,w,w,w,a,a,a,d,d,w,w,w,a,a,d,w,d,d,w,d,d,a,a,s,a,s,s,s,s,d,s,s,s,d,d,s,s,a,a,a,a,a,a,a,a,a,a,a,w,w,d,w,w,w,w,w,d,d,s,s,d,s,d,s,s,d,d,a,a,w,w,a,w,a,w,w,w,w,s,s,a,a,a,w,w,w,d,d,a,w,w,w,d,w,d,d,w,w,d,d,d,s,a,s,s,a,a,s,a,d,d,s,d,w,d,s,w,d,w,w,w,d,s,s,s,d,w,w,w,s,d,s,s,d,w,w,s,s,d,w,d]
        ],
        [
            // Final Fantasy Fighter
            18,26,4,7,
            [a,w,s,a,s,w,a,w,w,d,w,w,a,w,d,w,d,s,w,w,s,d,d,w,s,d,d,w,d,d,d,d,d,d,d,s,d,s,a,a,d,d,d,s,d,a,s,a,d,s,s,d,a,s,a,d,s,s,a,s,a,a,w,w,w,a,a,a,s,s,w,a,w,w,a,a,s,a,a,d,s,s,w,w,a,a,s,s,s,a,d,s,s,d,s,d,d,d,d,w,d,s,w,w,d,w,d,d,d,d,s,d,s,d,s,a,s,a,w,a,a,a,a,s,s,d,d,s,d,s,d,d,w,w,s,s,a,s,a,a,a,w,s,a,a,a,a,w,w,w,d,w,d,w,a,a,a,s,a,w,a,w,a,a,a,w,s,a,d,s,d,s,d,s,w,a,w,a,a,s,s,s,d,d,a,a,a,s,s,s,d,d,d,d,w,w,d,s,w,w,d,d,a,a,s,s,d,s,d,d,d,d,d,s,d,s,s,s,s,a,s,a,a,a,a,a,w,a,d,w,w,w,w,s,d,d,d,d,d]
        ],
        [
            // Final Fantasy Chocobo
            12,16,11,0,
            [a,a,a,a,a,a,a,a,s,s,s,d,s,a,w,w,w,a,a,s,a,s,s,s,s,s,d,d,w,d,d,d,d,a,s,a,s,a,s,a,s,d,s,d,s,d,s,d,s,s,a,d,d,w,d,s,d,d,d,a,w,a,a,w,d,w,d,d,w,d,w,d,w,d,w,d,a,w,w,d,a,w,s,a,s,a,d,s,a,a,a,s,a,a,s,w,w,w,w,w,d,w,w,a,d,w,d,s,w,d,w,d]
        ],
        [
            // Pac-Man Ghost (scared)
            14,14,0,6,
            [s,s,d,s,a,s,s,s,s,w,d,w,d,w,d,s,s,d,s,d,w,w,a,d,d,w,d,s,d,s,s,d,w,w,d,s,w,w,d,s,d,s,d,s,w,w,w,w,w,w,w,a,s,s,s,w,a,a,a,s,a,w,a,a,a,s,a,w,a,a,w,a,w,w,w,w,d,w,d,w,d,d,w,d,s,s,a,a,s,d,s,a,a,w,s,a,s,s,d,w,s,s,d,d,d,d,d,d,d,d,w,a,w,d,d,w,w,a,s,a,w,w,d,a,w,a,a,w,a,s,s,d,d,s,s,a,w,a,a,s,d,s,a,s,d,s]
        ],
        [
            // Pokemon Pokeball
            14,14,5,0,
            [d,d,d,s,d,d,s,d,s,d,s,s,d,s,s,a,w,a,s,a,a,d,w,w,a,d,d,w,a,w,a,s,a,w,w,d,a,a,s,a,w,w,d,a,a,a,a,s,a,s,a,d,d,d,w,d,s,s,a,a,a,a,s,a,s,d,d,w,d,d,a,s,s,d,d,s,s,d,d,d,w,w,w,a,a,a,s,a,a,a,a,a,s,s,d,s,s,d,s,d,d,s,d,d,d,d,d,w,d,d,w,d,w,w,d,w]
        ]
    ];
    if(ME.c === undefined){
        ME.c = 9999;
        ME.t = [];
        ME.n = Math.floor(Math.random()*Math.floor(ps.length));
    }
    if(gameInfo[0] == 1 && myself[1] < grid.length-ps[ME.n][0]+ps[ME.n][2] && myself[1] > ps[ME.n][2] && myself[2] < grid.length-ps[ME.n][1]+ps[ME.n][3] && myself[2] > ps[ME.n][3]){
        ME.c = 0;
    }
    if(ps[ME.n][4][ME.c] !== undefined){
        return ps[ME.n][4][ME.c++];
    }
    else if(ME.c < 9999){
        ME.c = 9999;
        ME.n = Math.floor(Math.random()*Math.floor(ps.length));
    }
    if(ME.t.length == 0){
        var rand = [
                [parseInt(Math.random()*(grid.length-ps[ME.n][0]))+ps[ME.n][2],parseInt(Math.random()*(grid.length-ps[ME.n][1]))+ps[ME.n][3]],
                [parseInt(Math.random()*(grid.length-ps[ME.n][0]))+ps[ME.n][2],parseInt(Math.random()*(grid.length-ps[ME.n][1]))+ps[ME.n][3]],
                [parseInt(Math.random()*(grid.length-ps[ME.n][0]))+ps[ME.n][2],parseInt(Math.random()*(grid.length-ps[ME.n][1]))+ps[ME.n][3]]
            ],
            colorable = [0,0,0],
            i, j, k;
        for(i=0;i<rand.length;i++){
            for(j=rand[i][0]-ps[ME.n][2];j<rand[i][0]-ps[ME.n][2]+ps[ME.n][0];j++){
                for(k=rand[i][1]-ps[ME.n][3];k<rand[i][1]-ps[ME.n][2]+ps[ME.n][1];k++){
                    if(grid[j][k] == 0 || (grid[j][k] != myself[0] && grid[j][k]%3 == 0)){
                        colorable[i]++;
                    }
                }
            }
        }
        if(colorable[0] >= colorable[1] && colorable[0] >= colorable[2]){
            ME.t = [rand[0][0],rand[0][1]];
        }
        else if(colorable[1] >= colorable[2]){
            ME.t = [rand[1][0],rand[1][1]];
        }
        else{
            ME.t = [rand[2][0],rand[2][1]];
        }
    }
    if(ME.t[0] > myself[1]){
        return 'right';
    }
    else if(ME.t[0] < myself[1]){
        return 'left';
    }
    else if(ME.t[1] > myself[2]){
        return 'down';
    }
    else if(ME.t[1] < myself[2]){
        return 'up';
    }
    else{
        ME.t = [];
        ME.c = 0;
    }
}

```

Has a list of (pixelated) paintings it likes to paint; picks one at random along with a random canvas/placement and paints. It seems to have issues with its paint, however, as sometimes the paint cleans the canvas, and other times it doesn't seem to stick to the canvas at all. The painter bot is unfazed by this, however.

### Update 25-Aug-2018:

* New pictures
* Wants his pictures seen so tries to pick better placement for them

### Update 29-Aug-2018:

* New Pictures + Updates to old ones

# Answer
> 9 votes
## P

```
function (myself, grid, bots, gameInfo) {

"use strict";

if (this.O == null) this.O = {};
const O = this.O;

// console.log(this);

const MAXBOTS = 60;
const MAXSZ = 3 * MAXBOTS;
const MAXID = MAXBOTS + 1;

if (gameInfo[0] == 1) {
    if (bots.length > MAXBOTS) {
        alert("ASSERTION FAILED: MAXBOTS EXCEEDED (contact @tomsmeding)");
        return 0;
    }

    for (const b of bots) {
        if (b[0] < 0 || b[0] > MAXID) {
            alert("ASSERTION FAILED: MAXID EXCEEDED (contact @tomsmeding)");
            return 0;
        }
    }
}

function from_base64(bs) {
    if (bs.length % 4 != 0) throw new Error("Invalid Base64 string");

    const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    const beta = new Array(256).fill(-1);

    for (let i = 0; i < alpha.length; i++) beta[alpha.charCodeAt(i)] = i;

    const arrbuf = new ArrayBuffer(bs.length / 4 * 3 | 0);
    const buf = new Uint8Array(arrbuf);

    let j = 0;
    for (let i = 0; i < bs.length; i += 4) {
        buf[j++] = (beta[bs.charCodeAt(i+0)] << 2) | (beta[bs.charCodeAt(i+1)] >> 4);
        if (bs[i+2] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+1)] << 4) | (beta[bs.charCodeAt(i+2)] >> 2);
        if (bs[i+3] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+2)] << 6) | (beta[bs.charCodeAt(i+3)] >> 0);
    }

    return new Uint8Array(arrbuf, 0, j);
}

function repeat(str, times) {
    return new Array(times + 1).join(str);
}

function println_func(ptr) {
    let s = "";
    for (; ptr < O.wa_membuf.length; ptr++) {
        if (O.wa_membuf[ptr] == 0) break;
        s += String.fromCharCode(O.wa_membuf[ptr]);
    }
    console.log(s);
}

function print_int_func(value) {
    console.log(value);
}

function seed_random() {
    for (let i = 0; i < O.wa_rand_state.length; i++) {
        O.wa_rand_state[i] = (Math.random() * 256) & 0xff;
    }
}

function transfer_myself(myself) {
    O.wa_my_id[0] = myself[0];
}

function transfer_grid(grid) {
    const W = grid.length, H = grid[0].length;
    O.wa_width[0] = W;
    O.wa_height[0] = H;
    for (let x = 0; x < W; x++) {
        for (let y = 0; y < H; y++) {
            O.wa_grid[W * y + x] = grid[x][y];
        }
    }
}

function transfer_bots(bots) {
    O.wa_nbots[0] = bots.length;
    for (let i = 0; i < bots.length; i++) {
        O.wa_bots[3 * i + 0] = bots[i][0];
        O.wa_bots[3 * i + 1] = bots[i][1];
        O.wa_bots[3 * i + 2] = bots[i][2];
    }
}

function transfer_gameInfo(gameInfo) {
    O.wa_round_idx[0] = gameInfo[0];
    O.wa_total_rounds[0] = gameInfo[1];
}

function stringify(thing) {
    if (Array.isArray(thing)) {
        return "[" + thing.map(stringify).join(",") + "]";
    } else if (thing instanceof Int8Array) {
        return "[" + thing.toString() + "]";
    } else {
        return thing.toString();
    }
}

function mc_calcmove() {
    // console.log("mc_calcmove(" + stringify(myself) + "," + stringify(grid) + "," + stringify(bots) + "," + stringify(gameInfo) + ")");
    transfer_myself(myself);
    transfer_grid(grid);
    transfer_bots(bots);
    transfer_gameInfo(gameInfo);
    return ["right", "down", "left", "up", "wait"][O.wa_mc_calcmove()];
    // return O.wa_mc_calcmove();
}

if (O.wasm_bytes == null) {
    O.wasm_bytes = from_base64(
// INSERT-WASM-HERE
"[encoded data, 35236 characters]"
    );

    // require("fs").writeFileSync("reverse-base64-output.txt", Buffer.from(O.wasm_bytes));

    O.memory = new WebAssembly.Memory({initial: 15});
    // O.importObject = {js: {mem: O.memory}, env: {println: println_func}};
    O.importObject = {
        env: {
            println: println_func,
            print_int: print_int_func,
            __linear_memory: O.memory,
            __indirect_function_table: new WebAssembly.Table({initial: 0, element: "anyfunc"}),
        },
    };

    // let wa_membuf, wa_width, wa_height, wa_nbots, wa_round_idx, wa_total_rounds, wa_my_id, wa_grid, wa_bots, wa_rand_state;

    /*const promise = fetch('../out/main.wasm').then(response =>
        response.arrayBuffer()
    ).then(bytes =>
        WebAssembly.instantiate(bytes, O.importObject)
    );*/
    // const promise = WebAssembly.instantiate(fs.readFileSync("hotpatcher/out.wasm"), O.importObject);
    const promise = WebAssembly.instantiate(O.wasm_bytes, O.importObject);

    promise.then(results => {
        const instance = results.instance;

        // console.log(instance.exports);

        // First set some pointers
        instance.exports.entry_fn(0);

        O.wa_membuf = new Uint8Array(O.memory.buffer);
        const ptrs = new Uint32Array(O.memory.buffer, 0, 9 * 4);

        O.wa_width = new Int32Array(O.memory.buffer, ptrs[0], 1);
        O.wa_height = new Int32Array(O.memory.buffer, ptrs[1], 1);
        O.wa_nbots = new Int32Array(O.memory.buffer, ptrs[2], 1);
        O.wa_round_idx = new Int32Array(O.memory.buffer, ptrs[3], 1);
        O.wa_total_rounds = new Int32Array(O.memory.buffer, ptrs[4], 1);
        O.wa_my_id = new Int32Array(O.memory.buffer, ptrs[5], 1);
        O.wa_grid = new Uint8Array(O.memory.buffer, ptrs[6], MAXSZ * MAXSZ);
        O.wa_bots = new Uint8Array(O.memory.buffer, ptrs[7], MAXBOTS * 3);
        O.wa_rand_state = new Uint8Array(O.memory.buffer, ptrs[8], 5 * 4);

        O.wa_mc_calcmove = function() { return instance.exports.entry_fn(2); }

        seed_random();

        // Signal that we're done setting up, and the wasm code can set itself up
        instance.exports.entry_fn(1);

        O.instantiated = true;
        console.log("MC: Instantiated!");
    }).catch(console.error);
}

if (gameInfo[0] > 5) {
    if (O.instantiated) {
        // const start = new Date();
        const output = mc_calcmove();
        // const end = new Date();

        // if (O.time_sum == null) O.time_sum = 0;
        // O.time_sum += end - start;

        // if (gameInfo[0] % 50 == 0) {
        //     console.log("Average time taken: " + O.time_sum / (gameInfo[0] - 1));
        // }

        return output;
    } else {
        throw new Error("SCREAM FIRE wasm instantiation");
    }
} else {
    console.log("MC: RANDOM MOVE BEFORE INSTANTIATE");
    return ["right", "down", "left", "up"][Math.random() * 4 | 0];
}

}

```

Meet P. P isn't really trying to be a good bot, or trying to win the game, it's just trying to paint squares. It's content doing that. P gets nervous when bots are on his tail, but is otherwise just going where the paintable squares are.

## Explanations

My bots P and MC are basically compiled from the same code. A description of how it all works can be found below.

The bots consist of the Javascript wrapper code shown above and the C code ~~shown~~ linked to below. (Including the code made the post too long for SE's ideas of good post length.) When preparing a bot for submission (or for testing), the C code is compiled via an elaborate process, described later, to a `wasm` file, which is encoded in base64 and inserted into the Javascript file. The Javascript file must instantiate a `WebAssembly.Instance` of the wasm code to be able to use it, but the API for that is asynchronous; therefore, it starts to instantiate it the first time the bot is invoked in a game, and does random moves until the asynchronous instantiation returns, and the wasm code is ready to use. This instantiation usually takes about two turns, which is little enough to not be significant.

Once the bot is instantiated, the one exported function of the wasm code is called (`entry_fn`) with mode argument 0, which instructs it to populate a `ptrs` table with pointers to various global variables that contain per-round information like the grid data, the bots array, the number of bots, etc. The table also contains a pointer to the state array used for the internal PRNG — WebAssembly is, as far as I know, only natively capable of performing deterministic actions, and has no built-in random source. Therefore, after the `ptrs` array is filled, the Javascript code generates some random numbers with `Math.random()` and fills this `rand_state` array with random data. When that is done, `entry_fn` is called with mode argument 1, indicating that the JS code is done setting up and that the wasm code can start initialising its data.

Once the wasm code has finished setting up and the bot is called again for a move, `entry_fn` is called with its last mode argument, 2, which calculates a move. Before doing that, the JS code first copies all necessary turn information (grid, bots, myself, etc.) to the global variables for which pointers were put in the `ptrs` array. `entry_fn(2)` then returns the move chosen, as an index in the array `["right", "down", "left", "up", "wait"]`.

The C code can be found in this gist, and an explanation of it can be found belowwwww.

## The C code

The reason for having only one exported function, and not exporting the global variables by name, and possibly other unidiomatic things, is because building this crap is difficult, and the tooling doesn't work together and has incompatibilities with other tooling and browsers. My own compilation pipeline is described later. (Note that the wasm ABI is 32-bit, so returning a pointer as an `int` is actually safe and fine (`sizeof(void*) == 32`).)

I use a bit of macro magic to select which bot this is to actually compile to. Both MC's and P's source are in the C source, and which is to be used is determined by the `USED_CALCMOVE_PREFIX` define. If it's `p_`, P's source is used, and if it's `mc_`, MC's source is used. At some point, I had plans of making them work together, after all being able to execute each other's logic, but I didn't.

Depending on the value of that define, when calculating a move, control is transferred to either `mc_calcmove()` or `p_calcmove()`.

### MC

MC is a Monte Carlo player, which means from the current position it tries all possible moves (5 or less in this game), and from the resulting board position it simulates a number of random games. The move with the best average result after the random playouts is chosen. In this case, I do 100 random playouts, each of only 5 turns. Usually in this kind of game AI's, a game is run till the end in each playout, but since this game is *completely* unpredictable at times, that didn't seem useful. And in any case, it would've been too slow.

For optimisation purposes, the entire board is not reset after each playout, but only the cells that were actually changed in the playout. This is done with the `modified` array. Also, the score delta is kept in `mc_random_step`, so that after each playout the entire board's score doesn't need to be calculated. Both reduce the time required to run this algorithm, and do not influence its decisions or calculated scores in any way.

For the most part, the C code is completely parallel to the JS code of a previous version of the MC submission here, which may be looked up if wanted. The only change is the use of the `last_dir` array to prevent walking backwards during a random playout.

### P

P is a more complex bot than MC. I'll not describe all the specific workings — you have the source now after all — but I'll give a general overview of what it's doing.

The main functionality is not far from DFSbot, I think. It's a depth-first search of the possible paths it can take for the coming 8 (= `P_WALK_DISTANCE`) moves, scoring each path as the sum of the scores given to each cell used in the path. Besides the per-cell scoring, each path starting move (i.e. move to be taken right now) is additionally scored using an "evil factor", which helps the bot evade other bots that are on its tail. (Briefly: if a bot is intruding on P's ground for at least 20 turns, the bot's position and the two in front of it in the direction it's travelling repel P; see `p_evil_factor()`. `bot_evil_score` records the number of moves intruding, falling quickly back to zero if the bot isn't intruding anymore.)

The base cell score is given by `p_paintScore()`, which doesn't like running over itself, painting stuff that it can't paint directly, running into another bot directly (I don't model the bots-on-same-cell-give-white for performance!), or walking where it's already been in the game. Added to that score is the score given by the `heat_map`, which is calculated by `p_populate_heatmap()`. White squares are good, P's own squares are bad, and other's squares that P can paint into its own colour are moderately good, otherwise they are moderately bad.

There is a factor of 0.98 that is multiplied with the tail end of the path at each point, making the starting part of the possible path more important than the tail end. The factor is purposefully chosen very close to 1, so that the total impact will not be very large, but will be large enough indeed to provide a tie-breaker function. (If we have two paths that are equivalent but with the only difference that one has a white square at the end and the other has a white square at the beginning, the second path is clearly better, and this weighting ensures that.)

There are a number of arrays, some of which keep their values over turns. Management of this data costs some code, mostly in `p_calcmove()`. The code isn't very beautiful, but it seems to work, so ¯\\\_(ツ)\_/¯

## Compilation process

In my working directory, I have a folder `compiler` that contains:

* A build of LLVM in `llvm`, compiled with `-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly`. The version is the master tip at the time of cloning, which is commit `3d765ce4b7f2fd25bdbc0efc26afdf42e84fecb2`. Compiling this with 3 threads took about an hour on my machine. Note that the resuilting `build` folder is about 29GB, so be wary of doing this yourself if you're short on disk space. .\_.
* A build of binaryen in `binaryen`, at commit `57328f8e1e4db509b9956b53dd5300fc49e424eb`.
* A build of WABT in `wabt`, at commit `71647d4f0e86a4c738f742f69f65b2cc41d4c004`.

The JS wrapper source is in `main.js` and the C source is in `main.c`. When I want to test what I've written, I save the file (most probably `main.c`), run `./CFIT.sh`, run `pbcopy <main.js` (which copies `main.js` to my clipboard), and paste it in the box in @dzaima's controller. Million thanks @dzaima for providing a controller that is actually that full-featured to let me easily do this.

The script `CFIT.sh` drives the compilation process, and looks as follows:

```
#!/usr/bin/env bash
# Compile, Fix, Insert, Test

set -e

./compile.sh
hotpatcher/hotpatcher main.wasm out.wasm
compiler/binaryen/build/bin/wasm-opt -O4 out.wasm -o out.wasm
./insert_wasm.sh out.wasm
./run_test.js

```

The hotpatcher is a whole 'nother story, and mentioned later. The script `compile.sh` looks as follows:

```
#!/usr/bin/env bash
compiler/llvm/build/bin/clang -Wall -Wextra main.c -c --target=wasm32-unknown-unknown -O3 -fno-builtin-memcpy -fno-builtin-memset -g -o main.wasm
compiler/wabt/build/wasm2wat main.wasm -o main.wat

```

The flags `-fno-builtin-{memset,memcpy}` are necessary because otherwise LLVM dutifully recognises the bodies of my `memset_x` and `memcpy_x` functions as the built-in `memset` and `memcpy` functions, which it then replaces the code with, leaving me with undefined references to these functions *despite* providing replacements.

The `insert_wasm.sh` script looks as follows:

```
#!/usr/bin/env bash
set -e
set -o pipefail

if [[ -n "$1" ]]; then wasmfile="$1"; else wasmfile="main.wasm"; fi
subject="main.js"
tmpfile=".insert_wasm_tmpfile.txt"

[[ -f "$subject" ]] || exit 1
[[ -f "$wasmfile" ]] || exit 1

echo "Inserting into $subject from wasm file $wasmfile"

./tostring.sh "$wasmfile" >"$tmpfile"

result="$( \
    cat "$subject" \
    | sed -n ':b; s/INSERT-WASM-HERE/INSERT-WASM-HERE/; p; t i; n; b b; :i; r '"$tmpfile"$'\n''; n' \
)"

cat >"$subject" <<<"$result"

rm "$tmpfile"

```

This replaces the huge base64 string in main.js directly with the compiled wasm code. The `tostring.sh` script looks as follows again:

```
#!/usr/bin/env bash
if [[ $# -ge 1 ]]; then
    exec <"$1"
fi

# $HOME/code/bin2c/bin2c | sed 's/^"//; s/"$//; s/\\a/\\7/g' | tr -d '\n'
base64 | tr -d '\n' | python3 -c 'import sys, re; text = sys.stdin.read(); print("\"" + re.sub(r"(.)\1{29,}", lambda m: "\"+repeat(\"" + m.group(0)[0] + "\"," + str(len(m.group(0))) + ")+\"", text, flags=re.I) + "\"")'

```

The `run_test.js` script just includes the `main.js` (including newly-inserted wasm code) with an `eval` and calls it twice with some parameters taken from a game that produced problems once, making sure to wait some time between the two calls to let the wasm code be instantiated. This provides the most basic of integration tests; if this doesn't work, something is wrong, and usually when something is actually wrong it is so wrong that this test fails, crashes, whatever.

### Hotpatcher

That leaves the hotpatcher. This is where this whole thing went overboard, honestly; clang generates fine wasm code, but it fails badly when doing *anything* but generating plain instructions. It doesn't emit exports for exported functions or exported variables, and it for some reason emits an exported *mutable global* used as a store for the stack pointer. And even worse, it actually uses that global. (Fortunately, it doesn't *really* use it: it only reads it at function entry and restores it a function exit, which is easily fixed by replacing the read with a constant and the writes with a `drop`.) Now mutable globals are not supported in any wasm implementation I've seen, so why clang thinks doing this is a good idea is beyond me. I haven't succeeded in making clang do any of this different, and being a lazy programmer I didn't like making all these fixes by hand, so I wrote a program to do it for me.

That program became a 1385 line C++ program that parses, modifies and re-serialises a wasm file. The source for that program can be found in this gist.

I'm not even going to explain what it all does; only that the actual bloody *patches* that it does are in `hotpatcher.cpp`; all the rest is only to make that function possible.

## Conclusion

Does this whole ton of crap need a conclusion? ¯\\\_(ツ)\_/¯ but I guess I'm obliged to say that this might have been done more easily using emscripten. The reason I didn't use it is because when you compile a C file using emcc, by default it generates a complete monstrosity of a JS file and an HTML file along with it, and the stuff was so insane I just dropped emcc in favour of doing everything myself. Later I read somewhere that emscripten is supposed to have some "shared library" mode (shared libraries are a lower-level thing, JS don't go stealing names that aren't yours) that doesn't generate all that bloatcode, but then I was already too far into all the fun stuff of bulding my hotpather (gambler's fallacy anyone?) to try that out.

I hope this is actually useful to someone and that I haven't run over SE's post size limit in writing this up.

# Answer
> 8 votes
## Random Filler

```
function([id, x, y], grid, bots, gameInfo) {
    let painted = {
        false: {
            un: [],
            other: [],
            me: [],
        },
        true: {
            un: [],
            other: [],
            me: [],
        },
    };
    let moves = {
        left: {x: x - 1, y},
        up: {x, y: y - 1},
        right: {x: x + 1, y},
        down: {x, y: y + 1},
        wait: {x, y},
    };
    let isbot = m => bots.some(([, x, y]) => m.x == x && m.y == y);
    let whose = n => n ? n == id || Math.abs(id - n) % 3 > 1 ? "me" : "other" : "un";
    for (let dir in moves) {
        let move = moves[dir];
        if (move.x >= 0 && move.x < grid.length && move.y >= 0 && move.y < grid.length)
            painted[isbot(move)][whose(grid[move.x][move.y])].push(dir);
    }
    choices = [painted.false.un, painted.false.other, painted.true.un, painted.true.other, painted.false.me, painted.true.me].find(choices => choices.length);
    let move = choices[Math.random() * choices.length | 0];
    return move;
}

```

Randomly walks with a preference for moving to unpainted squares, then squares it can repaint (twice if necessary), then any square. Edit: Updated to prefer squares that don't already contain a bot (including itself), except that squares with a bot are currently preferred to squares it can't paint.

# Answer
> 6 votes
# Borderline

```
function(myself, grid, bots, gameInfo) {
    // Check if already on border
    if (myself[1] == 0 || myself[1] == grid.length-1 || myself[2] == 0 || myself[2] == grid.length-1) {
        // Move anticlockwise around the border
        if (myself[1] == 0 && myself[2] != 0 && myself[2] != grid.length-1) {
            return "down";
        }
        if (myself[1] == 0 && myself[2] == 0) {
            return "down";
        }

        if (myself[2] == grid.length-1 && myself[1] != 0 && myself[1] != grid.length-1) {
            return "right";
        }
        if (myself[1] == 0 && myself[2] == grid.length-1) {
            return "right";
        }

        if (myself[1] == grid.length-1 && myself[2] != 0 && myself[2] != grid.length-1) {
            return "up";
        }
        if (myself[1] == grid.length-1 && myself[2] == grid.length-1) {
            return "up";
        }

        if (myself[2] == 0 && myself[1] != 0 && myself[1] != grid.length-1) {
            return "left";
        }
        if (myself[1] == grid.length-1 && myself[2] == 0) {
            return "left";
        }
    } else {
        // Find the nearest border and move to it
        if (myself[1] <= grid.length-1 - myself[1]) {
            // Move to left border
            return "left";
        } else {
            // Move to right border
            return "right";
        }
    }
}

```

Not too interesting, just moves around the edge of the grid.

# Answer
> 6 votes
## M.A.D.S.

```
function(myself, grid, bots, gameInfo)
{
    const w = 6, h = 6;
    let my_c = myself[0], my_x = myself[1], my_y = myself[2], size = grid.length, roundnum = gameInfo[0];

    if (!localStorage.steelyeyedmissileman) {
        var offset_x = Math.random() *(size-w-1) |0;
        var offset_y = Math.random() *(size-h-1) |0;
        localStorage.steelyeyedmissileman = JSON.stringify([offset_x, offset_y]);
    }
    offsets = JSON.parse(localStorage.steelyeyedmissileman);
    offset_x = offsets[0];
    offset_y = offsets[1];

    let targets = [];
    for(let grid_x = offset_x; grid_x < offset_x+6; grid_x++)
    {
        for(let grid_y = offset_y; grid_y < offset_y+6; grid_y++)
        {
            if(grid[grid_x][grid_y] != my_c)
            {
                targets.push([grid_x, grid_y]);
            }
        }
    }
    let target = targets.pop();
    if(target == undefined) return 'wait';
    if(target[0] > my_x) return 'right';
    if(target[0] < my_x) return 'left';
    if(target[1] > my_y) return 'down';
    if(target[1] < my_y) return 'up';
    return "left";
}

```

Picks a 6x6 spot on the board and defends it.

# Answer
> 6 votes
# Euclid

```
function euclidFn(myself, grid, bots, gameInfo) {
    const W = grid.length, H = grid[0].length;
    const meIdx = bots.findIndex(b => b[0] == myself[0]);
    const meClr = bots[meIdx][0];

    const botIdToIndex = {};
    for (let i = 0; i < bots.length; i++) {
        botIdToIndex[bots[i][0]] = i;
    }

    function paintValue(floor, clr) {
        if (floor == 0) return clr;
        else return [clr, 0, floor][Math.abs(clr - floor) % 3];
    }

    function paint(gr, x, y, clr) {
        gr[x][y] = paintValue(gr[x][y], clr);
    }

    function distance(x1, y1, x2, y2) {
        return Math.abs(y2 - y1) + Math.abs(x2 - x1);
    }

    function calcHeatmap() {
        const heat = new Array(W).fill(0).map(() => new Array(H).fill(0));

        function weight(dx, dy) {
            const d = dx + dy;
            return d < 3 ? 1 / (1 + d) : 0;
        }

        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                let s=0;
                for (let x2 = Math.max(x-3, 0); x2 <= Math.min(W-1, x+3); x2++) {
                    for (let y2 = Math.max(y-3, 0); y2 <= Math.min(H-1, y+3); y2++) {
                        if (grid[x2][y2] == meClr) {
                            s += weight(Math.abs(x2 - x), Math.abs(y2 - y));
                        }
                    }
                }
                heat[x][y] = s;
            }
        }

        return heat;
    }

    const heatmap = calcHeatmap();

    function scorePos(px, py) {
        let sc = 0;
        if (grid[px][py] != meClr && paintValue(grid[px][py], meClr) == meClr) {
            sc += 100;
        }

        let mindist = W + H + 1;
        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                if (grid[x][y] != meClr && paintValue(grid[x][y], meClr) == meClr) {
                    let d = distance(px, py, x, y);
                    if (d < mindist) mindist = d;
                }
            }
        }
        sc -= 3 * mindist;

        mindist = W + H + 1;
        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                if (grid[x][y] == largestBotId) {
                    let d = distance(px, py, x, y);
                    if (d < mindist) mindist = d;
                }
            }
        }
        sc -= 6 * mindist;

        sc -= 3 * heatmap[px][py];

        sc += Math.random();
        return sc;
    }

    function calcBotScores() {
        const res = new Array(bots.length).fill(0).map((_,i) => [bots[i][0], 0]);

        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                if (grid[x][y] > 0) {
                    let i = botIdToIndex[grid[x][y]];
                    if (i != undefined) res[i][1]++;
                }
            }
        }

        return res;
    }

    const botScores = calcBotScores();  // [id, size]
    const largestBotId = botScores
        .filter(p => p[0] != meClr && paintValue(p[0], meClr) == meClr)
        .sort((a,b) => b[1] - a[1])
        [0][0];

    const dxes = [1, 0, -1, 0, 0], dyes = [0, 1, 0, -1, 0];
    const outputs = ["right", "down", "left", "up", "wait"];

    let allscores = [];
    let maxscore = -Infinity, maxat = -1;

    let allowWait = grid[bots[meIdx][1]][bots[meIdx][2]] == 0;

    for (let i = 0; i < 4 + allowWait; i++) {
        const nx = bots[meIdx][1] + dxes[i];
        const ny = bots[meIdx][2] + dyes[i];
        if (nx < 0 || nx >= W || ny < 0 || ny >= H) {
            allscores.push(null);
            continue;
        }

        let score = scorePos(nx, ny);
        if (i == 4) score -= 20;
        if (euclidFn.lastMove != undefined && i != euclidFn.lastMove) score -= 3;

        allscores.push(~~(score * 1000) / 1000);

        if (score > maxscore) {
            maxscore = score;
            maxat = i;
        }
    }

    // console.log([maxscore, maxat], allscores);

    let move = maxscore == -1 ? Math.random() * 5 | 0 : maxat;
    euclidFn.lastMove = move;

    return outputs[move];
}

```

This does all kinds of arbitrary things. The name is badly chosen, but it does a number of things with distances, so I guess it makes sense somewhere.

I chooses the move that maximises `scorePos`, which really likes painting a square to its colour when it wasn't before, and otherwise doesn't like being far from colourable squares or from the largest colourable bot (not sure whether this is actually working well). It also doesn't like being near itself, because otherwise it goes overpainting itself often for some reason.

*EDIT* This gave errors before, I hope it's correct now...

# Answer
> 5 votes
## Hunter-Killer

```
function(myself, grid, bots, gameInfo) {
    targetColour = myself[0] % 3;
    // If I can paint someone else's space to my colour, do so.
    var options = [];
    if (myself[1] !== 0 && grid[myself[1] - 1][myself[2]] % 3 === targetColour && grid[myself[1] - 1][myself[2]] !== myself[0] && grid[myself[1] - 1][myself[2]] !== 0)
        options.push("left");
    if (myself[1] !== grid.length - 1 && grid[myself[1] + 1][myself[2]] % 3 === targetColour && grid[myself[1] + 1][myself[2]] !== myself[0] && grid[myself[1] + 1][myself[2]] !== 0)
        options.push("right");
    if (myself[2] !== 0 && grid[myself[1]][myself[2] - 1] % 3 === targetColour && grid[myself[1]][myself[2] - 1] !== myself[0] && grid[myself[1]][myself[2] - 1] !== 0)
        options.push("up");
    if (myself[2] !== grid.length - 1 && grid[myself[1]][myself[2] + 1] % 3 === targetColour && grid[myself[1]][myself[2] + 1] !== myself[0] && grid[myself[1]][myself[2] + 1] !== 0)
        options.push("down");
    if (options.length > 0) return options[Math.random() * options.length | 0];

    // Otherwise, move to the closest bot I can paint over.
    var targetBots = bots.filter(bot => {
        if (bot[0] === myself[0] || bot[0] % 3 !== targetColour) return false;
        return true;
    });
    if (targetBots.length > 0) {
        targetBots.sort((a, b) => (Math.abs(a[1] - myself[1]) + Math.abs(a[2] - myself[2])) < (Math.abs(a[1] - myself[1]) + Math.abs(a[2] - myself[2])));
        if (Math.abs(targetBots[0][1] - myself[1]) > Math.abs(targetBots[0][2] - myself[2])){
            return targetBots[0][1] - myself[1] > 0 ? "right" : "left";
        }
        return targetBots[0][2] - myself[2] > 0 ? "down" : "up";
    }

    options = [];
    // If we've killed them all, try to move to a blank space.
    if (myself[1] !== 0 && grid[myself[1] - 1][myself[2]] === 0 && grid[myself[1] - 1][myself[2]] !== myself[0])
        options.push("left");
    if (myself[1] !== grid.length - 1 && grid[myself[1] + 1][myself[2]] === 0 && grid[myself[1] + 1][myself[2]] !== myself[0])
        options.push("right");
    if (myself[2] !== 0 && grid[myself[1]][myself[2] - 1] === 0 && grid[myself[1]][myself[2] - 1] !== myself[0])
        options.push("up");
    if (myself[2] !== grid.length - 1 && grid[myself[1]][myself[2] + 1] === 0 && grid[myself[1]][myself[2] + 1] !== myself[0])
        options.push("down");
    if (options.length > 0) return options[Math.random() * options.length | 0];

    options = [];
    // If there aren't any, try to move to a space I can paint white.
    targetColour = (targetColour + 2) % 3
    if (myself[1] !== 0 && grid[myself[1] - 1][myself[2]] % 3 === 0 && grid[myself[1] - 1][myself[2]] !== myself[0])
        options.push("left");
    if (myself[1] !== grid.length - 1 && grid[myself[1] + 1][myself[2]] % 3 === 0 && grid[myself[1] + 1][myself[2]] !== myself[0])
        options.push("right");
    if (myself[2] !== 0 && grid[myself[1]][myself[2] - 1] % 3 === 0 && grid[myself[1]][myself[2] - 1] !== myself[0])
        options.push("up");
    if (myself[2] !== grid.length - 1 && grid[myself[1]][myself[2] + 1] % 3 === 0 && grid[myself[1]][myself[2] + 1] !== myself[0])
        options.push("down");
    if (options.length > 0) return options[Math.random() * options.length | 0];

    // Otherwise, pick one at random.
    return ["up","down","left","right"][Math.random() * 4 | 0];
}

```

Hunter-Killer targets the closest bot that it can paint over and tries to paint over all of its spaces, eliminating it. If it gets all of them, it reverts to a random-painting algorithm where it tries to move first to white spaces and second to spaces it can make white.

It seems to do well if it can latch on to a bot that has a good strategy and follow it for a while, but only moderate if it kills all of its targets quickly (or its targets are weak bots).

Doesn't work well with the current version of the controller, as bots aren't removed when eliminated. If that's not changed, I'll rewrite it to ignore bots which haven't moved in a few turns (potentially allowing a turtleing strategy which would allow a bot to survive, though likely not prosper).

All that's required to fix this is change the first loop in runBots to

```
for (var j = 0; j < botData.length; j++) {
    if (!botData[j].eliminated)
        bots_array.push([botData[j].uid, botData[j].x, botData[j].y]);
}

```

# Answer
> 5 votes
## GiveMeSpace

```
function(myself, grid, bots, gameInfo){
    if(!localStorage.givemespace){
        localStorage.givemespace = JSON.stringify({
            recent:[],
            timeout:-1,
            corner:[9999,-1,-1],
            following:[]
        });
    }
    var firstchoice = {up:-1,down:-1,left:-1,right:-1}, nearestblank = [9999,-1,-1], store = JSON.parse(localStorage.givemespace), unique = [], numunique = 0,
        currdist, i, j;
    if(store.timeout >= 0 && store.corner[1] >= 0 && store.corner[2] >= 0){
        store.timeout--;
        persiststorage(store);
        if(store.corner[2] < myself[2]){
            return 'up';
        }
        if(store.corner[2] > myself[2]){
            return 'down';
        }
        if(store.corner[1] < myself[1]){
            return 'left';
        }
        if(store.corner[1] > myself[1]){
            return 'right';
        }
    }
    if(store.recent.length == 20){
        for(i=0;i<store.recent.length;i++){
            if(unique.indexOf(store.recent[i][1]+"_"+store.recent[i][2]) == -1){
                unique.push(store.recent[i][1]+"_"+store.recent[i][2]);
                numunique++;
            }
        }
        if(numunique <= 6){
            store.recent = [];
            store.timeout = 10+numunique;
            store.corner = [[-1,0,0],[-1,0,grid.length-1],[-1,grid.length-1,0],[-1,grid.length-1,grid.length-1]][Math.random()*4|0];
            persiststorage(store);
        }
    }
    function dist(a,b){
        return Math.abs(a[1]-b[1])+Math.abs(a[2]-b[2]);
    }
    function finalcolor(a,b){
        return Math.abs(a-b)%3;
    }
    function persiststorage(store){
        if(store.recent.length > 20) store.recent = store.recent.slice(1);
        localStorage.givemespace = JSON.stringify(store);
    }
    store.recent.push(myself);
    persiststorage(store);
    if(myself[2] > 0 && myself[0] != grid[myself[1]][myself[2]-1] && (grid[myself[1]][myself[2]-1] == 0 || finalcolor(myself[0],grid[myself[1]][myself[2]-1]) == 0)){
        firstchoice.up = 9999;
    }
    if(myself[2] < grid.length-1 && myself[0] != grid[myself[1]][myself[2]+1] && (grid[myself[1]][myself[2]+1] == 0 || finalcolor(myself[0],grid[myself[1]][myself[2]+1]) == 0)){
        firstchoice.down = 9999;
    }
    if(myself[1] > 0 && myself[0] != grid[myself[1]-1][myself[2]] && (grid[myself[1]-1][myself[2]] == 0 || finalcolor(myself[0],grid[myself[1]-1][myself[2]]) == 0)){
        firstchoice.left = 9999;
    }
    if(myself[1] < grid.length-1 && myself[0] != grid[myself[1]+1][myself[2]] && (grid[myself[1]+1][myself[2]] == 0 || finalcolor(myself[0],grid[myself[1]+1][myself[2]]) == 0)){
        firstchoice.right = 9999;
    }
    if(firstchoice.up > 0 || firstchoice.down > 0 || firstchoice.left > 0 || firstchoice.right > 0){
        for(i=0;i<bots.length;i++){
            if(bots[i][0] != myself[0]){
                if(firstchoice.up > 0){
                    currdist = dist(bots[i],[0,myself[1],myself[2]-1]);
                    if(currdist < firstchoice.up){
                        firstchoice.up = currdist;
                    }
                }
                if(firstchoice.down > 0){
                    currdist = dist(bots[i],[0,myself[1],myself[2]+1]);
                    if(currdist < firstchoice.down){
                        firstchoice.down = currdist;
                    }
                }
                if(firstchoice.left > 0){
                    currdist = dist(bots[i],[0,myself[1]-1,myself[2]]);
                    if(currdist < firstchoice.left){
                        firstchoice.left = currdist;
                    }
                }
                if(firstchoice.right > 0){
                    currdist = dist(bots[i],[0,myself[1]+1,myself[2]]);
                    if(currdist < firstchoice.right){
                        firstchoice.right = currdist;
                    }
                }
            }
        }
        if(firstchoice.up >= firstchoice.down && firstchoice.up >= firstchoice.left && firstchoice.up >= firstchoice.right){
            return 'up';
        }
        else if(firstchoice.down >= firstchoice.left && firstchoice.down >= firstchoice.right){
            return 'down';
        }
        else if(firstchoice.left >= firstchoice.right){
            return 'left';
        }
        else{
            return 'right';
        }
    }
    for(i=0;i<grid.length;i++){
        for(j=0;j<grid.length;j++){
            if((i != myself[1] || j != myself[2]) && grid[i][j] != myself[0] && (grid[i][j] == 0 || finalcolor(myself[0],grid[i][j]) == 0)){
                currdist = dist(myself,[0,i,j]);
                if(currdist < nearestblank[0]){
                    nearestblank[0] = currdist;
                    nearestblank[1] = i;
                    nearestblank[2] = j;
                }
            }
        }
    }
    if(nearestblank[0] < 9999){
        if(nearestblank[2] < myself[2]){
            return 'up';
        }
        if(nearestblank[2] > myself[2]){
            return 'down';
        }
        if(nearestblank[1] < myself[1]){
            return 'left';
        }
        if(nearestblank[1] > myself[1]){
            return 'right';
        }
    }
    return ['up','down','left','right'][Math.random()*4|0];
}

```

Checks for any paintable (can paint into its own color) spaces next to it and chooses the one furthest from any other bots. If there are no paintable spaces adjacent to the bot, it finds the closest paintable space and heads towards it.

Currently avoids infinite loops.

Todo: Avoid hunters.

# Answer
> 5 votes
# TRAVELER

```
function TRAVELER([myColor, myX, myY], grid, bots, [frame, maxFrames]) {
    class BinaryHeapStrategy {
        constructor(options) {
            this.comparator = options.comparator;
            this.data = [];
            this.heapify();
        }
        heapify() {
            if (this.data.length > 0) {
                for (let i = 0; i < this.data.length; i++) {
                    this.bubbleUp(i);
                }
            }
        }
        queue(value) {
            this.data.push(value);
            this.bubbleUp(this.data.length - 1);
        }
        dequeue() {
            const ret = this.data[0];
            const last = this.data.pop();
            if (this.data.length > 0 && last !== undefined) {
                this.data[0] = last;
                this.bubbleDown(0);
            }
            return ret;
        }
        peek() {
            return this.data[0];
        }
        clear() {
            this.data.length = 0;
        }
        bubbleUp(pos) {
            while (pos > 0) {
                const parent = (pos - 1) >>> 1;
                if (this.comparator(this.data[pos], this.data[parent]) < 0) {
                    const x = this.data[parent];
                    this.data[parent] = this.data[pos];
                    this.data[pos] = x;
                    pos = parent;
                }
                else {
                    break;
                }
            }
        }
        bubbleDown(pos) {
            let last = this.data.length - 1;
            while (true) {
                const left = (pos << 1) + 1;
                const right = left + 1;
                let minIndex = pos;
                if (left <= last && this.comparator(this.data[left], this.data[minIndex]) < 0) {
                    minIndex = left;
                }
                if (right <= last && this.comparator(this.data[right], this.data[minIndex]) < 0) {
                    minIndex = right;
                }
                if (minIndex !== pos) {
                    const x = this.data[minIndex];
                    this.data[minIndex] = this.data[pos];
                    this.data[pos] = x;
                    pos = minIndex;
                }
                else {
                    break;
                }
            }
            return void 0;
        }
    }
    class PriorityQueue {
        constructor(options) {
            this.length = 0;
            this.length = 0;
            this.strategy = new BinaryHeapStrategy(options);
        }
        queue(value) {
            this.length++;
            this.strategy.queue(value);
        }
        dequeue() {
            if (!this.length)
                return;
            this.length--;
            return this.strategy.dequeue();
        }
        peek() {
            if (!this.length)
                return;
            return this.strategy.peek();
        }
        clear() {
            this.length = 0;
            this.strategy.clear();
        }
    }
    const mapSize = {
        width: grid[0].length,
        height: grid.length
    };
    const mapArea = mapSize.width * mapSize.height;
    const maxOpenNodes = 300;
    const centerNode = Node(myX, myY);
    const colorStats = new Array(bots.length + 1).fill(0);
    const nearestBotAtNode = new Array(mapArea);
    for (let x = 0; x < mapSize.width; ++x) {
        let row = grid[x];
        for (let y = 0; y < mapSize.height; ++y) {
            let color = row[y];
            ++colorStats[color];
            let id = nodeId(Node(x, y));
            let closestBots = null;
            for (let [botColor, botX, botY] of bots) {
                let distance = Math.max(1, manhattanDistance(x, y, botX, botY));
                if (closestBots === null || distance < closestBots.distance) {
                    closestBots = { distance, colors: [botColor] };
                }
                else if (distance == closestBots.distance) {
                    closestBots = { distance, colors: [...closestBots.colors, botColor] };
                }
            }
            nearestBotAtNode[id] = closestBots;
        }
    }
    const bestSpace = { node: null, space: 0 };
    const primaryEnemy = winningColor({ includeMyself: false, includeWhite: false, canErase: true });
    const isBehindWinner = winningColor({ includeMyself: true, includeWhite: false, canErase: false, ignoreColor: primaryEnemy }) == myColor;
    var step = Math.round(Math.max(1, mapSize.width / 30));
    for (let x = step; x < mapSize.width - step; x += step) {
        for (let y = step; y < mapSize.height - step; y += step) {
            let space = countSpace(x, y);
            if (bestSpace.node == null || space > bestSpace.space) {
                bestSpace.node = Node(x, y);
                bestSpace.space = space;
            }
        }
    }
    const goalNode = bestSpace.node;
    const isRetreat = nearestBotAtNode[nodeId(centerNode)].colors.length > 1;
    function Node(x, y) {
        return { x, y };
    }
    function AStarNode(node) {
        return Object.assign({}, node);
    }
    function nodeId(node) {
        return node.y * mapSize.height + node.x;
    }
    function defaultComparator(a, b) {
        return (a.cost + a.goal) - (b.cost + b.goal);
    }
    function nonDiagonalNodes(node) {
        return [
            node.x + 1 < mapSize.width ? Node(node.x + 1, node.y) : null,
            node.y + 1 < mapSize.height ? Node(node.x, node.y + 1) : null,
            node.x > 0 ? Node(node.x - 1, node.y) : null,
            node.y > 0 ? Node(node.x, node.y - 1) : null
        ].filter(x => x);
    }
    function mixColor(floorColor, botColor) {
        return [botColor, 0, floorColor][Math.abs(botColor - floorColor) % 3];
    }
    function countSpace(x, y, area = -1) {
        if (x < 0 || y < 0 || x >= mapSize.width || y >= mapSize.height) {
            return 0;
        }
        let color = grid[x][y];
        if (area == -1) {
            area = 0;
            while (countSpace(x, y, area)) {
                ++area;
            }
            return area * 2 * 4;
        }
        else if (area == 0) {
            if (color == myColor) {
                return 0;
            }
            if (color == 0 || mixColor(color, myColor) == myColor) {
                return 1;
            }
            return 0;
        }
        else {
            for (let dx = -area; dx <= area; ++dx) {
                if (!countSpace(x + dx, y - area, 0))
                    return 0;
                if (!countSpace(x + dx, y + area, 0))
                    return 0;
            }
            for (let dy = -area + 1; dy <= area - 1; ++dy) {
                if (!countSpace(x - area, y + dy, 0))
                    return 0;
                if (!countSpace(x + area, y + dy, 0))
                    return 0;
            }
            return area * 2 * 4;
        }
    }
    function manhattanDistance(x1, y1, x2, y2) {
        return Math.abs(x2 - x1) + Math.abs(y2 - y1);
    }
    function moveFromNodes(node1, node2) {
        if (node2.x < node1.x)
            return 'left';
        if (node2.x > node1.x)
            return 'right';
        if (node2.y < node1.y)
            return 'up';
        if (node2.y > node1.y)
            return 'down';
        return 'wait';
    }
    function winningColor(options) {
        let winningColor = {
            color: 0,
            count: 0
        };
        for (let color = 0; color < colorStats.length; ++color) {
            if (color === 0) {
                if (!options.includeWhite) {
                    continue;
                }
            }
            else if (color === myColor) {
                if (!options.includeMyself) {
                    continue;
                }
            }
            else if (color === options.ignoreColor) {
                continue;
            }
            else if (options.canErase && mixColor(color, myColor) === color) {
                continue;
            }
            if (colorStats[color] > winningColor.count) {
                winningColor.color = color;
                winningColor.count = colorStats[color];
            }
        }
        if (winningColor.count === 0) {
            return null;
        }
        return winningColor.color;
    }
    function goal(node) {
        let goal = manhattanDistance(node.x, node.y, goalNode.x, goalNode.y);
        return goal;
    }
    function cost(node, changes, depth) {
        let cost = 0;
        let id = nodeId(node);
        let color;
        if (changes[id] !== undefined) {
            color = changes[id];
        }
        else {
            color = grid[node.x][node.y];
        }
        if (color !== 0 && color !== myColor) {
            let mixedColor = changes[id] = mixColor(color, myColor);
            if (mixedColor === myColor) {
                if (nearestBotAtNode[nodeId(centerNode)].colors.includes(color) || nearestBotAtNode[id].colors.includes(color)) {
                    cost += 5000;
                }
                if (color === primaryEnemy) {
                    if (isBehindWinner) {
                        cost -= 80;
                    }
                    else {
                        cost -= 60;
                    }
                }
                else {
                    cost -= 30;
                }
            }
            else if (mixedColor === color) {
                cost += 0;
            }
            else if (mixedColor === 0) {
                if (color === primaryEnemy) {
                    if (isBehindWinner) {
                        cost -= 15;
                    }
                    else {
                        cost -= 10;
                    }
                }
                else {
                    cost -= 5;
                }
            }
        }
        if (color === 0) {
            changes[id] = myColor;
            let nearestBot = nearestBotAtNode[id];
            if (nearestBot.colors.includes(myColor)) {
                if (depth == 1 && nearestBot.colors.length > 1) {
                    cost += 20;
                }
                else {
                    cost -= 60;
                }
            }
            else {
                if (depth == 1 && nearestBot.distance == 0) {
                    cost += 30;
                }
                else {
                    let distanceDelta = depth - nearestBot.distance;
                    if (distanceDelta >= 0) {
                        cost += -50 + distanceDelta * 20;
                    }
                    else {
                        cost += -60;
                    }
                }
            }
        }
        return cost;
    }
    function bestMove(options) {
        let walkCost = 25;
        let goalImportance = 3;
        let goalTarget = false;
        if (options.strategy === 'long-sighted') {
            walkCost = 0;
            goalImportance = 25;
            goalTarget = true;
        }
        options.maxOpenNodes = Math.min(options.maxOpenNodes, mapArea);
        const openNodes = new PriorityQueue({
            comparator: defaultComparator
        });
        const bestNodes = new PriorityQueue({
            comparator: defaultComparator
        });
        const closedNodes = {};
        const closeNode = (node) => closedNodes[nodeId(node)] = node;
        const getClosedNode = (node) => closedNodes[nodeId(node)];
        if (!isRetreat) {
            const waitNode = AStarNode(options.fromNode);
            waitNode.depth = 0;
            waitNode.changes = {};
            waitNode.goal = goal(waitNode);
            waitNode.cost = cost(waitNode, waitNode.changes, waitNode.depth);
            bestNodes.queue(waitNode);
            closeNode(waitNode);
        }
        const startNode = AStarNode(options.fromNode);
        startNode.depth = 0;
        startNode.changes = {};
        startNode.goal = goal(startNode);
        startNode.cost = cost(startNode, startNode.changes, startNode.depth);
        openNodes.queue(startNode);
        while (openNodes.length && openNodes.length < maxOpenNodes) {
            let bestNode = openNodes.dequeue();
            nonDiagonalNodes(bestNode).forEach(node => {
                let nextNode = AStarNode(node);
                nextNode.depth = bestNode.depth + 1;
                nextNode.changes = Object.assign({}, bestNode.changes);
                nextNode.parent = bestNode;
                nextNode.goal = goal(node) * goalImportance;
                nextNode.cost = bestNode.cost + walkCost + cost(nextNode, nextNode.changes, nextNode.depth);
                if (goalTarget && nextNode.x == goalNode.x && nextNode.y == goalNode.y) {
                    bestNodes.queue(nextNode);
                    return;
                }
                let closedNode = getClosedNode(node);
                if (!closedNode || defaultComparator(nextNode, closedNode) < 0) {
                    openNodes.queue(nextNode);
                }
            });
            closeNode(bestNode);
            if (bestNode != startNode) {
                bestNodes.queue(bestNode);
            }
        }
        let directions = [];
        let bestNode = bestNodes.peek();
        if (options.strategy === 'short-sighted' && bestNode.depth < 6 && !isRetreat) {
            return bestMove(Object.assign({}, options, { strategy: 'long-sighted' }));
        }
        else {
            let nextMoveNode = bestNode;
            while (nextMoveNode.parent) {
                directions.unshift(moveFromNodes(nextMoveNode.parent, nextMoveNode));
                if (nextMoveNode.parent === startNode) {
                    break;
                }
                nextMoveNode = nextMoveNode.parent;
            }
            return moveFromNodes(startNode, nextMoveNode);
        }
    }
    return bestMove({
        strategy: 'short-sighted',
        fromNode: centerNode,
        maxOpenNodes,
    });
}

```

Stateless bot uses A-star pathing to find the most the most optimal path (optimal being the lowest cost path). Usually predicts 150 moves ahead on average, however if the best path has less than 6 moves ahead, the bot will be forced to walk to a cluster with most obtainable colors.

Some costs are driven by determining the winning color, and some costs are set to avoid circling around and losing trolls.

If anyone wishes to improve the code by playing around with editing the cost values, or rewriting the cost function, you are more than welcome to.

Have fun everyone.

**EDIT:** I cannot reply on chat (new accounts are not able to), but I just saw the comments. I optimised it for under 50ms now, all the way from 400ms. It will get faster over time. I am happy to optimise more if needed, just drop a message on chat or comment here.

**EDIT 2:** Some here are saying A-Star can only be used for finding path between two points, and while generally it is, in any pathing algorithm you can tweak the G (goal function) to return 0, that makes the path finding have no goal but rather seeks most efficient path in all directions, where each path cost is a cumulative cost of each step. Cost of step can be defined if they are good or bad steps, such as repainting enemy's square colour with ours is a good step, and walking onto a square that cannot be repained is a bad step. You will notice however the `goal()` function does not return 0, instead it slightly contributes to walking towards a cluster with most amount of paintable squares. **tldr; path end is the best cluster and the obstacles are how the color will be mixed along the way.**

# Answer
> 4 votes
## DFSBot

```
function(myself, grid, bots, gameinfo) {
    let max_scores = Array(12);
    max_scores[11] = 0;
    max_scores[10] = 0.05 * (1 + 1e-6 + 16.1 * 1e-10);
    for (let i = 9; i >= 0; i--) {
        max_scores[i] = max_scores[10] + 0.95 * max_scores[i + 1];
    }
    let my_id = myself[0];
    let my_x = myself[1];
    let my_y = myself[2];
    let delta = [[0, -1], [0, 1], [-1, 0], [1, 0], [0, 0]];
    let seen = Array(grid.length).fill().map(() => Array(grid.length).fill(false));
    let scores = Array(bots.length + 2).fill(0);
    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid.length; j++) {
            scores[grid[i][j]]++;
        }
    }
    function search(x, y, depth) {
        if (depth == 11) {
            return [4, 0];
        }
        let max_score = 0;
        let best_move = 0;
        for (let i = 0; i < 4; i++) {
            let x1 = x + delta[i][0];
            let y1 = y + delta[i][1];
            if ((x1 < 0) || (x1 >= grid.length)) {
                continue;
            }
            if ((y1 < 0) || (y1 >= grid.length)) {
                continue;
            }
            if (seen[x1][y1]) {
                continue;
            }
            let n = 0;
            for (let dx = -1; dx <= 1; dx++) {
                let x2 = x1 + dx;
                if ((x2 < 0) || (x2 >= grid.length)) {
                    continue;
                }
                for (let dy = -1; dy <= 1; dy++) {
                    if ((dx == 0) && (dy == 0)) {
                        continue;
                    }
                    let y2 = y1 + dy;
                    if ((y2 < 0) || (y2 >= grid.length)) {
                        continue;
                    }
                    n++;
                    if (grid[x2][y2] == my_id) {
                        n++;
                    }
                }
            }
            let prev = grid[x1][y1];
            if (prev == 0) {
                next = my_id;
            } else {
                next = [my_id, 0, prev][Math.abs(my_id - prev) % 3];
            }
            let score = 0;
            score += 1e-10 * (n + 0.1 * Math.random());
            if (next != prev) {
                if (next == my_id) {
                    score += 1;
                }
                if (prev == 0 || scores[prev] > scores[my_id]) {
                    score += 1e-6;
                }
            }
            score *= 0.05;
            if (score + 0.95 * max_scores[depth + 1] <= max_score) {
                continue;
            }
            grid[x1][y1] = next;
            seen[x1][y1] = true;
            let final_score = score + 0.95 * search(x1, y1, depth + 1)[1];
            seen[x1][y1] = false;
            grid[x1][y1] = prev;
            if (final_score > max_score) {
                max_score = final_score;
                best_move = i;
            }
        }
        return [best_move, max_score];
    }
    let best_move = search(my_x, my_y, 0)[0];
    return ["up", "down", "left", "right", "wait"][best_move];
}

```

# Answer
> 4 votes
## FarSightedGreed

```
function([id, x, y], grid, bots, gameInfo) {
    let value = n => n ? n == id ? 0 : 2 - Math.abs(id - n) % 3 : 2;
    let directions = [
        {name: "wait", x: 0, y: 0},
        {name: "left", x: -1, y: 0},
        {name: "up", x: 0, y: -1},
        {name: "right", x: 1, y: 0},
        {name: "down", x: 0, y: 1},
    ];
    for (let d of directions) {
        d.score = 0;
        for (let i = 1; ; i++) {
            let px = x + i * d.x;
            let py = y + i * d.y;
            if (px < 0 || py < 0 || px == grid.length || py == grid.length) break;
            if (bots.some(([, x, y]) => x == px && y == py)) break;
            d.score += value(grid[px][py]) / i;
        }
    }
    let best = Math.max(...directions.map(({score}) => score));
    return directions.find(({score}) => score == best).name;
}

```

Name shamelessly plagirised from NearSightedGreed. Simply scores all the visible squares in all cardinal directions according to distance and colour, and chooses the direction with the highest sum.

# Answer
> 3 votes
# Humble Paint Salesman

```
// Humble Paint Salesman
function(myself, grid, bots, gameInfo) {
    let [id, x, y] = myself;
    // if first move
    if(gameInfo[0] == 1) {
        this.size = grid.length;
        this.mid = this.size / 2;
        this.dx = x < this.mid ? "right" : "left";
        this.dy = y < this.mid ? "down" : "up";
        this.flip = function(v) {
            this.dict = this.dict || {
                right: "left",
                left: "right",
                down: "up",
                up: "down"
            };
            return this.dict[v];
        }
        this.queue = [];
    }
    if(grid[x][y] == 0) {
        return "wait";
    }
    else if(this.queue.length) {
        return this.queue.shift();
    }
    else if(x == 0 || x + 1 == this.size) {
        this.dx = this.flip(this.dx);
        if(y == 0 || y + 1 == this.size) {
            this.dy = this.flip(this.dy);
        }
        this.queue.push(this.dx);
        return this.dy;
    }
    return this.dx;
}

```

Simply covers the board, iterating up and down the board. Waits if the cell below him is empty (a salesman must peddle his wares!).

# Answer
> 3 votes
# AnnoyingLittleBrother

```
function(myself, grid, bots, gameInfo) {

    // Some paramters
    var brother_loop_count = 0;
    var brother_score = -1;
    var brother_id = 0;
    var number_of_brothers_followed = 0;
    var num_of_bots = -1;

    var saw_all_brothers_moves = 0;
    var moves_write = 0;
    let moves_to_follow = 30;      // How much moves will we follow?
    let moves_to_use = 5; // Only follow the last 5 elements of this array
    var moves_saw = makeArray(moves_to_follow, 2, 0);

    var my_id = myself[0];
    var my_x = myself[1];
    var my_y = myself[2];
    var round = gameInfo[0];
    var end_round = gameInfo[1];
    var last_num_of_bots = 0;

    // Handle Storage
    if(!localStorage.LB_nfirst){ // First round (Dont rely on round number)
      localStorage.LB_nfirst = true;

      brother_loop_count = 0;// lock on to anyone
      moves_write = 0;
      moves_saw = makeArray(moves_to_follow, 2, 0);
      let num_of_bots = bots.length;

      localStorage.LB_moves_saw = encode_moves(moves_saw);
      localStorage.LB_moves_write = moves_write;// Save it
      localStorage.LB_brother_id = brother_id;// Save it
      localStorage.LB_brother_loop_count = brother_loop_count; // Save it
      localStorage.LB_saw_all_brothers_moves = saw_all_brothers_moves;
      localStorage.LB_number_of_brothers_followed = number_of_brothers_followed;
      localStorage.LB_num_of_bots = num_of_bots;
    }
    else{
      moves_saw = decode_moves(localStorage.LB_moves_saw);
      moves_write = parseInt(localStorage.LB_moves_write);
      brother_id = parseInt(localStorage.LB_brother_id);
      brother_loop_count = parseInt(localStorage.LB_brother_loop_count);
      saw_all_brothers_moves = parseInt(localStorage.LB_saw_all_brothers_moves);
      last_num_of_bots = parseInt(localStorage.LB_last_num_of_bots);
      number_of_brothers_followed = parseInt(localStorage.LB_number_of_brothers_followed);
      num_of_bots = parseInt(localStorage.LB_num_of_bots);
    }

    // Check if our big brother was eliminated
    if(last_num_of_bots !== bots.length){
      // A bot was elimitated. Just tell LittleBrother to search for a new brother
      var found = false;
      for(var i = 0; i<bots.length; i++){
          if (bots[i][0]==brother_id){
              found = true;
              break;
          }
      }
      if(!found){
          brother_loop_count = 0;
          brother_id = 0;
      }
      last_num_of_bots = bots.length;
    }
    // Check if we are in a infinite loop with big brother
    function equals(a, b) {
        return a[0]===b[0] && a[1]===b[1];
    }
    if (brother_id !== 0 && (saw_all_brothers_moves===1)){
        var found_curr_step = new Uint32Array(moves_to_use);
        var left = (moves_write+1)%moves_to_follow;
        var right = (moves_write+1+moves_to_use)%moves_to_follow;
        if (right > left){var comp = moves_saw.slice(left,right);}
        else{var comp = moves_saw.slice(left);comp.push(...moves_saw.slice(0,right));}
        for (var i = 0; i < moves_to_follow-moves_to_use; i++){
            for (var j = 0; j < moves_to_use; j++){if(equals(comp[j], moves_saw[(i+right)%moves_to_follow])){found_curr_step[j]=true;}}
        }
        var should_clear = true;
        for(var j = 0; j < moves_to_use; j++){if(!found_curr_step[j]){should_clear = false;break;}}
        if (should_clear){
            brother_loop_count = 0;
            brother_id = 0;
        }

    }

    // Are we tired of this brother yet?
    if (brother_loop_count === 0){
      // Determine each bot's score
      var bot_scores = new Uint32Array(num_of_bots+1);
      for (var x = 0; x < grid.length; x++) {
        for (var y = 0; y < grid.length; y++) {
          bot_scores[grid[x][y]] += 1;    // Increase the score of the bot's who color this is
          // The eliminated bots' scores will just stay zero
        }
      }

      // Find a bot to follow
      brother_id = 0;
      if (Math.random() > 0.6){
        var backup_bro = 0;
        var tolerance = 0;
        var chance = Math.random();
        if (chance > 2){tolerance = 1;} // Never
        if (chance > 2){tolerance = 2;} // Never
        for (var uid = 1; uid < bot_scores.length; uid++){
          if (bot_scores[uid]>brother_score && my_id!==uid){
            if (Math.abs(my_id - uid)%3<=tolerance){// Will it be annoying to the brother?
              brother_score = bot_scores[uid];
              brother_id = uid;
            }
            else{
              if(Math.abs(my_id - uid)%3<2){
                backup_id = uid; // In case we didn't find what we wanted.
              }
            }
          }
        }
      }
      // If we don't have a brother yet, find a random one
      if (brother_id === 0){
        var tries = 0;
        do{
          var ridx = Math.round(Math.random()*(bots.length-1));
          if(bots[ridx][0]!==my_id && Math.abs(my_id - bots[ridx][0])%3===0){
            brother_id = bots[ridx][0];
          }
        }while(brother_id === 0 && tries++<=20);
      }
      if (brother_id===0){brother_id = (my_id===1)?2:1;}

      // Start the brother follow counter
      moves_write = 0;
      saw_all_brothers_moves = 0;
      brother_loop_count = 200 + 300*number_of_brothers_followed;
      number_of_brothers_followed ++;
    }

    // Decrease the loop count variable to make sure we don't stagnate
    brother_loop_count -= 1; // But only for so long

    // Now do the actual following
    var aim_x = -1;
    var aim_y = -1;
    var bro_x = -1;
    var bro_y = -1;
    if (brother_id > 0){

    // Find where brother currently is
    for (var i = 0; i < bots.length; i++){
      if (bots[i][0] === brother_id){
        brother_idx = i;
        break;
      }
    }

    // Which point are we aiming for?
    if(saw_all_brothers_moves === 1 || moves_write > moves_to_use){ // Did I see how my brother moves?

      // Calculate the slice of steps we are going to use
      var left = ((saw_all_brothers_moves===1) ? moves_write+1 : 0)%moves_to_follow;
      var right = ((saw_all_brothers_moves===1) ? moves_write+moves_to_use+1 : moves_to_use)%moves_to_follow;
      if (right > left){// want to read left --> right in moves_saw
         var steps_to_use = moves_saw.slice(left,right);
      }
      else{
        var steps_to_use = moves_saw.slice(0,right)
        steps_to_use.push(...moves_saw.slice(left));
      }

      // Check if we are in his footsteps?
      var in_brothers_footsteps = false;
      for (var step = 0; step<steps_to_use.length; step++){
        if ((steps_to_use[step][0] === my_x) && ((steps_to_use[step][1] === my_y))){
          in_brothers_footsteps = true;
          break;
        }
      }

      if(in_brothers_footsteps === true){
        // We are in his footsteps. Go to the next one!;
        step++; if (step >= steps_to_use.length){step=0;}
        aim_x = steps_to_use[step][0];aim_y = steps_to_use[step][1];
      }
      else{
        // We are not in his footsteps, aim for the footsteps
        aim_x = 0; aim_y = 0;
        for (var step = 0; step<steps_to_use.length; step++){// Calculate step's center of mass
           aim_x += steps_to_use[step][0];aim_y += steps_to_use[step][1];
        }
        aim_x /= moves_to_use; aim_y /= moves_to_use;
      }
    }
    else{
      // No, not yet. Just run towards him
      aim_x = bots[brother_idx][1];
      aim_y = bots[brother_idx][2];
    }

    // Check if we might touch big brother
    let [dx, dy] = PosAt(toPos([aim_x, aim_y]));
    if (my_x+dx===bots[brother_idx][1] && my_y+dy===bots[brother_idx][2]){
      // EEEUUW. Flinch away, because it's weird.
      aim_x = my_x; aim_y = my_y;
    }
    }

    // Watch big brother's moves
    if(brother_id > 0){
      moves_saw[moves_write][0] = bots[brother_idx][1];
      moves_saw[moves_write][1] = bots[brother_idx][2];
      moves_write ++;
      if (moves_write===moves_to_follow){
        moves_write = 0; // Wrap counter for circular buffer

        // Have I seen enough of them?
        if(saw_all_brothers_moves === 0){
          saw_all_brothers_moves = 1;
        }
      }
    }

    // Save updated variables
    localStorage.LB_moves_saw = encode_moves(moves_saw);
    localStorage.LB_moves_write = moves_write;// Save it
    localStorage.LB_brother_id = brother_id;// Save it
    localStorage.LB_brother_loop_count = brother_loop_count; // Save it       l
    localStorage.LB_saw_all_brothers_moves = saw_all_brothers_moves;
    localStorage.LB_last_num_of_bots = last_num_of_bots;
    localStorage.LB_number_of_brothers_followed = number_of_brothers_followed;

    // Finish function
    if (brother_id <= 0){ // If not following anybody, move randomly
      return ["up","down","left","right"][Math.random()*4|0];
    }
    else{
      // Following a big brother!
      return toPos([aim_x, aim_y]);
    }

    // Some functions to ease the load
    function toPos([x,y]) {
      var dx = x - my_x;
      var dy = y - my_y;
      if(Math.abs(dx)>Math.abs(dy)){
        if (x > my_x) return "right";
        if (x < my_x) return "left";
        if (y < my_y) return "up";
        if (y > my_y) return "down";
      }
      else{
        if (y < my_y) return "up";
        if (y > my_y) return "down";
        if (x > my_x) return "right";
        if (x < my_x) return "left";
      }
      return 'wait';
    }
    function PosAt(dir){
      if (dir === 'left') return [-1,0];
      if (dir === 'right') return [1, 0];
      if (dir === 'up') return [0, -1];
      if (dir === 'down') return [0, 1];
      return [0,0];
      }
      function decode_moves(moves_str){
      var moves_array = [];
      var moves_strs = moves_str.split(';');
      for (var i = 0; i<moves_to_follow; i++){
        var splot = moves_strs[i].split(',');
        moves_array[i] = [];
        moves_array[i][0] = parseInt(splot[0]);
        moves_array[i][1] = parseInt(splot[1]);
      }
    return moves_array;
    }
      function encode_moves(moves_array){
      var moves_str = "";
      for (var i = 0; i < moves_array.length; i++){
        moves_str += moves_array[i][0] + ',' + moves_array[i][1];
        if (i < moves_array.length - 1){moves_str += ';';}
      }
      return moves_str;
    }
    function makeArray(w, h, val) {
      var arr = [];
      for(i = 0; i < w; i++) {
        arr[i] = [];
        for(j = 0; j < h; j++) {
          arr[i][j] = 0;
        }
      }
      return arr;
    }
}

```

This little bot is like any little brother. It will **latch on to you**, and mirror your **every step**. Like your little brother would jump into your footsteps with his over-sized boots. But he will only follow you if he can annoy you. He is your little brother after all.

Essentially it selects the highest ranking bot it can affect as its big brother, and follows it relentlessly. Initially it just runs straight towards it, but then it starts to remember the big brother's moves, and follows them step-by-step (using some circular buffer magic).

This is my first submission in this SE, and my first time programming in Javascript. So any advice/feedback would be greatly appreciated!

I hope `LittleBrother` doesn't annoy you guys too much ;)

*Note: Although the function itself is pretty huge, it's very quick. There isn't much time consuming things in.*

**Update 22 Aug 2018 20:42:**

* Big brother selection improved to actually work. Now it only has 30% chance of chasing someone who's colour it will only clear. The rest of the times it will try and overwrite colours. It will never follow anyone that it can't do anything against.
* As game time passes it will grow attached for longer periods of time.
* Stop relying on round number for variable initialization.

**Update 23 Aug 2018 11:19:**

* Little brother no longer gets lots if he struggles to find a big brother.
* He is now more light footed because he is scared of John. So never will he step on Jim, or any other big brother of his.

**Update 25 Aug 2018 19:26 (*Niceness* Update):**

* Lil'Bro no longer latches on to the leaders. It only finds a random older brother to follow. But, as we all do, he sometimes get jealous. So he has a 40% chance of selecting to follow the leading brother.
* He also knows nice people get stepped on. So he now follows bots from a distance. Usually about 25 cells behind, but still follows your *every step*. Hopefully this will keep John at peace.
* A large purpose of this update is to limit the infinite loops. Top bots usually takes the closest squares, which usually are the ones Lil'Bro just took from them. Therefore, Lil'Bro follows random bots, hoping they won't notice as easily. Also, by trailing so far behind, there is *usually* nicer bait for the target, than the cells Lil'Bro just overwrote.

**Update 27 Aug 2018 21:32 (Some New Smarts):**

* Improved smart while another player dies. No longer just resets, instead LittleBrother now checks if it actually concerned him.
* LittleBrother realized his score is severely inhibited if he gets into a quarrel (infinite loop) with his brother. No he gets annoyed, and looks for someone else if this happens.

# Answer
> 3 votes
# DragonBot

```
function dragonCurve(myself, grid, bots, gameInfo){
  dCurve=n=>{
    if(n==0){return "1 "}
    return dCurve(n-1).replace(/(.)(.)/g,"1$10$2")
  }
  [id,x,y]=myself;
  dir=0;
  if(gameInfo[0]==1){
    dragon=dCurve(12);
    if(x<3*bots.length-x){
      if(y<x){dir=0;}
      else if(3*bots.length-y<x){dir=2;}
      else{dir=3;}
    }
    else{
      if(y<3*bots.length-x){dir=0;}
      else if(y>x){dir=2;}
      else{dir=1;}
    }
    window.localStorage.setItem("dragon",dragon);
    window.localStorage.setItem("dragonDir",dir);
    window.localStorage.setItem("dragonStep",0);
    return ["up","right","down","left"][dir];
  }
  dragon=window.localStorage.getItem("dragon")
  dir=window.localStorage.getItem("dragonDir")-0;
  step=window.localStorage.getItem("dragonStep")-0;
  if(gameInfo[0]%2==0){
    return ["up","right","down","left"][dir];
  }
  validStep=false;
  while(!validStep){
    if(-dragon[step]){dir=(dir+1)%4;}
    else{dir=(dir+3)%4;}
    step+=1;
    validStep=((dir==0&&y!=0)||(dir==3&&x!=0)||(dir==1&&x!=3*bots.length-1)||(dir==2&&y!=3*bots.length-1));
  }
  window.localStorage.setItem("dragon",dragon);
  window.localStorage.setItem("dragonDir",dir);
  window.localStorage.setItem("dragonStep",step);
  return ["up","right","down","left"][dir];
}

```

DragonBot simply tries to draw a dragon curve with side lengths of 2 (so that it leaves spaces).

# Answer
> 3 votes
# Drone-BEA7

```
function(myData, gridData, botData, gameInfoData) {
  function customSetup(fThis) {
    fThis.botUID = 0;
    fThis.swarm = new Array(3);
    fThis.matchedSize = 0;
    bots.forEach(b => { b.failedSignal = 0; b.trespass = 0; b.desecrate = 0; });
    delete fThis.connected;
    delete fThis.target;
    delete fThis.chaser;
    delete fThis.cleaners;
    delete fThis.roamers;
  }

  let XY = this.xyClass;
  let Bot = this.botClass;
  let Cell = this.cellClass;

  function at(pos, usedGrid = grid) { // NEVER EVER THINK ABOUT PUTTING THIS ON THE GRID ITSELF
    return pos.withinBounds() ? usedGrid[pos.toIndex()] : new Cell(null);
  }

  if (gameInfoData[0] === 1) {
    XY = this.xyClass = (class XY {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }

      static fromIndex(index) {
        return new XY(Math.floor(index / gridSize), index % gridSize);
      }
      toIndex() {
        return this.x * gridSize + this.y;
      }

      add(other) {
        return new XY(this.x + other.x, this.y + other.y);
      }
      sub(other) {
        return new XY(this.x - other.x, this.y - other.y);
      }
      div(value) {
        return new XY(Math.round(this.x / v), Math.round(this.y / v));
      }
      mul(value) {
        return new XY(Math.round(this.x * m), Math.round(this.y * m));
      }
      equals(other) {
        return this.x === other.x && this.y === other.y;
      }

      distance(other) {
        return Math.abs(other.x - this.x) + Math.abs(other.y - this.y);
      }
      chebyshevDistance(other) {
        return Math.max(Math.abs(other.x - this.x), Math.abs(other.y - this.y));
      }

      withinBounds() {
        return this.x >= 0 && this.x < gridSize && this.y >= 0 && this.y < gridSize;
      }

      getNeighbors() {
        return neighbors.map(p => this.add(p));
      }
      getRealNeighbors() {
        return this.getNeighbors().filter(p => p.withinBounds());
      }
    });
    Bot = this.botClass = (class Bot extends XY {
      constructor(botData) {
        super(botData[1], botData[2]);
        this.id = botData[0];
        this.score = 0;
        this.dead = true;
      }
    });
    Cell = this.cellClass = (class Cell {
      constructor(id, xy) {
        this.id = id;
        this.pos = xy;
      }
    });

    this.botMap = [];
    this.botIDs = [];
    botData.forEach(d => { this.botMap[d[0]] = new Bot(d); this.botIDs.push(d[0]); });
    this.currentRound = 0;

    delete this.prevGrid;
  }

  const gridSize = gridData.length;
  const gridSizeSqr = gridSize * gridSize;
  const grid = new Array(gridSize * gridSize);
  for (var x = 0; x < gridSize; x++) {
    for (var y = 0; y < gridSize; y++) {
      grid[x * gridSize + y] = new Cell(gridData[x][y], new XY(x, y));
    }
  }
  const prevGrid = this.prevGrid;
  this.prevGrid = grid;

  const bots = [];
  const botMap = this.botMap;
  this.botIDs.forEach(id => botMap[id].dead = true);
  botData.forEach(d => {
    const r = botMap[d[0]];
    r.dead = false;
    r.lastPosition = new XY(r.x, r.y);
    r.x = d[1];
    r.y = d[2];
    r.score = grid.reduce((sum, cell) => sum + (cell.id === r.id), 0);
    bots.push(r);
    at(r).bot = r;
  });
  const me = botMap[myData[0]];

  const currentRound = this.currentRound++;
  const maxRound = gameInfoData[1] - 1;

  const zero = new XY(0, 0);
  const neighbors = [new XY(1, 0), new XY(0, 1), new XY(-1, 0), new XY(0, -1)];
  const moves = ["right", "down", "left", "up", "wait"];

  if (gameInfoData[0] === 1) {
    customSetup(this);
  }

  function rand(max = 1, min = 0) {
    return min + Math.random() * (max - min);
  }
  function randInt(max, min = 0) {
    return Math.floor(rand(max, min));
  }
  function roll(chance = 0.5) {
    return Math.random() < chance;
  }

  function separation(id1, id2) {
    return Math.abs(id1 - id2) % 3;
  }

  function value(id, bot = me) {
    return id === bot.id ? 1 : id === 0 ? 4 : id === null ? 0 : [5, 3, 2][separation(bot.id, id)];
  }

  function travelTo(goal, start = me) {
    const relative = goal.sub(start);
    return Math.abs(relative.x) > Math.abs(relative.y) ? (
      relative.x > 0 ? 0 : 2
    ) : (
      relative.y > 0 ? 1 : relative.y < 0 ? 3 : 4
    );
  }
  function travelToList(goal, start = me) {
    const relative = goal.sub(start);
    return [...start.getRealNeighbors(), start].sort((a, b) => (a.chebyshevDistance(goal) - b.chebyshevDistance(goal)) * gridSizeSqr + (a.distance(goal) - b.distance(goal)));
  }

  const swarm = this.swarm;
  const swarmSize = swarm.length;
  const botUID = this.botUID;

  const signalPatterns = [[3, 0, 1, 1, 0], [0, 1, 2, 2, 2, 3, 3, 2, 2, 1], [2, 3, 2, 3, 0, 0, 1, 0, 3, 3]];
  function patternMove(pos, round, ...pattern) {
    const e = pattern[round % pattern.length];
    const f = (e + 2) % 4;
    function calcPos(d) { return pos.add(neighbors[d]); }
    if (calcPos(e).withinBounds()) {
      return e;
    } else {
      return f;
    }
  }
  function signal(uid = botUID, pos = me, round = currentRound) {
    return patternMove(pos, round, ...signalPatterns[uid]);
  }

  if (currentRound) {
    for (var i = 0; i < swarmSize; i++) {
      if (!swarm[i]) {
        const consideredBots = bots.filter(b => !(b.failedSignal & (1 << i)));
        const matchedBots = consideredBots.filter(b => {
          const prevPos = b.lastPosition;
          const expected = neighbors[signal(i, prevPos, currentRound - 1)];
          const performed = b.sub(prevPos);
          const matched = performed.equals(expected);
          if (!matched) {
            b.failedSignal |= (1 << i);
          }
          return matched;
        });
        if (matchedBots.length === 1) {
          swarm[i] = matchedBots[0];
          swarm[i].member = true;
          this.matchedSize++;
          console.log("Swarm member", i, "found!");
        }
      }
    }
  }

  function findTarget() {
    const lists = [];
    lists.unshift(bots.filter(b => b.removal.candidate));
    lists.unshift(lists[0].filter(b => b.removal.separations[0] === 0));
    lists.unshift(lists[0].filter(b => b.removal.speed === 3));
    lists.unshift(lists[2].filter(b => b.removal.separations[0] === 2));
    const bestList = lists.find(l => l.length);
    if (!bestList) {
      console.log("No more targets!");
      return undefined;
    }
    const bestTarget = bestList.sort((a, b) => b.trespass - a.trespass)[0]; // TODO: Remove sort. TODO: Improve.
    console.log("Best target:", bestTarget);
    return bestTarget;
  }

  if (this.matchedSize === swarmSize) {
    if (!this.connected) {
      bots.forEach(b => {
        const separations = swarm.map(m => separation(b.id, m.id));
        const speed = Math.floor(separations.reduce((sum, val) => sum + (val < 2 ? 1 : 0.5), 0));
        b.removal = {separations: separations, speed: speed, candidate: speed > 1 && !b.member};
      });
      console.log("All connections established.");
      this.connected = true;
    }

    bots.forEach(b => {
      if (b.removal.separations[0] !== 2 && at(b, prevGrid).id === swarm[0].id) {
        b.desecrate++;
      }
      swarm.forEach((m, i) => {
        if (b.removal.separations[i] !== 2 && at(b, prevGrid).id === m.id) {
          b.trespass++;
        }
      });
    });

    if (!this.target || this.target.dead) {
      this.target = findTarget();

      swarm.forEach(b => {
        delete b.partner;
      });

      const sep = this.target.removal.separations;
      const overwriters = [];
      const eraser = [];
      const helpers = [];
      for (var i = 0; i < swarmSize; i++) {
        if (swarm[i].partner) {
          continue;
        }
        if (sep[i] === 0) {
          overwriters.push(swarm[i]);
        } else if (sep[i] === 1) {
          eraser.push(swarm[i]);
        } else if (sep[i] === 2) {
          for (var j = i + 1; j < swarmSize; j++) {
            if (sep[j] === 2) {
              swarm[j].partner = swarm[i];
              swarm[i].partner = swarm[j];
              eraser.push(swarm[i]);
              break;
            }
          }
          if (!swarm[i].partner) {
            helpers.push(swarm[i]);
          }
        }
      }

      this.chaser = eraser.pop() || overwriters.pop();
      this.cleaners = [...overwriters, ...eraser];
      this.roamers = helpers; // TODO: Make helpers more useful by making them simply target the next guy?
    }

    function findImmediate(target, bot = me) {
      const list = travelToList(target, bot);
      return list.find(p => !at(p).reserved) || list[0];
    }

    grid.forEach(c => c.reserved = 0);
    function reserve(bot, target) {
      if (!bot.target) {
        bot.immediateTarget = findImmediate(target, bot);
        bot.target = target;
        at(bot.immediateTarget).reserved++;
        at(target).reserved++;
      }
    }
    function unreserve(bot) {
      if (bot.target) {
        at(bot.immediateTarget).reserved--;
        at(bot.target).reserved--;
        delete bot.immediateTarget;
        delete bot.target;
      }
    }

    reserve(this.chaser, chase(this.target));

    for (var i = 0; i < swarmSize; i++) {
      const emergency = preserveLife(swarm[i]);
      if (emergency) {
        unreserve(swarm[i]);
        reserve(swarm[i], emergency);
      }
    }

    this.cleaners.forEach(b => reserve(b, clean(b, this.target, this.cleaners)));
    this.roamers.forEach(b => reserve(b, roam(b)));

    const immediateTarget = me.immediateTarget || findImmediate(me.partner.target);
    swarm.forEach(b => unreserve(b));

    return moves[travelTo(immediateTarget)];
  } else {
    return moves[signal()];
  }

  function chase(target) {
    return target;
  }
  function clean(bot, target, cleaners) {
    return grid.filter(c => {
      return c.id === target.id && !c.reserved;
    }).reduce((best, c) => {
      const closest = Math.min(...cleaners.map(b => b.distance(c.pos)));
      const distance = bot.distance(c.pos);
      const wrongness = distance - closest;
      const distanceFromTarget = target.distance(c.pos);
      if (wrongness < best.wrongness || (wrongness === best.wrongness && (distance < best.distance || (distance === best.distance && distanceFromTarget > best.distanceFromTarget)))) {
        return {wrongness: wrongness, distance: distance, distanceFromTarget: distanceFromTarget, pos: c.pos};
      } else {
        return best;
      }
    }, {wrongness: Infinity, distance: Infinity, distanceFromTarget: -Infinity, pos: bot}).pos;
  }
  function roam(bot) {
    const dangerousBots = bots.filter(b => !b.member && separation(b.id, bot.id) !== 2);
    return grid.filter(c => {
      return value(c.id, bot) >= 4 && !c.bot && !c.reserved && !swarm.find(m => m.id === c.id);
    }).reduce((best, c) => {
      const val = value(c.id, bot);
      const distance = bot.distance(c.pos);
      const comfyness = c.pos.getNeighbors().reduce((sum, next) => sum + (value(at(next).id, bot) <= 2), 0);
      const closestBotDist = Math.min(...dangerousBots.map(b => b.distance(c.pos)));
      if (distance < best.distance || (distance === best.distance && (val > best.val || (val === best.val && (comfyness > best.comfyness || (comfyness === best.comfyness && closestBotDist > best.closestBotDist)))))) {
        return {distance: distance, val: val, comfyness: comfyness, closestBotDist: closestBotDist, pos: c.pos};
      } else {
        return best;
      }
    }, {distance: Infinity, val: -Infinity, comfyness: -Infinity, closestBotDist: -Infinity, pos: bot}).pos;
  }
  function preserveLife(bot) {
    if (bot.score < 20) {
      return roam(bot);
    }
  }
}

```

This is the leader of a trio of drones. Their task is simple: destroy enemy painters with the power of coding and algorithms! A more in-depth description is coming soon, along with much-needed performance optimizations.

## Changelog

### 1.1

* Fixed on the official controller
* Improved performance by ~25%

### 1.0

* Initial release

# Answer
> 2 votes
## NearSightedGreed

```
function(myself, grid, bots, gameInfo) {
    let ret = [];
    let col = myself[0];
    let myX = myself[1];
    let myY = myself[2];

    if(grid[myX][myY] != col){
        return "wait";
    }
    if(myX != 0 && grid[myX-1][myY] != col){
        ret.push("up")
    }
    if(myX != grid.length-1 && grid[myX+1][myY] != col){
        ret.push("down")
    }
    if(myY != 0 && grid[myX][myY-1] != col){
        ret.push("left")
    }
    if(myY != grid[0].length && grid[myX][myY+1] != col){
        ret.push("right")
    }
    return ret[Math.random() * ret.length|0]
}

```

Tries to move to adjacent fields with enemy colors, otherwise moves randomly.
Always prefers painting the current field until it's the right color

# Answer
> 2 votes
# No Do Overs

```
function(myself, grid, bots, gameInfo) {
    this.setupDone = false; if(this.setupDone == false) {
    var c = myself[0];
    var x = myself[1];
    var y = myself[2];
    var n = grid.length;

    var dirs = ["left", "up", "down", "right"]
    for(var _ = 0; _ < 4; _++) {
     var dir = dirs.splice(Math.random() * dirs.length | 0, 1);
     if(dir == "left" && x != 0 && grid[x-1][y] == 0) {
      return "left";
     }
     if(dir == "right" && x != n - 1&& grid[x+1][y] == 0) {
      return "right";
     }
     if(dir == "up" && y != 0 && grid[x][y-1] == 0) {
      return "up";
     }
     if(dir == "down" && y != n - 1 && grid[x][y+1] == 0) {
      return "down";
     }
     if(dir == "left" && x != 0 && grid[x-1][y] != c) {
      return "left";
     }
     if(dir == "right" && x != n - 1 && grid[x+1][y] != c) {
      return "right";
     }
     if(dir == "up" && y != 0 && grid[x][y-1] != c) {
      return "up";
     }
     if(dir == "down" && y != n - 1 && grid[x][y+1] != c) {
      return "down";
     }
    }
    dirs = [];
    if(x != 0) dirs[dirs.length] = "left";
    if(x != n - 1) dirs[dirs.length] = "right";
    if(y != 0) dirs[dirs.length] = "up";
    if(y != n - 1) dirs[dirs.length] = "down";
    return dirs[Math.random() * dirs.length | 0];
} }

```

Named "No Do Overs" because it will not paint over its own color, unless the only other option would be "wait".

# Answer
> 2 votes
# Clever Name

```
function(myself, grid, bots, gameInfo) {
    // Do a quick dance for identification.
    let round = gameInfo[0];
    if (round < 5) {
        return ["down", "right", "up", "left"][round % 4];
    }

    // Parse the arguments.
    let [myId, myX, myY] = myself;

    // Check each square to see if it's a good target.
    let targetX, targetY, targetDist = Infinity;
    let numAtDist;
    for (let x = 0; x < grid.length; x++) {
        for (let y = 0; y < grid.length; y++) {
            // Whoever's fighting for this square can have it.
            if (x === myX && y === myY) { continue; }
            // We don't care about our own squares.
            if (grid[x][y] === myId) { continue; }

            // Only squares that we can recolor are useful.
            if (grid[x][y] === 0 || Math.abs(grid[x][y] - myId) % 3 !== 2) {
                // Avoid squares that take effort.
                if (Math.abs(grid[x][y] - myId) % 3 === 1 && Math.random() < 0.5) { continue; }

                // If this is the closest we've seen, target it.
                let dist = Math.abs(myX - x) + Math.abs(myY - y);
                if (dist < targetDist) {
                    targetX = x;
                    targetY = y;
                    targetDist = dist;
                    numAtDist = 1;
                // If it's tied for the closest, sometimes target it.
                } else if (dist === targetDist) {
                    numAtDist++;
                    if (Math.floor(numAtDist * Math.random()) === 0) {
                        targetX = x;
                        targetY = y;
                    }
                }
            }
        }
    }

    // Move toward the target.
    if (targetX < myX) { return "left"; }
    if (targetX > myX) { return "right"; }
    if (targetY < myY) { return "up"; }
    if (targetY > myY) { return "down"; }
    return "wait";
}

```

There are two ways to be the best: build yourself up, or tear others down. This takes the former approach. It moves greedily toward one of the nearest squares that can be colored.

# Answer
> 2 votes
### Jealous Ant V14 1214923

```
function ([mc, mx, my], grid, bots, [rcurr, rmax]) {
    var [dest, ddest, odest, rev] = [{ left: "left", right: "right", up: "up", down: "down" }, {leftup: ["left", "up"], leftdown: ["left", "down"], rightup: ["right", "up"], rightdown: ["right", "down"]}, {wait: "wait"}, {left: "right", right: "left", up: "down", down: "up" }];
    var deltas = { x: { wait: 0, left: -1, right: +1, up: 0, down: 0, leftup: -1, leftdown: -1, rightup: +1, rightdown: +1, wait: 0 }, y: { wait: 0, left: 0, right: 0, up: -1, down: +1, leftup: -1, leftdown: +1, rightup: +1, rightdown: -1}};
    var [[gmin, gmax], blank, name] = [[0, grid.length - 1], 0, "Jealous Ant V14 1214923"];
    var db = JSON.parse(localStorage.getItem(name)) || onInit(db)
    var [pstrategy, pplus, pboost, pbotdist, pdepth, dsight, dpaint] = db.params
    function calcColor(color = mc, x = mx, y = my) { return Object.keys({...dest, ...ddest, ...odest}).map(pos => getColorXY(x + deltas.x[pos], y + deltas.y[pos]) == color).filter(v => v).length; }
    function calcDistanceXY(x, y, xx = mx, yy = my) { return Math.abs(xx - x) + Math.abs(yy - y); }
    function calcPaintable(x = mx, y = my) { return Object.keys({...dest, ...ddest}).map(pos => isPaintableXY(x + deltas.x[pos], y + deltas.y[pos]) && !isFriendXY(x + deltas.x[pos], y + deltas.y[pos])).filter(v => v).length; }
    function calcScores() { return bots.map(c => [c[0], getScore(c[0])]); }
    function canMoveHard(pos) { return isValidMove(pos); }
    function canMoveSoft(pos, prev) { return canMoveHard(pos) && isEdible(pos) && !isNearEdge(pos) && !isPrevMove(pos, prev); }
    function doNoise(op) { console.log(name + ": " + op); }
    function getAvailMoves(x = mx, y = my) { return Object.values(dest).map(function (pos) { return isValidMove(pos, x, y) ? pos : false; }).filter(function (pos) { return pos; }); }
    function getBotXY(uid) { return bots.find(b => b[0] == uid).slice(1); }
    function getColor(pos) { return getColorXY(mx + deltas.x[pos], my + deltas.y[pos]); }
    function getColorXY(x, y) { return isValidXY(x, y) ? grid[x][y] : -1; }
    function getEnemyXY() { if (rcurr % 5 == 0 && db.enemies.length > 0) doNoise(Array(db.enemies[0][0]).join(" Arf!")); return getBotXY(db.enemies.length > 0 ? db.enemies[0][0] : db.scores.filter(bot => bot[0] != mc && Math.abs(mc - bot[0]) % 3 != 2).sort((a,b) => b[1] - a[1])[0][0]) || [mx, my]; }
    function getLongestDir() { return [[dest.left, calcDistanceXY(0, my)], [dest.right, calcDistanceXY(gmax, my)], [dest.up, calcDistanceXY(mx, 0)], [dest.down, calcDistanceXY(mx, gmax)]].sort((a,b) => b[1] - a[1])[0][0]; }
    function getRandomDir() { moves = Object.values(dest).filter(m => isValidMove(m)); return moves[Math.floor(Math.random() * moves.length)]; }
    function getBadBots() { return bots.filter(b => b[0] != mc && getNextColor(mc, b[0]) == b[0]).map(a => a[0]); }
    function getDiagonalMoves(moves) { return [].concat(...best.map(m => Object.values(ddest).filter(a => a.includes(m)))); }
    function getDirectionXY(x, y) { return Object.keys(dest).map(dir => [dir, calcDistanceXY(x - deltas.x[dir], y - deltas.y[dir])]).sort((a,b) => a[1] - b[1])[0][0]; }
    function getNextColor(color, base = mc) { return color == blank ? base : [base, 0, color][Math.abs(base - color) % 3]; }
    function getNextMove() {
        new_pos = getStrategyPos(db.mpos);
        return db.steps > 0 || (db.steps > -10 && isPaintable(new_pos) && calcPaintable(mx + deltas.x[new_pos], my + deltas.y[new_pos]) >= dpaint && !isFriendPos(new_pos))
            ? [new_pos, db.steps]
            : getBestDir(db.mpos, db.mprev) || [dest.wait, 5];
    }
    function getBestDir(pos, prev) {
        main_moves = getBestMoves() || [getDirectionXY(getEnemyXY(x, y))]
        diagonal = getDiagonalMoves(main_moves);
        if (main_moves.length == 1) return [main_moves[0], 1];
        routes = Object.values(main_moves).map(function (pos) { return { pos: pos, depth: 0, score: getScoreDir(pos, prev) }; });
        routes = routes.concat([].concat(...Object.values(diagonal)
            .map(function (pos) { score = getScoreDir(pos, prev); return [{pos: pos[0], depth: 0, score: score/2}, {pos: pos[1], depth: 0, score: score/2}]; }))
            .filter(p => main_moves.includes(p["pos"])));
        results = main_moves.map(pos => pos = [pos, routes.filter(a => a["pos"] == pos).reduce((a, b) => a + b["score"] - b["depth"] * 1/dsight, 0)]).sort((a, b) => b[1] - a[1]);
        routes.sort(function (a, b) { return b["score"] - a["score"] })
        var [depth, steps, best_pos, best_score, max_depth] = [0, 1, routes[0]["pos"], routes[0]["score"], gmax / rmax * Math.min(rcurr, rmax) / 2];
        if (routes.length > 1 && noEdible()) {
            for (depth = 1; depth <= max_depth; depth++) {
                routes = routes.concat(Object.values(main_moves).map(function (pos) {
                    [x, y] = [mx + deltas.x[pos] * depth, my + deltas.y[pos] * depth]; moves = getBestMoves(x, y);
                    return { pos: pos, depth: depth, score: Math.max(...Object.values(moves).map(function (pos) { return getScoreDir(pos, prev, x, y); }), 0) };
                }))
                routes = routes.concat([].concat(...Object.values(diagonal)
                    .map(function (pos) { score = getScoreDir(pos, prev); return [{pos: pos[0], depth: 0, score: score/2}, {pos: pos[1], depth: 0, score: score/2}]; }))
                    .filter(p => main_moves.includes(p["pos"])));
                routes.sort(function (a, b) { return b["score"] - a["score"] });
            }
            results = main_moves.map(pos => pos = [pos, routes.filter(a => a["pos"] == pos).reduce((a, b) => a + b["score"] - b["depth"] * 1/dsight, 0)]).sort((a, b) => b[1] - a[1]);
            [best_pos, best_score] = results[0];
        }
        return best_score > 0 ? [best_pos, 1] : [getDirectionXY(...getEnemyXY()), 1];
    }
    function getBestMoves(x = mx, y = my) {
        if (!isValidXY(x, y)) return [];
        avail = getAvailMoves(x, y);
        moves = [
            avail.filter(p => isPaintableXY(x + deltas.x[p], y + deltas.y[p]) && !isFriendXY(x + deltas.x[p], y + deltas.y[p]) && !isBotPos(p) && p != rev[db.mprev]),
            avail.filter(p => isSoftXY(x + deltas.x[p], y + deltas.y[p]) && !isBotPos(p) && p != rev[db.mprev]),
            avail.filter(p => p != rev[db.mprev]),
        ].filter(i => i.length > 0);
        best = moves.length > 0 ? moves[0].filter(m => Object.keys({...dest}).map(pos => isPaintableXY(x + deltas.x[m] + deltas.x[pos], y + deltas.y[m] + deltas.y[pos])).filter(v => v).length > 0) : [];
        return best.length > 0 ? best : moves[0] || avail;
    }
    function getEnemies() {
        var pts = getStats(mc)[1];
        return db.scores.filter(bot => bot[0] != mc && !isFriendCol(bot[0]) && bot[1] > pts && Math.abs(mc - bot[0]) % 3 != 2).sort((a,b) => b[1] - a[1]);
    }
    function getFriends() {
        if (rcurr == 10 && db.friends.length > 0) doNoise(Array(db.friends.length+1).join(" Eep!"));
        return rcurr == 9 ? db.start.filter(b => b[0] != mc && [3, 4].indexOf(calcColor(...b)) > -1 && calcDistanceXY(...db.start.find(s => s[0] == b[0]).slice(1), ...bots.find(s => s[0] == b[0]).slice(1)) <= 2) : db.friends;
    }
    function getScoreDir(pos, prev, sx = mx, sy = my) {
        if (!isValidXY(sx, sy)) return -1;
        let [dx, dy] = [typeof pos == "object" ? pos.reduce((sum, p) => sum + deltas.x[p], 0) : deltas.x[pos], typeof pos == "object" ? pos.reduce((sum, p) => sum + deltas.y[p], 0) : deltas.y[pos]]
        let [score, penalty, x, y] = [calcPaintable(sx, sy) * pboost, 0, sx + dx, sy + dy];
        while (isValidXY(x, y)) {
            if (isBotAround(x, y, pbotdist)) score /= 2;
            else if (isEnemyXY(x, y)) score += pplus * calcColor(getColorXY(x, y), x, y) * pboost - penalty
            else if (isPaintableXY(x, y)  && !isFriendXY(x, y)) score += pplus * calcPaintable(x, y) * pboost - penalty
            else if (isPaintedXY(x, y) || isFriendXY(x, y)) score -= pplus - penalty;
            [x, y, penalty] = [x + dx, y + dy, penalty - pplus/Math.max(calcDistanceXY(x, y), 1)];
        }
        return parseFloat(score.toFixed(2));
    }
    function getStrategyPos(pos = db.mpos) {
        switch(Number(pstrategy)) {
            case 1: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 8 ? dest.left : dest.right) : pos;
            case 2: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 8 ? dest.right : dest.right) : pos;
            case 3: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 10 ? (rcurr % 6 != 0 ? dest.left : dest.right) : dest.right) : pos;
            default: return db.mpos;
        }
    }
    function getScore(uid) { res = 0; grid.forEach(function(x) { res += x.reduce((a,b) => a += b == uid, 0) }); return res; }
    function getStats(uid) { return db.scores.find(a => a[0] == uid); }
    function isColorEdible(color) { return color != getNextColor(color); }
    function isEdge(pos, x = mx, y = my) { return (dest.left && x == gmin) || (dest.right && x == gmax) || (dest.up && y == gmin) || (dest.down && y == gmax); }
    function isEdgeXY(x, y) { return x == gmin || y == gmin || x == gmax || y == gmax; }
    function isEdible(pos, x = mx, y = my) { return isEdibleXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isEdibleXY(x, y) { return isValidXY(x, y) && getColorXY(x, y) != getNextColor(getColorXY(x, y)); }
    function isEnemyXY(x, y) { return isValidXY(x, y) && db.enemies.map(e => getColorXY(x, y) == e[0]).includes(true); }
    function isFirstHalf() { return rcurr < rmax / 2; }
    function isFriendCol(uid) { return uid > 0 && db.friends.map(f => f[0]).indexOf(uid) > -1; }
    function isFriendPos(pos) { return isFriendCol(getColor(pos)); }
    function isFriendXY(x, y) { return isFriendCol(getColorXY(x, y)); }
    function isNearEdge(pos, x = mx, y = my) { return isEdge(pos, x + deltas.x[pos], y + deltas.y[pos]); }
    function isPaintable(pos, x = mx, y = my) { return isPaintableXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isPaintableAround(x = mx, y = my) { return !Object.keys({...dest}).map(a => isPaintableXY(x + deltas.x[a], y + deltas.y[a])).includes(false); };
    function isPaintableXY(x, y, self = false) { return isValidXY(x, y) && (getColorXY(x, y) != mc && getNextColor(getColorXY(x, y)) == mc) || isEnemyXY(x, y); }
    function isPaintedXY(x, y) { return getColorXY(x, y) == mc; }
    function isPrevMove(pos, prev) { return deltas.x[pos] + deltas.x[prev] == deltas.y[pos] + deltas.y[prev]; }
    function isSafeAround(x = mx, y = my) { return !Object.keys({...dest, ...ddest}).map(a => isSafeXY(x + deltas.x[a], y + deltas.y[a])).includes(false); };
    function isSafeXY(x = mx, y = my) { return isValidXY(x, y) && !isBotAround(x, y) && !isEdgeXY(x, y) && getNextColor(getColorXY(x, y)) == mc; }
    function isSoftXY(x = mx, y = my) { return isEdibleXY(x, y) && !isEdgeXY(x, y) && !isBotAround(x, y); }
    function isValidMove(pos, x = mx, y = my) { return isValidXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isValidXY(x, y) { return (gmin <= x && gmin <= y && x <= gmax && y <= gmax); }
    function noBlanks() { return !Object.values(dest).map(function (pos) { return getColor(pos) == blank; }).includes(true); }
    function noEdible() { return !Object.values(dest).map(function (pos) { return isColorEdible(getColor(pos)); }).includes(true); }
    function onInit() { return {mpos: getLongestDir(), mprev: "wait", steps: 1, chased: 0, params: name.split(" ").slice(-1)[0].split(""), start: bots, scores: [], enemies: [], friends: [], bad: getBadBots()}; }
    function turn(pos = db.mpos) { return pos == dest.left ? turnLeft() : (pos == dest.right ? turnRight() : turnRandom()); }
    function turnLeft(pos = db.mpos) { next = { left: dest.down, right: dest.up, up: dest.left, down: dest.right }; return next[pos]; }
    function turnRandom(pos = db.mpos) { return Math.floor(Math.random() * 2) ? turnLeft() : turnRight(); }
    function turnRight(pos = db.mpos) { next = { left: dest.up, right: dest.down, up: dest.right, down: dest.left }; return next[pos]; }
    function isBotAround(bx = mx, by = my, far = 1) { return Object.values(bots).map(function ([c, x, y]) { return db.bad.includes(c) && calcDistanceXY(x, y, bx, by) <= far; }).includes(true); }
    function isBotPos(pos) { return Object.values(bots).map(function ([c, bx, by]) { return c != mc && calcDistanceXY(bx + deltas.x[pos], by + deltas.y[pos]) == 0; }).includes(true); }
    function isChased() { hello = isBotAround(mx, my, 2); if (!hello) db.chased = Math.max(0, --db.chased); return hello && db.chased++ > 5; }
    // Main logic.
    [next_move, db.steps] = !isChased() ? getNextMove() : [getDirectionXY(...getEnemyXY()), db.chased--];
    db = {mpos: next_move, mprev: next_move, steps: --db.steps, chased: db.chased, params: db.params, start: db.start, scores: rcurr % 10 == 0 || rcurr == 1 ? calcScores() : db.scores, enemies: rcurr % 5 == 0 ? getEnemies() : db.enemies, friends: getFriends(), bad: db.bad};
    localStorage.setItem(name, JSON.stringify(db));
    if (!dest[next_move]) doNoise("Eeek!");
    return next_move;
}

```

At start, an ant creates a nest. Once built, it checks whether can sense the other ants. Then it starts a journey to unknown terrain for food searching (edible squares). There are some times when the ant becomes jealous and start attacking opponents. Although it doesn't eats food of another ant from the same family (it happens when it finds another nest at start). On other times, when it senses danger (like being chased), it runs for help (usually to the strongest opponent which can eat the predator).

Imprinted number at the end of the `name` is a blueprint extract from its DNA which makes its unique behaviour. It's responsible for its senses such as focus, sight, movement patterns and so on. Therefore you can clone an ant and alter its blueprint for its unique abilities (it's over million of permutations). So given the laboratory conditions (repeatable trial and error process), you can find the most optimal blueprint for the given environment.

Noise translation:

* `Eep!` \- it successfully communicated with other ants which are close to their nests;

<sup>Note: Number of noises corresponds to the number of ants found.</sup>
* `Arf!` \- depending on the situation:

+ In an ant got scared (e.g. it sensed the danger, because something is chasing it), it's running for help to the strongest opponent.
    + If an ant becomes jealous (can't find food around) or angry for other reason, it receives a burst of energy and running to the strongest opponent (in hope it can eat the predator).

<sup>Note: Number of noises corresponds to the number of opponent (uid) responsible for the situation.</sup>
* `Eeek!` \- something got seriously wrong and ant can't move (should never happen);

Family: Angry Ant, Hungry Ant.

# Answer
> 2 votes
# Kneecapper

```
function(myself, grid, bots, gameInfo) {
    let [myId, myX, myY] = myself;
    let round = gameInfo[0];

    // Find our friend.
    if (round === 1) {
        localStorage.kneecapper_possibleAllies = JSON.stringify(bots.map(bot => bot[0]));
    }
    let possibleAllies = JSON.parse(localStorage.kneecapper_possibleAllies);

    // Players who don't do the identifying dance aren't allies.
    if (1 < round && round <= 5) {
        let previousPositions = JSON.parse(localStorage.kneecapper_previousPositions);
        let expectedDx = [-1, 0, 1, 0];
        let expectedDy = [0, 1, 0, -1];
        let notAllies = [];
        for (let i = 0; i < possibleAllies.length; i++) {
            let j = possibleAllies[i] - 1;
            let dx = bots[j][1] - previousPositions[j][1];
            let dy = bots[j][2] - previousPositions[j][2];
            if (dx === 0 && dy === 0) {
                if (expectedDx === -1 && bots[j][1] !== 0) {
                    notAllies.push(possibleAllies[i]);
                } else if (expectedDx === 1 && bots[j][1] !== grid.length - 1) {
                    notAllies.push(possibleAllies[i]);
                } else if (expectedDy === -1 && bots[j][2] !== 0) {
                    notAllies.push(possibleAllies[i]);
                } else if (expectedDy === 1 && bots[j][2] !== grid.length - 1) {
                    notAllies.push(possibleAllies[i]);
                }
            }
            if (dx !== expectedDx[round % 4] || dy !== expectedDy[round % 4]) {
                notAllies.push(possibleAllies[i]);
            }
        }
        possibleAllies = possibleAllies.filter(id => notAllies.indexOf(id) < 0);
        localStorage.kneecapper_possibleAllies = JSON.stringify(possibleAllies);
    }
    localStorage.kneecapper_previousPositions = JSON.stringify(bots);

    let partner = possibleAllies[0];

    // Figure out who's doing well.
    let targets = bots.map(bot => bot[0]).filter(id => (id !== myId) && (id !== partner) && (Math.abs(id - myId) % 3 !== 2));
    let flatGrid = [].concat.apply([], grid);
    targets = targets.sort((a, b) => flatGrid.reduce((n, val) => n + (val === a) - (val === b), 0));

    let targetX, targetY;
    let targetScore = 0;
    for (let x = 0; x < grid.length; x++) {
        for (let y = 0; y < grid.length; y++) {
            let dist = Math.abs(x - myX) + Math.abs(y - myY);
            let scariness = targets.indexOf(grid[x][y]) + 1;
            if (scariness === 0) { continue; }

            // Find a successful opponent who's not too far away.
            let score = scariness ** 1.5 / (dist + 1);
            if (score > targetScore) {
                targetX = x;
                targetY = y;
                targetScore = score;
            }
        }
    }

    // Move toward the target.
    if (targetX < myX) { return "left"; }
    if (targetX > myX) { return "right"; }
    if (targetY < myY) { return "up"; }
    if (targetY > myY) { return "down"; }
    return "wait";
}

```

There are two ways to be the best: build yourself up, or tear others down. This takes the latter approach. This finds nearby squares owned by the higher-scoring players and removes them.

# Answer
> 2 votes
# Boxer

```
function(myself, grid, bots, gameInfo) {
    let val = gameInfo[0] % 16;
    if(val < 3){
        return "right";
    }else if(val < 6){
        return "up";
    }else if(val < 9){
        return "left";
    }else if(val < 12){
        return "down";
    }else if(val < 14){
        return ["up","down","left","right"][Math.random() *4 |0];
    }else{
        let xdist = myself[1];
        let ydist = myself[2];
        let xfardist = grid.length - 1 - myself[1];
        let yfardist = grid.length - 1 - myself[2];
        if(gameInfo[0] % 400 < 200){
            if (xdist < ydist && xdist < xfardist && xdist < yfardist){
                return "right";
            }else if (ydist < xfardist && ydist < yfardist){
                return "down";
            }else if (xfardist < yfardist){
                return "left";
            }else{
                return "up";
            }
        }else{
            if (xdist > ydist && xdist > xfardist && xdist > yfardist){
                return "right";
            }else if (ydist > xfardist && ydist > yfardist){
                return "up";
            }else if (xfardist > yfardist){
                return "left";
            }else{
                return "down";
            }
        }
    }
}

```

Pretty straightforward; moves in a small 4x4 box repeatedly, and after every loop it takes a random step and then moves a few steps closer or further from the center. Pretty vulnerable to hunters, since it moves in a small zone. Main goal is just to control one area.

# Answer
> 2 votes
## Fuzzy Guy

```
function(myself, grid, bots, gameInfo) {
    var i,j,x,y = 0;
    this.answerToLifeTheUniverseAndEverything = 42;
    this.round = gameInfo[0];
    this.coloringStruggle = [];
    this.myColor = myself[0];
    this.botCount = bots.length;
    this.sizeOfGrid = grid.length;
    this.storageName = 'm53kp1of6igcnpsq';
    this.storageName2 = 'ji38df8djsdf8zf0a';
    this.distances = {up: 0, right: 0, down: 0, left: 0};
    this.foodSmell = {up: 0, right: 0, down: 0, left: 0};
    this.botSmell = {up: 0, right: 0, down: 0, left: 0};
    this.botPredictedSmell = {up: 0, right: 0, down: 0, left: 0};
    this.directionPoints = {up: 0, right: 0, down: 0, left: 0};

    this.blockedMoves = function() {
        var backwards = 'wait', prevDirection, blocked = [];
        if(myself[1] == 0) {
            blocked.push('left');
        }
        if(myself[2] == 0) {
            blocked.push('up');
        }
        if(myself[1] == this.sizeOfGrid - 1) {
            blocked.push('right');
        }
        if(myself[2] == this.sizeOfGrid - 1) {
            blocked.push('down');
        }

        if (this.round > 1) {
            prevDirection = JSON.parse(localStorage.getItem(this.storageName2));
            backwards = (prevDirection == 'up' ? 'down' : backwards);
            backwards = (prevDirection == 'down' ? 'up' : backwards);
            backwards = (prevDirection == 'left' ? 'right' : backwards);
            backwards = (prevDirection == 'right' ? 'left' : backwards);
            blocked.push(backwards);
        }

        return blocked;
    }

    this.getDistance = function(x1,y1) {
        return [Math.abs(myself[1]-x1), Math.abs(myself[2]-y1)];
    }

    this.finddeliciousDirection = function() {
        for (x = 0; x < this.sizeOfGrid; x++) {
            for (y = 0; y < this.sizeOfGrid; y++) {
                if (y < myself[2]) {
                    this.foodSmell.up+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
                if (y > myself[2]) {
                    this.foodSmell.down+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
                if (x < myself[1]) {
                    this.foodSmell.left+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
                if (x > myself[1]) {
                    this.foodSmell.right+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
            }
        }
    }

    this.predictFuture = function(x0,y0,x1,y1) {
        var xMovement = x1-x0;
        var yMovement = y1-y0;
        var xAfter2Turns = x1 + xMovement * 2;
        var yAfter2Turns = y1 + yMovement * 2;
        var hitsWall = [1, 1];

        if (xMovement == 1) {
            hitsWall = [2, 1]
        } else if (xMovement == -1) {
            hitsWall = [0, 1]
        } else if (yMovement == 1) {
            hitsWall = [1, 2]
        } else if (yMovement == -1) {
            hitsWall = [1, 0]
        } else {
            hitsWall = [1, 1]
        }

        if (xAfter2Turns < 0) {
            xAfter2Turns = 0;
        } else if (xAfter2Turns >= this.sizeOfGrid) {
            xAfter2Turns = this.sizeOfGrid -1;
        }

        if (yAfter2Turns < 0) {
            yAfter2Turns = 0;
        } else if (yAfter2Turns >= this.sizeOfGrid) {
            yAfter2Turns = this.sizeOfGrid -1;
        }

        return [xAfter2Turns, yAfter2Turns, hitsWall];
    }

    this.findCloseBots = function() {
        var prevPositions;
        var currentBot;
        var future;
        if (this.round > 1) {
            prevPositions = JSON.parse(localStorage.getItem(this.storageName));
        }

        for (i = 0; i < bots.length; i++) {
            if (bots[i][2] < myself[2]) {
                this.botSmell.up+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }
            if (bots[i][2] > myself[2]) {
                this.botSmell.down+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }
            if (bots[i][1] < myself[1]) {
                this.botSmell.left+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }
            if (bots[i][1] > myself[1]) {
                this.botSmell.right+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }

            if (this.round > 1) {
                currentBot = prevPositions.find(function(element) {
                    return element[0] == bots[i][0];
                });

                if (currentBot[0] != this.myColor) {
                    future = this.predictFuture(currentBot[1], currentBot[2], bots[i][1], bots[i][2]);
                    if (future[1] < myself[2]) {
                        this.botPredictedSmell.up+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }
                    if (future[1] > myself[2]) {
                        this.botPredictedSmell.down+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }
                    if (future[0] < myself[1]) {
                        this.botPredictedSmell.left+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }
                    if (future[0] > myself[1]) {
                        this.botPredictedSmell.right+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }

                    if (future[2][0] == 0) {
                        this.botPredictedSmell.left+=0.314159;
                    }
                    if (future[2][0] == 2) {
                        this.botPredictedSmell.right+=0.314159;
                    }
                    if (future[2][1] == 0) {
                        this.botPredictedSmell.up+=0.314159;
                    }
                    if (future[2][1] == 2) {
                        this.botPredictedSmell.down+=0.314159;
                    }
                }
            }
        }

        localStorage.setItem(this.storageName, JSON.stringify(bots));
    }

    this.calculateColoringStruggle = function() {
        for (x = 0; x < this.sizeOfGrid; x++) {
            var yAxis = [];
            for (y = 0; y < this.sizeOfGrid; y++) {
                if (this.myColor == grid[x][y]) {
                    yAxis[y] = 2;
                } else if (grid[x][y] == 0) {
                    yAxis[y] = 0;
                }
                else {
                    yAxis[y] = [0, 1, 2][Math.abs(this.myColor - grid[x][y])%3];
                }
            }
            this.coloringStruggle.push(yAxis);
        }
    }

    this.getEmptySlotsInDirection = function() {

        for (x = (myself[1] + 1); x < this.sizeOfGrid; x++) {
            if (grid[x][myself[2]] == 0) {
                this.distances.right = (x-myself[1]) * 1.23456789;
            } else {
                if (x-myself[1]-1 == 0) {
                    this.distances.right = 0;
                }
                break;
            }
        }
        for (y = (myself[2] + 1); y < this.sizeOfGrid; y++) {
            if (grid[myself[1]][y] == 0) {
                this.distances.down = (y-myself[2]) * 1.23456789;
            } else {
                if (y-myself[2]-1 == 0) {
                    this.distances.down = 0;
                }
                break;
            }
        }
        for (x = (myself[1] - 1); x > -1; x--) {
            if (grid[x][myself[2]] == 0) {
                this.distances.left = (myself[1]-x) * 1.23456789;
            } else {
                if (myself[1]-x-1 == 0) {
                    this.distances.left = 0;
                }
                break;
            }
        }
        for (y = (myself[2] - 1); y > -1; y--) {
            if (grid[myself[1]][y] == 0) {
                this.distances.up = (myself[2]-y) * 1.23456789;
            } else {
                if (myself[2]-y-1 == 0) {
                    this.distances.up = 0;
                }
                break;
            }
        }
    }
    this.getBestDistance = function() {
        var max = -999, maxDir = 'up';
        for (var property in this.distances) {
            if (this.distances.hasOwnProperty(property)) {
                this.directionPoints[property] = (this.distances[property] + this.foodSmell[property] - this.botSmell[property] - this.botPredictedSmell[property]);
                if (this.directionPoints[property] > max && this.blockedMoves().indexOf(property) == -1) {
                    max = this.directionPoints[property];
                    maxDir = property;
                }
            }
        }

        return maxDir;
    };

    this.findCloseBots();
    this.calculateColoringStruggle();
    this.getEmptySlotsInDirection();
    this.finddeliciousDirection();

    var answer = this.getBestDistance();
    localStorage.setItem(this.storageName2, JSON.stringify(answer));

    return(answer);
}

```

This is my first participation here, but I think it's not last as I really like this KoTH idea Basically what my bot does is:

1. Calculates how much food and how far is in each direction
2. Calculates how many and how close are bots to each direction
3. Calculates some more "very useful" data
4. *Update* \- Doesn't go to previous cell in next turn

In the end it uses fuzzy logic to weight each direction and picks one with best value

I think I'll create a new bot from scratch later as this one was meant to kinda get me rolling

# Answer
> 2 votes
# Jack

Starting of with NearSightedGrid's logic (I wanted to created such a bot), I have come up with the following strategy:

* Will move from the right bottom to the left top (i.e. first the bottom row by going left, then one above by going right, etc.). Thus, starting somewhere right bottom gives it an advantage.
* It will though never do the opposite move of the last move (so that it won't get stuck, which it did in earlier versions).
* It will also not prefer to move to a location on which it needs to stay another round, depending on the current color of that location. If possible, such a move is not executed. But it is done if the alternative is moving random.
* If no move can be found from above, it will move random. Once it moves random and tries to move random again right away, it will move in the same direction so that it doesn't get stuck in a big field of a color which it cannot defeat. It will though move random again if it will go outside the boundary.

Note: not a developer so my code is awful. But strategically it is not that bad.

```
function (myself, grid, bots, gameInfo) {
    var col = myself[0];
    var myX = myself[1];
    var myY = myself[2];

    var notPreferred = [];

    var move = "wait";
    if(grid[myX][myY] != col){
        var go = checkMove(move, grid[myX][myY]);
        if(go) {
            if(go == "notPreferred") {
                //notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "left";
    if(myX > 0 && grid[myX-1][myY] != col){
        var go = checkMove(move, grid[myX-1][myY]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "right";
    if(myX < grid.length-1 && grid[myX+1][myY] != col){
        var go = checkMove(move, grid[myX+1][myY]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "up";
    if(myY > 0 && grid[myX][myY-1] != col){
        var go = checkMove(move, grid[myX][myY-1]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "down";
    if(myY < grid[0].length && grid[myX][myY+1] != col){
        var go = checkMove(move, grid[myX][myY+1]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    if(notPreferred[0]) {
        nextMove(notPreferred[0], "notPreferred");
        return notPreferred[0];
    }

    var random = randomMove();
    nextMove(random, "random");
    return random;

    function checkMove(move, currentColor) {
        var go = false;
        if(currentColor === 0) {
            go = true;
        } else {
            var z = [col, 0, currentColor][Math.abs(col - currentColor)%3]
            go = z == 0 ? "notPreferred" : z != currentColor;
        }

        if(go) {
            if(localStorage.jacksNextMoveShouldNotBe && localStorage.jacksNextMoveShouldNotBe == move) {
                return false;
            }
        }
        return go;
    }

    function randomMove() {
        if(localStorage.jacksPreviousMoveWasRandom) {
            var repeatMove = localStorage.jacksPreviousMoveWasRandom;
            if(repeatMove == "left" && myX > 0 || repeatMove == "right" && myX < grid.length-1 || repeatMove == "up" && myY > 0 || repeatMove == "down" && myY < grid.length-1){
                return repeatMove;
            }
        }

        var random = ["up","down","left","right"][Math.random() *4|0];
        localStorage.jacksPreviousMoveWasRandom = random;
        return random;
    }

    function nextMove(move, message) {
        var oppositeMove = "wait";
        if(move == "left") {
            oppositeMove = "right";
        } else if(move == "right") {
            oppositeMove = "left";
        } else if(move == "up") {
            oppositeMove = "down";
        } else if(move == "down") {
            oppositeMove = "up";
        }
        localStorage.jacksNextMoveShouldNotBe = oppositeMove;
        if(message != "random") {
            localStorage.jacksPreviousMoveWasRandom = "";
        }
    }
}

```

# Answer
> 2 votes
# Eraser

This bot tries to erase as much of the board as possible. To avoid getting stuck in an infinite loop, it ignores cells that are very close to the bot that created them.

Priorities:

1. If the current cell is erasable, wait
2. Move ~~counter~~-clockwise around the edge of an erasable area (changed to avoid getting stuck erasing borderline)
3. Move towards the nearest erasable cell
4. Move left

```
function([id,x,y],grid,bots){
    function manhattan_search(x,y,board_size,callback){
        var dest_x,dest_y;
        try{
            for(var dist=1;dist<grid.length*2;dist++){
                check(0, dist); //x+
                check(0,-dist); //x-
                check( dist,0); //y+
                check(-dist,0); //y-
                for(var i=1;i<dist;i++){
                    check( i,  dist-i ); //++
                    check(-i,  dist-i ); //-+
                    check( i,-(dist-i)); //+-
                    check(-i,-(dist-i)); //--
                }
            }
            return undefined;
        }catch(e){
            //console.log(e);
            return [dest_x,dest_y];
        }
        function check(vx,vy){
            dest_x=x+vx;
            dest_y=y+vy;
            if(callback(dest_x,dest_y))
                throw undefined;
        }
    }
    function can_erase(x,y){
        if(grid[x]!==undefined && grid[x][y]!==undefined && grid[x][y]!==0 && Math.abs(id-grid[x][y])%3===1){
            for(var i=0;i<bots.length;i++)
                if(bots[i][0]===grid[x][y])
                    break;
            if(bots[i])
                return Math.abs(x-bots[i][1])+Math.abs(y-bots[i][2])>3;
        }
    }

    if(can_erase(x,y))
        return "wait";
    var name=["up","right","down","left"];
    var dx=[0,1,0,-1],dy=[-1,0,1,0];
    dir=this.last_dir-1&3;
    for(var i=1;i<=4;i++){
        if(can_erase(x+dx[dir],y+dy[dir]))
            return name[this.last_dir=dir];
        dir=dir+1&3;
    }
    var dest=manhattan_search(x,y,grid.length,can_erase);
    if(dest){
        return name[this.last_dir=[
            [0,0,1],
            [3,3,1],
            [3,2,2]
        ][Math.sign(dest[1]-y)+1][Math.sign(dest[0]-x)+1]];
    }
    return "left";
}

```

# Answer
> 2 votes
# John

John is Jims big brother. As Jim often gets in trouble, he is always on lookout for him and will help when there is the need. John might be a bit clumsy, but when the need arrives, he will do whatever is needed to protect him.

```
function([mc, mx, my], grid, bots, [round, maxRound]) {const ID = 0;
  var S = this;
  const botAm = 3;
  function log(...args) {
    //if (round > 1) console.log(ID+" "+args[0], ...args.slice(1));
    return true;
  }
  if (round == 1) {
    var all = new Array(bots.length).fill().map((_,i)=>i+1);
    S.fs = new Array(botAm).fill().map(c =>
      [all.slice(), all.slice(), all.slice(), all.slice()]
    );
    S.doneSetup = false;
    var center = grid.length/2;
    // UL=0; DL=1; DR=2; UR=3
    S.dir = mx<center? (my<center? 0 : 1) : (my<center? 3 : 2);
    S.job = 0;
    S.setupFail = S.finished = false;
    S.tbotc = undefined;
    S.botAm = bots.length;
    S.botEvilness = new Array(bots.length+1).fill(0);
    S.keys = [[1,1,0,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,0,0],
              [0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,0,0,0,1,1],
              [1,0,0,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,0,1,1,1,1,0,1,1,1,0]];
    /*if (ID == 2) */{
      S.chased = 0;
      S.ignore = [];
      S.badMoves = 0;
      S.pastMoves = new Array(100).fill("-1;0");
      S.timer = 0;
      S.jimFn = function([mc, mx, my], grid, bots, [round, maxRound]) { // ---------- BEGIN JIM ---------- \\
        var output;
        var allowRetracing = false;

        var checkSize = 3;
        var eatSize = 5;
        var myScore;
        var scoreboard;

        if (grid[mx][my] == 0 && !bots.some(([col, bx, by])=> col != mc && bx==mx && by==my)) return "wait"; // collect those sweet points

        // rescore every now and then
        if (S.timer > 200) rescore();

        S.pastMoves.push(mx+";"+my);
        S.pastMoves.shift();

        var orth = [[-1,0],[0,-1],[1,0],[0,1]];
        if (S.atTarget
        || S.targetX === undefined || S.targetY === undefined
        || S.targetX === mx && S.targetY === my
        || orth.map(([x,y])=>[mx+x,my+y]).filter(c=>get(c)==0 && inbounds(c)).length > 2) {

          S.atTarget = true;
          var neighbors = orth
            .map(([x,y]) => [x+mx, y+my])
            .filter(inbounds)
            .filter(([x,y]) => !bots.some(([bid, bx, by]) => bx==x && by==y))
            .map(c=>[c,get(c)]);

          let test = (neighbors, f, msg) => {
            return bestOf(neighbors.filter(f).map(c=>c[0])) && log(msg);
          }

          if (test(neighbors, ([,c]) => c===0, "good")) return output;
          if (test(neighbors, ([,c]) => overMap(c, 1) && S.BCs,  "sad")) return output;

          S.atTarget = false;
          S.targetX = S.targetY = undefined;
          let bestScore = 7;
          let bfscore = 0;

          for (let dist = 4; dist < 8; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds([cx, cy]) && grid[cx][cy] === 0  ) {
                  let score = scoreOf(cx, cy, 1, false);
                  if(score>bfscore)bfscore=score;
                  if (score > bestScore) {
                    bestScore = score;
                    S.targetX = cx;
                    S.targetY = cy;
                  }
                }
              }
            }
          }
          if (S.targetX) {
            log("short goto", S.targetX, S.targetY,"(rel",S.targetX-mx, S.targetY-my,") score", bestScore);
            return to([S.targetX, S.targetY]);
          } else log("long goto",bfscore);

          rescore();
          return to([S.targetX, S.targetY]);
        } else log("going to target", S.targetX, S.targetY);

        return to([S.targetX, S.targetY]);

        function myScore() {
          if (!myScore) calculateScoreboard();
          return myScore;
        }
        function calculateScoreboard() {
          scoreboard = grid.map(column=> {
            var arr = new Int16Array(grid.length);
            column.forEach((c, x) => (
              myScore+= c==mc,
              arr[x] = overMap(c, 1, 0, 0, 0, 5)
            ));
            return arr;
          });
          for (let [bc, bx, by] of bots) if (bc != mc) {
            scoreboard[bx][by] = -100;
            if (inbounds([bx-2, by])) scoreboard[bx-2][by] = -50;
            if (inbounds([bx+2, by])) scoreboard[bx+2][by] = -50;
            if (inbounds([bx, by-2])) scoreboard[bx][by-2] = -50;
            if (inbounds([bx, by+2])) scoreboard[bx][by+2] = -50;
          }
        }
        function scoreOf (x, y, size, includeEnemies) {
          if (!scoreboard) calculateScoreboard();
          let score = 0;
          for (let dx = -size; dx <= size; dx++) {
            let cx = dx + x;
            if (cx < 1 || cx >= grid.length-1) continue;
            for (let dy = -size; dy <= size; dy++) {
              let cy = dy + y;
              if (cy < 1 || cy >= grid.length-1) continue;
              let cs = scoreboard[cx][cy];
              if (cs > 0 || includeEnemies) score+= cs;
            }
          }
          return score;
        }
        function rescore() { // heatmap of best scoring places
          //log(JSON.stringify(scoreboard));
          S.bestScore = -Infinity;
          var blur = grid.map((column, x)=>column.map((c, y) => {
            let score = scoreOf(x, y, checkSize, true);
            if (score > S.bestScore) {
              S.bestScore = score;
              S.targetX = x;
              S.targetY = y;
            }
            return score;
          }));
          S.atTarget = false;
          S.timer = 0;
          S.bestScore = scoreOf(S.targetX, S.targetY, eatSize);
          S.badMoves = 0;
          // log("scored to", S.targetX, S.targetY, S.bestScore);
        }
        function over(col) { // 1 if overrides happen, -1 if overrides don't happen, 0 if override = 0
          let res = Math.abs(mc-col) % 3;
          return res==1? 0 : res==0? 1 : -1;
        }
        function overMap(col, best = 0, good = 0, bad = 0, mine = 0, zero = 0) { // best if overrides happen, bad if overrides don't happen, good if override = 0
          let res = Math.abs(mc-col) % 3;
          return col == 0? zero : col == mc? mine : res==1? good : res==0? best : bad;
        }
        function iwin   (col) { return over(col) == 1; }
        function zeroes (col) { return over(col) == 0; }
        function to([x, y]) {
          //debugger
          var LR = x > mx? [mx+1, my] : x < mx? [mx-1, my] : null;
          var UD = y > my? [mx, my+1] : y < my? [mx, my-1] : null;
          if (LR && UD) {
            var LRScore = overMap(LR, 1, 0, 0, 0, 3);
            var UDScore = overMap(UD, 1, 0, 0, 0, 3);
            if (LRScore == UDScore) return toPos([LR, UD][Math.random()>.5? 1 : 0])
            else if (LRScore > UDScore) return toPos(LR);
            else return toPos(UD);
          } else return toPos(LR || UD || [x, y]);
        }
        function toPos([x,y]) {
            if (x > mx) return "right";
            if (x < mx) return "left";
            if (y < my) return "up";
            if (y > my) return "down";
            return 'wait';
        }
        function inbounds([x, y]) {
          // if (x<grid.length && y<grid.length && x>=0 && y>=0) return true;
          if (x<grid.length-1 && y<grid.length-1 && x>=1 && y>=1) return true;
          return false;
        }
        function get([x,y]) {
          if (inbounds([x, y])) return grid[x][y];
          return 0;
        }
        function bestOf (arr) {
          if (arr.length == 0) return false;
          var bestScore = -Infinity;
          var bestPos;
          for (var [x, y] of arr) {
            let score = 0;
            for (var [bcol, bx, by] of bots) {
              let dist = Math.sqrt((x-bx)**2 + (y-by)**2);
              let res = over(bcol);
              let power = res==0? 1 : res==1? 0.4 : 1.4;
              score+= power * dist;
            }
            score-= Math.sqrt((x-S.targetX)**2 + (y-S.targetY)**2);
            if (S.pastMoves.includes(x+";"+y)) score-= 1000000;

            if (score > bestScore) {
              bestScore = score;
              bestPos = [x,y];
            }
          }
          if (bestScore < -500000) {
            if (allowRetracing) log("RETRACING");
            else return false;
          }
          output = to(bestPos);
          return true;
        }
      } // ---------- END JIM ---------- \\
    }
  }
  const dirs = ['up','left','down','right'];

  if (!S.doneSetup && round < 37) { // ---------- HANDSHAKE ---------- \\
    let finished = 0;
    if (round != 1) {
      for (let id = 0; id < botAm; id++) {
        let f = S.fs[id];
        let remaining = f.map(c=>c.length).reduce((a,b)=>a+b);
        if (remaining == 1) {
          finished++;
          continue;
        }
        if (remaining == 0) {
          // mourn the loss of a good friend
          finished++;
          continue;
        }
        for (let dir = 0; dir < 4; dir++) {
          let possible = f[dir];

          for (let i = possible.length-1; i >= 0; i--) {
            let bc = possible[i];
            let curr =       bots.find(c=>c[0]==bc);
            let prev = S.pastBots.find(c=>c[0]==bc);
            if (!curr || !prev) {
              possible.splice(i,1);
              continue;
            }
            let dx = curr[1]-prev[1];
            let dy = curr[2]-prev[2];
            let move;
            if (dy == 0) {
              if (dx == 1) move = 'right';
              else         move =  'left';
            } else {
              if (dy == 1) move =  'down';
              else         move =    'up';
            }
            let omove = rotate(move, dir);
            let expected = ['down','right'][S.keys[id][round-1]];
            // if (id == 0 && dir == S.dir) log();
            if (omove != expected) possible.splice(i,1);
          }
        }
      }
    }
    S.pastBots = bots;
    if (finished == botAm) {
      S.doneSetup = true;
      S.pastBots = undefined;
      S.BCs = new Array(botAm).fill().map((_,i) => (S.fs[i].find(c=>c.length > 0) || [-1])[0]); // AKA idtoc
      S.fighters = S.BCs.slice(0,2);
      S.ctoid = {[S.BCs[0]]:0, [S.BCs[1]]:1, [S.BCs[2]]:2};
      log("identified", S.BCs);
      if (ID == 2) {
        log("can beat", bots.filter(c=>S.fighters.filter(b=>Math.abs(b-c[0])%3 != 2).length > 0).map(c=>c[3]));
      }
    } else {
      // log(ID,S.fs);
      return rotate(['down','right'][S.keys[ID][round]], S.dir);
    }
  }
  if (!S.doneSetup) { // HANDSHAKE FAILED
    S.setupFail = true;
    S.BCs=[];
    S.fighters = [];
    S.ctoid = {};
  }

  if (S.pastGrid) for (let [bc, bx, by] of bots) { // calculate bot evilness
    let prev = S.pastGrid[bx][by];
    let fID = S.BCs.indexOf(prev);
    if (fID === 2) S.botEvilness[bc]+= 10;
    else if (fID !== -1) S.botEvilness[bc]+= 5;
    else {
      let over = Math.abs(bc - prev) % 3;
      if (over === 0) S.botEvilness[bc]+= 1;
      else if (over === 1) S.botEvilness[bc]+= 2;
    }

  }

  S.pastGrid = grid;

  if (ID == 2) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]);

  if (S.setupFail || !bots.find(c=>c[0]==S.fighters[1-ID])) return 'wait'; // for my demise
  // TODO yeah no

  if (round < 50 || !bots.find(c=>c[0]==S.BCs[2])) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]); // if Jim's dead or if it's early game, be Jim so others don't win needlessly/scoreboard becomes more clear

  let tbot = bots.find(c=>c[0] == S.tbotc);

  // ---------- NEW TARGET ---------- \\
  let tried;

  // {
  //   let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
  //   for (let column of grid) for (let item of column) scores[item]++;
  //   log("scores", scores.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  //   log("evilness", S.botEvilness.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  // }

  let makeSureImNotStupidAgain = 0;
  while ((!S.tbotc || !tbot) && !S.finished) {
    makeSureImNotStupidAgain++;
    if (makeSureImNotStupidAgain > 100) {
      console.log("dzaima is stupid");
      S.finished = true;
      break;
    }
    if (!tried) tried = S.BCs.slice();
    S.gotoX = S.gotoY = undefined;
    let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
    for (let column of grid) for (let item of column) scores[item]++;
    var bbc, bbs=-Infinity;
    for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && !tried.includes(i)) {
      bbs = scores[i];
      bbc = i;
    }
    S.tbotc = bbc;
    tbot = bots.find(c=>c[0] == bbc);
    if (!tbot) {
      tried.push(bbc);
    } else {
      S.jobs = [0,0];
      let executers = S.fighters.filter(c=>Math.abs(c-bbc)%3 == 1).concat(S.fighters.filter(c=>Math.abs(c-bbc)%3 == 0));
      if (executers.length > 1) {
        S.jobs[S.ctoid[executers.pop()]] = 1;
        S.jobs[S.ctoid[executers.pop()]] = 2;
        //S.jobs.forEach((c,id) => c==0? S.jobs[id]=2 : 0);
        log("targetting", botName(bbc),"jobs",S.jobs);
      } else {
        // cry
        tried.push(bbc);
        S.tbotc = tbot = undefined;
      }
      S.job = S.jobs[ID];
    }
    if (tried.length >= bots.length) {
      // everyone is dead
      S.job = 0;
      S.jobs = new Array(2).fill(0);
      S.finished = true;
      break;
    }
  }

  if (tbot && !S.finished) {
    let [_, tx, ty] = tbot;

    switch (S.job) {
      case 1: // follow
        return to(tx, ty, S.tbotc);
      break;
      case 2: // erase
        let endingClearing = false;
        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          S.gotoX = undefined;
          var ending = [S.tbotc, ...S.fighters.filter(c=>c != mc)].map(c => bots.find(b=>b[0]==c)).filter(I=>I);
          search: for (let dist = 1; dist < grid.length*2+2; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds(cx, cy)) {
                  if (grid[cx][cy] == S.tbotc && ending.every(([_,bx,by]) => (bx-cx)**2 + (by-cy)**2 > Math.random()*10)) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
          if (S.gotoX === undefined) {
            let available = [];
            grid.forEach((column, x) => column.forEach((c, y) => c==S.tbotc? available.push([x,y]) : 0));
            [S.gotoX, S.gotoY] = available[Math.floor(Math.random()*available.length)];
            endingClearing = true;
          }
        }
        return to(S.gotoX, S.gotoY, endingClearing? undefined : S.tbotc);
      break;
      case 0: // exercise

        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          let scores = new Uint32Array(S.botAm+1);
          for (let column of grid) for (let item of column) scores[item]++;
          var bbc, bbs=-Infinity;
          for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && Math.abs(mc-i)%3 == 0 && !S.BCs.includes(i)) {
            bbs = scores[i];
            bbc = i;
          }
          if (bbc) {
            S.gotoX = undefined;
            search: for (let dist = 1; dist < grid.length*2+2; dist++) {
              for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
                for (let i = 0; i < dist; i++) {
                  let cx = dx*i + dsx*dist + mx;
                  let cy = dy*i + dsy*dist + my;
                  if (inbounds(cx, cy) && grid[cx][cy] == bbc) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
        }
        if (S.gotoX !== undefined) return to(S.gotoX, S.gotoY);
        return dirs[Math.floor(Math.random()*4)];
      break;
    }
  }

  function to (x, y, col) {
    if  (x == mx&&y== my) return 'wait';
    let dx =   x    - mx ;
    let dy =      y - my ;
    let ax = Math.abs(dx);
    let ay = Math.abs(dy);
    var          diag;
    if   (     ax==ay   ) {
      if (col&&ax+ ay==2) {
        let i=[[x, my], [mx, y]].findIndex(c=>grid[c[0]][c[1]]==col);
        if (i<0) diag = Math.random()>=.5;
        else     diag =           i  == 0;
      } else     diag = Math.random()>=.5;
    }
    if (ax==ay?  diag :  ax>ay) {
      if (dx>0) return 'right';
      else      return  'left';
    } else {
      if (dy>0) return  'down';
      else      return    'up';
    }
  }

  function rotate (move, dir) {
    if ((move == 'up' || move == 'down') && (dir && dir<3)) {
      if (move == 'up') return 'down';
      else return 'up';
    }
    if ((move == 'left' || move == 'right') && dir>1) {
      if (move == 'left') return 'right';
      else return 'left';
    }
    return move;
  }
  function botName(id) {
    let bot = bots.find(c=>c[0]==id);
    if (!bot) return id.toString();
    return bot[3] + "/" + id;
  }
  function inbounds(x, y) { return x<grid.length && y<grid.length && x>=0 && y>=0 }
}

```

# Answer
> 2 votes
# HeatBot

```
function(myself, grid, bots, gameInfo) {
    [myC, myX, myY] = myself;
    let heatMap = null;
    if (!this.heatMap) {
        heatMap = Array(grid.length).fill().map(() => new Float32Array(grid.length).fill(0));
    } else {
        heatMap = this.heatMap;
    }

    function isValidPosition(x, y) {
        return x >= 0 && y >= 0 && x < grid.length && y < grid.length;
    }

    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid.length; j++) {
            if (grid[i][j] == 0 || (myC != grid[i][j] && Math.abs(myC - grid[i][j]) % 3 == 0)) {
                heatMap[i][j] /= 2;
            }
        }
    }

    for (let i = 0; i < bots.length; i++) {
        heatMap[bots[i][1]][bots[i][2]] += 1;
    }

    let heatDelta = Array(grid.length).fill().map(() => new Float32Array(grid.length).fill(0));

    function transferHeat(x0, y0, x1, y1) {
        if (isValidPosition(x1, y1)) {
            let heat0 = heatMap[x0][y0];
            let heat1 = heatMap[x1][y1];
            if (heat0 > heat1) {
                let dt = heat0 - heat1;
                let q = dt * 0.0003;
                heatDelta[x0][y0] -= q;
                heatDelta[x1][y1] += q;
            }
        }
    }

    for (let steps = 0; steps < 9; steps++) {
        for (let i = 0; i < grid.length; i++) {
            for (let j = 0; j < grid.length; j++) {
                transferHeat(i, j, i - 1, j);
                transferHeat(i, j, i + 1, j);
                transferHeat(i, j, i, j - 1);
                transferHeat(i, j, i, j + 1);
            }
        }

        for (let i = 0; i < grid.length; i++) {
            for (let j = 0; j < grid.length; j++) {
                heatMap[i][j] += heatDelta[i][j];
                heatDelta[i][j] = 0;
            }
        }
    }

    let bestMove = "wait";
    let lowestHeat = Number.MAX_SAFE_INTEGER;
    let allMoves = [["up", myX, myY - 1], ["down", myX, myY + 1], ["left", myX - 1, myY], ["right", myX + 1, myY]];
    for (let i = 0; i < allMoves.length; i++) {
        if (isValidPosition(allMoves[i][1], allMoves[i][2])) {
            let heat = heatMap[allMoves[i][1]][allMoves[i][2]];
            if (heat < lowestHeat) {
                lowestHeat = heat;
                bestMove = allMoves[i][0];
            }
        }
    }

    this.heatMap = heatMap;
    return bestMove;
}

```

Pretends that bots emit heat and tries to go to places that are colder.

# Answer
> 2 votes
### ClaimEverything

```
        function (myself, grid, bots, gameInfo) {
            let my_c = myself[0], my_x = myself[1], my_y = myself[2], size = grid.length, roundnum = gameInfo[0];

            let getDistance = function (x1, y1, x2, y2) {
                return (Math.abs(x1 - x2) + Math.abs(y1 - y2));
            };

            let getColorValue = function (color) {
                if (color === 0) {
                    return my_c;
                }
                return [my_c, 0, color][Math.abs(my_c - color) % 3];
            };

            if (!localStorage.claim) {
                let lastMove = "";
                localStorage.claim = JSON.stringify([lastMove]);
            }
            offsets = JSON.parse(localStorage.claim);
            lastMove = offsets[0];

            let targets = [];
            let distance = 999999;
            let lowestDistance = 999999;
            for (let grid_x = 0; grid_x < size; grid_x++)
            {
                for (let grid_y = 0; grid_y < size; grid_y++)
                {
                    if (grid[grid_x][grid_y] !== my_c && getColorValue(grid[grid_x][grid_y]) === my_c)
                    {
                        distance = getDistance(my_x, my_y, grid_x, grid_y);
                        targets[distance] = [grid_x, grid_y];

                        if (distance < lowestDistance) {
                            lowestDistance = distance;
                        }
                    }
                }
            }
            let target = targets[lowestDistance];

            //Nothing directly paintable available, search for erasable
            if (target === undefined)
            {
                targets = [];
                distance = 999999;
                lowestDistance = 999999;
                for (let grid_x = 0; grid_x < size; grid_x++)
                {
                    for (let grid_y = 0; grid_y < size; grid_y++)
                    {
                        if (grid[grid_x][grid_y] !== my_c && getColorValue(grid[grid_x][grid_y]) !== grid[grid_x][grid_y])
                        {
                            distance = getDistance(my_x, my_y, grid_x, grid_y);
                            targets[distance] = [grid_x, grid_y];

                            if (distance < lowestDistance) {
                                lowestDistance = distance;
                            }
                        }
                    }
                }
            }
            target = targets[lowestDistance];

            let move = "";
            if (target === undefined) {
                move = 'wait';
            } else if (target[0] > my_x) {
                move = 'right';
            } else if (target[0] < my_x) {
                move = 'left';
            } else if (target[1] > my_y) {
                move = 'down';
            } else if (target[1] < my_y) {
                move = 'up';
            } else {
                move = "wait";
            }

            if (move === "wait" && lastMove === "wait") {
                move = "left";
            }

            localStorage.claim = JSON.stringify([move]);

            return move;
        }

```

# Answer
> 2 votes
# Feudal Noble

```
function (me, board, painters, info) {
    let meX = me[1], meY = me[2], round = info[0], size = board.length, sectionSize = Math.ceil(size / 3), storage, storageKey = 'jijdfoadofsdfasz', section;

    if (round === 1 || typeof this[storageKey] === 'undefined') {
        let bounds = [
            [0, 0, sectionSize - 1, sectionSize - 1],
            [sectionSize, 0, (sectionSize * 2) - 1, sectionSize - 1],
            [sectionSize * 2, 0, size - 1, sectionSize - 1],
            [sectionSize * 2, sectionSize, size - 1, (sectionSize * 2) - 1],
            [sectionSize * 2, sectionSize * 2, size - 1, size - 1],
            [sectionSize, sectionSize * 2, (sectionSize * 2) - 1, size - 1],
            [0, sectionSize * 2, sectionSize - 1, size - 1],
            [0, sectionSize, sectionSize - 1, (sectionSize * 2) - 1],
        ];
        section = bounds[(sectionSize + painters[0][1]) % 8];
        storage = this[storageKey] = {section: section, mode: 0, move: 1};
    } else {
        storage = this[storageKey];
        section = storage.section;
    }

    let isInSection = function (x, y, section) {
        return (x >= section[0] && y >= section[1] && x <= section[2] && y <= section[3]);
    };

    if (isInSection(meX, meY, section)) {
        let mode = storage.mode, move = storage.move, nextY, nextX;

        if (mode === 0) {
            if (meX <= section[0]) mode = 1;
            else if (meY <= section[1]) mode = 2;
            else if (meX >= section[2]) mode = 3;
            else if (meY >= section[3]) mode = 4;
            storage.mode = mode;
        }

        if (mode === 1) {
            nextY = meY + move;
            if (nextY < section[1] || nextY > section[3]) {
                nextX = meX + 1;
                if (nextX > section[2]) {
                    storage.mode = (nextY < section[1] ? 2 : 4);
                    storage.move = -1;
                    return 'left';
                } else {
                    storage.move *= -1;
                    return 'right';
                }
            } else {
                return (move > 0 ? 'down' : 'up');
            }
        } else if (mode === 2) {
            nextX = meX + move;
            if (nextX < section[0] || nextX > section[2]) {
                nextY = meY + 1;
                if (nextY > section[3]) {
                    storage.mode = (nextX < section[0] ? 1 : 3);
                    storage.move = -1;
                    return 'up';
                } else {
                    storage.move *= -1;
                    return 'down';
                }
            } else {
                return (move > 0 ? 'right' : 'left');
            }
        } else if (mode === 3) {
            nextY = meY + move;
            if (nextY < section[1] || nextY > section[3]) {
                nextX = meX - 1;
                if (nextX < section[0]) {
                    storage.mode = (nextY < section[1] ? 2 : 4);
                    storage.move = 1;
                    return 'right';
                } else {
                    storage.move *= -1;
                    return 'left';
                }
            } else {
                return (move > 0 ? 'down' : 'up');
            }
        } else if (mode === 4) {
            nextX = meX + move;
            if (nextX < section[0] || nextX > section[2]) {
                nextY = meY - 1;
                if (nextY < section[1]) {
                    storage.mode = (nextX < section[0] ? 1 : 3);
                    storage.move = 1;
                    return 'down';
                } else {
                    storage.move *= -1;
                    return 'up';
                }
            } else {
                return (move > 0 ? 'right' : 'left');
            }
        }

        return 'wait';
    } else {
        let dX = ((section[0] + section[2]) / 2) - meX, dY = ((section[1] + section[3]) / 2) - meY;
        if (Math.abs(dX) > Math.abs(dY)) return (dX < 0 ? 'left' : 'right');
        else return (dY < 0 ? 'up' : 'down');
    }
}

```

This feudal noble claims 1/9 of the map as his own palace, moves to it as fast as possible and starts painting it the way he likes it. Since there are no peasants on this map to do the painting job, the poor noble has to do all the paintings himself. If he gets enough time, he will go back and re-paint previously painted spots to make sure his palace stays beautiful. And don't worry, this time he won't be killed or eaten by anyone on purpose!

# Answer
> 2 votes
# Muncher

```
function(myself, grid, bots, gameInfo) {
    const W = grid.length, H = grid[0].length;
    const rounds_left = gameInfo[1] - gameInfo[0];
    const directions = [[0, -1], [1, 0], [0, 1], [-1, 0]];

    function rank_square([x, y]) {
        if (grid[x][y] == myself[0]) return 3;
        if (grid[x][y] == 0) return 1;
        var value = Math.abs(grid[x][y] - myself[0]) % 3;
        if (value) value += 1;
        return value;
    }

    function select_long_paths() {
        const ranked = directions.map(to_coords).filter(legal).map((coords)=>{
            return calculate_min_score(4, [coords]);
        });
        const min = Math.min(...ranked);
        const result = directions.filter((dir, index)=>{return ranked[index] == min;});
        return result;
    }

    function new_coords([x, y], path) {
        const last_coords = path[path.length - 1];
        return [x + last_coords[0], y + last_coords[1]];
    }

    function calculate_min_score(num_steps, path_so_far) {
        if (!num_steps) return 0;
            var scores = directions.map((dir)=>{
            return new_coords(dir, path_so_far);
        }).filter(legal).filter((coords)=>{
            var i;
            for (i = 0; i < path_so_far.length; i++) {
                if (path_so_far[i] == coords) return false;
            }
            return true;
        }).map((coords)=>{
            var new_path = path_so_far.slice();
            new_path.push(coords);
            return rank_square(coords) + calculate_min_score(num_steps - 1, new_path);
        });
        return Math.min(...scores);
    }

    function to_coords([x, y]) {
        return [x + myself[1], y + myself[2]];
    }

    function legal([x, y]) {
        return 0 <= x && x < W && 0 <= y && y < H;
    }

    function filter_by_strength(dirs) {
        const ranked = dirs.map(to_coords).filter(legal).map(rank_square);
        const min = Math.min(...ranked);
        const result = dirs.filter((dir, index)=>{return ranked[index] == min;});
        return result;
    }

    function convert([x, y]) {
        x += myself[1];
        y += myself[2];

        if (x > myself[1]) return "right";
        if (x < myself[1]) return "left";
        if (y < myself[2]) return "up";
        return "down";
    }

    const options = select_long_paths();
    const choices = filter_by_strength(options);

    return convert(choices[Math.random() * choices.length |0]);
}

```

# Answer
> 2 votes
# Drone-B075

```
function(myData, gridData, botData, gameInfoData) {
  function customSetup(fThis) {
    fThis.botUID = 1;
    fThis.swarm = new Array(3);
    fThis.matchedSize = 0;
    bots.forEach(b => { b.failedSignal = 0; b.trespass = 0; b.desecrate = 0; });
    delete fThis.connected;
    delete fThis.target;
    delete fThis.chaser;
    delete fThis.cleaners;
    delete fThis.roamers;
  }

  let XY = this.xyClass;
  let Bot = this.botClass;
  let Cell = this.cellClass;

  function at(pos, usedGrid = grid) { // NEVER EVER THINK ABOUT PUTTING THIS ON THE GRID ITSELF
    return pos.withinBounds() ? usedGrid[pos.toIndex()] : new Cell(null);
  }

  if (gameInfoData[0] === 1) {
    XY = this.xyClass = (class XY {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }

      static fromIndex(index) {
        return new XY(Math.floor(index / gridSize), index % gridSize);
      }
      toIndex() {
        return this.x * gridSize + this.y;
      }

      add(other) {
        return new XY(this.x + other.x, this.y + other.y);
      }
      sub(other) {
        return new XY(this.x - other.x, this.y - other.y);
      }
      div(value) {
        return new XY(Math.round(this.x / v), Math.round(this.y / v));
      }
      mul(value) {
        return new XY(Math.round(this.x * m), Math.round(this.y * m));
      }
      equals(other) {
        return this.x === other.x && this.y === other.y;
      }

      distance(other) {
        return Math.abs(other.x - this.x) + Math.abs(other.y - this.y);
      }
      chebyshevDistance(other) {
        return Math.max(Math.abs(other.x - this.x), Math.abs(other.y - this.y));
      }

      withinBounds() {
        return this.x >= 0 && this.x < gridSize && this.y >= 0 && this.y < gridSize;
      }

      getNeighbors() {
        return neighbors.map(p => this.add(p));
      }
      getRealNeighbors() {
        return this.getNeighbors().filter(p => p.withinBounds());
      }
    });
    Bot = this.botClass = (class Bot extends XY {
      constructor(botData) {
        super(botData[1], botData[2]);
        this.id = botData[0];
        this.score = 0;
        this.dead = true;
      }
    });
    Cell = this.cellClass = (class Cell {
      constructor(id, xy) {
        this.id = id;
        this.pos = xy;
      }
    });

    this.botMap = [];
    this.botIDs = [];
    botData.forEach(d => { this.botMap[d[0]] = new Bot(d); this.botIDs.push(d[0]); });
    this.currentRound = 0;

    delete this.prevGrid;
  }

  const gridSize = gridData.length;
  const gridSizeSqr = gridSize * gridSize;
  const grid = new Array(gridSize * gridSize);
  for (var x = 0; x < gridSize; x++) {
    for (var y = 0; y < gridSize; y++) {
      grid[x * gridSize + y] = new Cell(gridData[x][y], new XY(x, y));
    }
  }
  const prevGrid = this.prevGrid;
  this.prevGrid = grid;

  const bots = [];
  const botMap = this.botMap;
  this.botIDs.forEach(id => botMap[id].dead = true);
  botData.forEach(d => {
    const r = botMap[d[0]];
    r.dead = false;
    r.lastPosition = new XY(r.x, r.y);
    r.x = d[1];
    r.y = d[2];
    r.score = grid.reduce((sum, cell) => sum + (cell.id === r.id), 0);
    bots.push(r);
    at(r).bot = r;
  });
  const me = botMap[myData[0]];

  const currentRound = this.currentRound++;
  const maxRound = gameInfoData[1] - 1;

  const zero = new XY(0, 0);
  const neighbors = [new XY(1, 0), new XY(0, 1), new XY(-1, 0), new XY(0, -1)];
  const moves = ["right", "down", "left", "up", "wait"];

  if (gameInfoData[0] === 1) {
    customSetup(this);
  }

  function rand(max = 1, min = 0) {
    return min + Math.random() * (max - min);
  }
  function randInt(max, min = 0) {
    return Math.floor(rand(max, min));
  }
  function roll(chance = 0.5) {
    return Math.random() < chance;
  }

  function separation(id1, id2) {
    return Math.abs(id1 - id2) % 3;
  }

  function value(id, bot = me) {
    return id === bot.id ? 1 : id === 0 ? 4 : id === null ? 0 : [5, 3, 2][separation(bot.id, id)];
  }

  function travelTo(goal, start = me) {
    const relative = goal.sub(start);
    return Math.abs(relative.x) > Math.abs(relative.y) ? (
      relative.x > 0 ? 0 : 2
    ) : (
      relative.y > 0 ? 1 : relative.y < 0 ? 3 : 4
    );
  }
  function travelToList(goal, start = me) {
    const relative = goal.sub(start);
    return [...start.getRealNeighbors(), start].sort((a, b) => (a.chebyshevDistance(goal) - b.chebyshevDistance(goal)) * gridSizeSqr + (a.distance(goal) - b.distance(goal)));
  }

  const swarm = this.swarm;
  const swarmSize = swarm.length;
  const botUID = this.botUID;

  const signalPatterns = [[3, 0, 1, 1, 0], [0, 1, 2, 2, 2, 3, 3, 2, 2, 1], [2, 3, 2, 3, 0, 0, 1, 0, 3, 3]];
  function patternMove(pos, round, ...pattern) {
    const e = pattern[round % pattern.length];
    const f = (e + 2) % 4;
    function calcPos(d) { return pos.add(neighbors[d]); }
    if (calcPos(e).withinBounds()) {
      return e;
    } else {
      return f;
    }
  }
  function signal(uid = botUID, pos = me, round = currentRound) {
    return patternMove(pos, round, ...signalPatterns[uid]);
  }

  if (currentRound) {
    for (var i = 0; i < swarmSize; i++) {
      if (!swarm[i]) {
        const consideredBots = bots.filter(b => !(b.failedSignal & (1 << i)));
        const matchedBots = consideredBots.filter(b => {
          const prevPos = b.lastPosition;
          const expected = neighbors[signal(i, prevPos, currentRound - 1)];
          const performed = b.sub(prevPos);
          const matched = performed.equals(expected);
          if (!matched) {
            b.failedSignal |= (1 << i);
          }
          return matched;
        });
        if (matchedBots.length === 1) {
          swarm[i] = matchedBots[0];
          swarm[i].member = true;
          this.matchedSize++;
          console.log("Swarm member", i, "found!");
        }
      }
    }
  }

  function findTarget() {
    const lists = [];
    lists.unshift(bots.filter(b => b.removal.candidate));
    lists.unshift(lists[0].filter(b => b.removal.separations[0] === 0));
    lists.unshift(lists[0].filter(b => b.removal.speed === 3));
    lists.unshift(lists[2].filter(b => b.removal.separations[0] === 2));
    const bestList = lists.find(l => l.length);
    if (!bestList) {
      console.log("No more targets!");
      return undefined;
    }
    const bestTarget = bestList.sort((a, b) => b.trespass - a.trespass)[0]; // TODO: Remove sort. TODO: Improve.
    console.log("Best target:", bestTarget);
    return bestTarget;
  }

  if (this.matchedSize === swarmSize) {
    if (!this.connected) {
      bots.forEach(b => {
        const separations = swarm.map(m => separation(b.id, m.id));
        const speed = Math.floor(separations.reduce((sum, val) => sum + (val < 2 ? 1 : 0.5), 0));
        b.removal = {separations: separations, speed: speed, candidate: speed > 1 && !b.member};
      });
      console.log("All connections established.");
      this.connected = true;
    }

    bots.forEach(b => {
      if (b.removal.separations[0] !== 2 && at(b, prevGrid).id === swarm[0].id) {
        b.desecrate++;
      }
      swarm.forEach((m, i) => {
        if (b.removal.separations[i] !== 2 && at(b, prevGrid).id === m.id) {
          b.trespass++;
        }
      });
    });

    if (!this.target || this.target.dead) {
      this.target = findTarget();

      swarm.forEach(b => {
        delete b.partner;
      });

      const sep = this.target.removal.separations;
      const overwriters = [];
      const eraser = [];
      const helpers = [];
      for (var i = 0; i < swarmSize; i++) {
        if (swarm[i].partner) {
          continue;
        }
        if (sep[i] === 0) {
          overwriters.push(swarm[i]);
        } else if (sep[i] === 1) {
          eraser.push(swarm[i]);
        } else if (sep[i] === 2) {
          for (var j = i + 1; j < swarmSize; j++) {
            if (sep[j] === 2) {
              swarm[j].partner = swarm[i];
              swarm[i].partner = swarm[j];
              eraser.push(swarm[i]);
              break;
            }
          }
          if (!swarm[i].partner) {
            helpers.push(swarm[i]);
          }
        }
      }

      this.chaser = eraser.pop() || overwriters.pop();
      this.cleaners = [...overwriters, ...eraser];
      this.roamers = helpers; // TODO: Make helpers more useful by making them simply target the next guy?
    }

    function findImmediate(target, bot = me) {
      const list = travelToList(target, bot);
      return list.find(p => !at(p).reserved) || list[0];
    }

    grid.forEach(c => c.reserved = 0);
    function reserve(bot, target) {
      if (!bot.target) {
        bot.immediateTarget = findImmediate(target, bot);
        bot.target = target;
        at(bot.immediateTarget).reserved++;
        at(target).reserved++;
      }
    }
    function unreserve(bot) {
      if (bot.target) {
        at(bot.immediateTarget).reserved--;
        at(bot.target).reserved--;
        delete bot.immediateTarget;
        delete bot.target;
      }
    }

    reserve(this.chaser, chase(this.target));

    for (var i = 0; i < swarmSize; i++) {
      const emergency = preserveLife(swarm[i]);
      if (emergency) {
        unreserve(swarm[i]);
        reserve(swarm[i], emergency);
      }
    }

    this.cleaners.forEach(b => reserve(b, clean(b, this.target, this.cleaners)));
    this.roamers.forEach(b => reserve(b, roam(b)));

    const immediateTarget = me.immediateTarget || findImmediate(me.partner.target);
    swarm.forEach(b => unreserve(b));

    return moves[travelTo(immediateTarget)];
  } else {
    return moves[signal()];
  }

  function chase(target) {
    return target;
  }
  function clean(bot, target, cleaners) {
    return grid.filter(c => {
      return c.id === target.id && !c.reserved;
    }).reduce((best, c) => {
      const closest = Math.min(...cleaners.map(b => b.distance(c.pos)));
      const distance = bot.distance(c.pos);
      const wrongness = distance - closest;
      const distanceFromTarget = target.distance(c.pos);
      if (wrongness < best.wrongness || (wrongness === best.wrongness && (distance < best.distance || (distance === best.distance && distanceFromTarget > best.distanceFromTarget)))) {
        return {wrongness: wrongness, distance: distance, distanceFromTarget: distanceFromTarget, pos: c.pos};
      } else {
        return best;
      }
    }, {wrongness: Infinity, distance: Infinity, distanceFromTarget: -Infinity, pos: bot}).pos;
  }
  function roam(bot) {
    const dangerousBots = bots.filter(b => !b.member && separation(b.id, bot.id) !== 2);
    return grid.filter(c => {
      return value(c.id, bot) >= 4 && !c.bot && !c.reserved && !swarm.find(m => m.id === c.id);
    }).reduce((best, c) => {
      const val = value(c.id, bot);
      const distance = bot.distance(c.pos);
      const comfyness = c.pos.getNeighbors().reduce((sum, next) => sum + (value(at(next).id, bot) <= 2), 0);
      const closestBotDist = Math.min(...dangerousBots.map(b => b.distance(c.pos)));
      if (distance < best.distance || (distance === best.distance && (val > best.val || (val === best.val && (comfyness > best.comfyness || (comfyness === best.comfyness && closestBotDist > best.closestBotDist)))))) {
        return {distance: distance, val: val, comfyness: comfyness, closestBotDist: closestBotDist, pos: c.pos};
      } else {
        return best;
      }
    }, {distance: Infinity, val: -Infinity, comfyness: -Infinity, closestBotDist: -Infinity, pos: bot}).pos;
  }
  function preserveLife(bot) {
    if (bot.score < 20) {
      return roam(bot);
    }
  }
}

```

See the main post for more information.

# Answer
> 1 votes
# CandyButton

Goes up and down the grid pretending to be a Turing machine. Based on the idea that it should be as long as possible before revisiting the same location again. I will slowly extend this bot to do some more intelligent waiting/skipping of columns, but the basic idea will stay the same.

```
function(myself, grid, bots, gameInfo) {
var mc = myself[0];
var mx = myself[1];
var my = myself[2];

if(grid[mx][my]==0) return "wait"; // Edit: wait when white.
if(mx==grid.length-1 && my<grid.length-1) return "down";
if(my==grid.length-1 && mx>0) return "left";
if(mx==0 && my>0) return "up";
if(mx==0 && my==0) return "right";
if(mx%2){
    if(my<grid.length-2) return "down";
    return "right";
}
if(my>0) return "up"
return "right";
}

```

# Answer
> 1 votes
## Territorial

```
function (myself, grid, bots, gameInfo) {
    const w = 15, h = 15;
    let my_c = myself[0], my_x = myself[1], my_y = myself[2], size = grid.length, roundnum = gameInfo[0];

    let getDistance = function (x1, y1, x2, y2) {
        return (Math.abs(x1 - x2) + Math.abs(y1 - y2));
    };

    let getColorValue = function (color) {
        if (color === 0) {
            return my_c;
        }
        return [my_c, 0, color][Math.abs(my_c - color) % 3];
    };

    if (!localStorage.territorial) {
        //Choosing closest corner to defend
        const topLeft = [0, 0], bottomLeft = [0, size - 1], topRight = [size - 1, 0], bottomRight = [size - 1, size - 1];

        var distanceToTopLeft = getDistance(my_x, my_y, topLeft[0], topLeft[1]);
        var distanceToTopRight = getDistance(my_x, my_y, topRight[0], topRight[1]);
        var distanceToBottomLeft = getDistance(my_x, my_y, bottomLeft[0], bottomLeft[1]);
        var distanceToBottomRight = getDistance(my_x, my_y, bottomRight[0], bottomRight[1]);

        var nearestCorner = Math.min(distanceToTopLeft, distanceToTopRight, distanceToBottomLeft, distanceToBottomRight);

        if (nearestCorner === distanceToTopLeft) {
            //console.log('nearest corner is: topLeft');
            var offset_x = topLeft[0];
            var offset_y = topLeft[1];
            var innermostCorner_x = topLeft[0] + w - 1;
            var innermostCorner_y = topLeft[1] + h - 1;
        } else if (nearestCorner === distanceToTopRight) {
            //console.log('nearest corner is: topRight');
            var offset_x = topRight[0] - (w - 1);
            var offset_y = topRight[1];
            var innermostCorner_x = offset_x;
            var innermostCorner_y = topRight[1] + h - 1;
        } else if (nearestCorner === distanceToBottomLeft) {
            //console.log('nearest corner is: bottomLeft');
            var offset_x = bottomLeft[0];
            var offset_y = bottomLeft[1] - (h - 1);
            var innermostCorner_x = bottomLeft[0] + w - 1;
            var innermostCorner_y = offset_y;
        } else if (nearestCorner === distanceToBottomRight) {
            //console.log('nearest corner is: bottomRight');
            var offset_x = bottomRight[0] - (w - 1);
            var offset_y = bottomRight[1] - (h - 1);
            var innermostCorner_x = offset_x;
            var innermostCorner_y = offset_y;
        }
        let lastMove = "";
        localStorage.territorial = JSON.stringify([offset_x, offset_y, innermostCorner_x, innermostCorner_y, lastMove]);
    }
    offsets = JSON.parse(localStorage.territorial);
    offset_x = offsets[0];
    offset_y = offsets[1];
    innermostCorner_x = offsets[2];
    innermostCorner_y = offsets[3];
    lastMove = offsets[4];

    let targets = [];
    let distance = 999999;
    let lowestDistance = 999999;
    for (let grid_x = offset_x; grid_x < offset_x + w; grid_x++)
    {
        for (let grid_y = offset_y; grid_y < offset_y + h; grid_y++)
        {
            if (grid[grid_x][grid_y] !== my_c && getColorValue(grid[grid_x][grid_y]) !== grid[grid_x][grid_y])
            {
                distance = getDistance(my_x, my_y, grid_x, grid_y);
                targets[distance] = [grid_x, grid_y];

                if (distance < lowestDistance) {
                    lowestDistance = distance;
                }
            }
        }
    }

    let target = targets[lowestDistance];
    //If territory is safe, move to border nearest boardCenter
    if (target === undefined) {
        targets.push([innermostCorner_x, innermostCorner_y]);
        target = targets.pop();
    }

    let move = "";
    if (target === undefined) {
        move = 'wait';
    } else if (target[0] > my_x) {
        move = 'right';
    } else if (target[0] < my_x) {
        move = 'left';
    } else if (target[1] > my_y) {
        move = 'down';
    } else if (target[1] < my_y) {
        move = 'up';
    } else {
        move = "wait";
    }

    if (move === "wait" && lastMove === "wait") {
        move = "left";
    }

    localStorage.territorial = JSON.stringify([offset_x, offset_y, innermostCorner_x, innermostCorner_y, w, h, move]);

    return move;
}

```

Based on M.A.D.S. by @John Aaron but defends the nearest corner instead.
Ignores unpaintable spots in the territory.
If territory is save, moves to the corner nearest the boardCenter to await oncoming attackers (could be upgraded to be intelligent).
Another planned upgrade is to claim more territory the longer the game goes.
Also uses the getDistance function stolen from The Bot That Paints The Board Constantly But Is Not A Painter by @Night2.

# Answer
> 1 votes
# The Follower

```
function(myself, grid, bots, gameInfo) {
    var dirs;

    window.localStorage.FCOLOR = window.localStorage.FCOLOR || 0;
    window.localStorage.FDIR = window.localStorage.FDIR || "";
    var c = myself[0];
    var x = myself[1];
    var y = myself[2];

    var n = grid.length;

    function result(color) {
        if(color == 0) return c;
        else return [c, 0, color][Math.abs(c - color)%3];
    }

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 5; _++) {
        var dir = _ == 0 ? window.localStorage.FDIR : dirs.splice(Math.random() * dirs.length |0, 1);
        if(window.localStorage.FCOLOR != 0 && dir == "left" && x != 0 && grid[x-1][y] == window.localStorage.FCOLOR) {
            window.localStorage.FDIR = dir;
            return "left";
        }
        if(window.localStorage.FCOLOR != 0 && dir == "right" && x != n-1 && grid[x+1][y] == window.localStorage.FCOLOR) {
            window.localStorage.FDIR = dir;
            return "right";
        }
        if(window.localStorage.FCOLOR != 0 && dir == "up" && y != 0 && grid[x][y-1] == window.localStorage.FCOLOR) {
            window.localStorage.FDIR = dir;
            return "up";
        }
        if(window.localStorage.FCOLOR != 0 && dir == "down" && y != n-1 && grid[x][y+1] == window.localStorage.FCOLOR) {
            window.localStorage.FDIR = dir;
            return "down";
        }
    }

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 4; _++) {
        var dir = dirs.splice(Math.random() * dirs.length |0, 1);
        if(dir == "left" && x != 0 && grid[x-1][y] != 0 && grid[x-1][y] != c && result(grid[x-1][y]) == c) {
            window.localStorage.FCOLOR = grid[x-1][y];
            window.localStorage.FDIR = dir;
            return "left";
        }
        if(dir == "right" && x != n-1 && grid[x+1][y] != 0 && grid[x+1][y] != c && result(grid[x+1][y]) == c) {
            window.localStorage.FCOLOR = grid[x+1][y];
            window.localStorage.FDIR = dir;
            return "right";
        }
        if(dir == "up" && y != 0 && grid[x][y-1] != 0 && grid[x][y-1] != c && result(grid[x][y-1]) == c) {
            window.localStorage.FCOLOR = grid[x][y-1];
            window.localStorage.FDIR = dir;
            return "up";
        }
        if(dir == "down" && y != n-1 && grid[x][y+1] != 0 && grid[x][y+1] != c && result(grid[x][y+1]) == c) {
            window.localStorage.FCOLOR = grid[x][y+1];
            window.localStorage.FDIR = dir;
            return "down";
        }
    }

    //window.localStorage.FCOLOR = 0;
    window.localStorage.FDIR = "";

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 4; _++) {
        var dir = dirs.splice(Math.random() * dirs.length |0, 1);
        if(dir == "left" && x != 0 && grid[x-1][y] == 0) return "left";
        if(dir == "right" && x != n-1 && grid[x+1][y] == 0) return "right";
        if(dir == "up" && y != 0 && grid[x][y-1] == 0) return "up";
        if(dir == "down" && y != n-1 && grid[x][y+1] == 0) return "down";
    }

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 4; _++) {
        var dir = dirs.splice(Math.random() * dirs.length |0, 1);
        if(dir == "left" && x != 0) return "left";
        if(dir == "right" && x != n-1) return "right";
        if(dir == "up" && y != 0) return "up";
        if(dir == "down" && y != n-1) return "down";
    }
    return "wait";
}

```

This bot will move randomly across the grid in certain priority levels. First, it checks for the color that the bot is "following" (one that it can paint over). Then, it checks for other colors it can paint over, and sets the "following" color to that. Then, it checks for any blank squares. If any of the checks are satisfied, a random path is chosen from the directions that satisfy the first of the checks satisfied. Also stores a direction to not be lead astray while following.

# Answer
> 1 votes
# NearRandomGridBot

```
function randomGrid(myself, grid, bots, gameInfo){
  dir=0;tmp=(grid.length/2)|0;
  for(i=tmp-10;i<tmp+10;i++){
    for(j=tmp-10;j<tmp+10;j++){
      dir=(dir+grid[i][j])
    }
  }
  return ["up","right","down","left"][dir%4];
}

```

NearRandomGridBot uses the sum value mod 4 of all the colors in a 10 by 10 square around the center of the grid to select what direction to go.

# Answer
> 1 votes
# Nice Bot

Nice bot will never overwrite cells that were colored by another bot.

Uses wall following and breadth-first-search to find empty spaces, and treats colors it can overwrite as walls.

```
function([id,x,y],map,bots,[round]){
    //set up checked spaces 2d array
    if(round===1)
        this.checked=make_2d_array(map.length,map[0].length);
    else
        //to avoid infinite loops, if previous position is now empty, don't pathfind to it.
        if(map[this.last_pos.x][this.last_pos.y]===0)
            map[this.last_pos.x][this.last_pos.y]=id;
    //store old position
    this.last_pos={x:x,y:y};
    //don't walk into spaces occupied by other bots
    bots.forEach(([id,x,y])=>{
        if(map[x][y]===0)
            map[x][y]=id;
    });
    //wall following
    var dx=[0,1,0,-1],dy=[-1,0,1,0];
    var dir=this.last_dir-1&3;
    for(var i=1;i<=4;i++){
        if(can_draw_at(x+dx[dir],y+dy[dir]))
            return ["up","right","down","left"][this.last_dir=dir];
        dir=dir+1&3;
    }
    //pathfinding
    fill_2d_array(this.checked,0);
    var spaces=[0,0,0,0];
    var next_level=[],current_level=[];
    var found_space=false;
    var extra_levels=0;
    //check initial surrounding points, with directions
    check(map,this.checked,current_level,x  ,y-1,0);
    check(map,this.checked,current_level,x+1,y  ,1);
    check(map,this.checked,current_level,x  ,y+1,2);
    check(map,this.checked,current_level,x-1,y  ,3);

    while(current_level.length && extra_levels<7){
        if(found_space)
            extra_levels++;
        while(current_level.length){
            [x,y,dir]=current_level.pop();
            check(map,this.checked,next_level,x  ,y-1,dir);
            check(map,this.checked,next_level,x+1,y  ,dir);
            check(map,this.checked,next_level,x  ,y+1,dir);
            check(map,this.checked,next_level,x-1,y  ,dir);
        }
        [current_level,next_level]=[next_level,current_level]; //current_level is empty here
    }
    //find the best direction
    if(found_space)
        return ["up","right","down","left"][this.last_dir=spaces.indexOf(Math.max(...spaces))];

    //check
    function check(map,checked,list,x,y,dir){
        if(checked[x] && checked[x][y]!==undefined && (checked[x][y] & 1<<dir)===0){
            if(can_walk_on(map[x][y])){
                list.push([x,y,dir]);
                checked[x][y] |= 1<<dir;
                if(map[x][y]===0){
                    found_space=true;
                    spaces[dir]+=1/(extra_levels+0.1);
                }//else
                    //spaces[dir]+=0.1/(extra_levels+0.1);
            }
        }
    }
    //if cell can be walked on without rudely changing the color
    function can_walk_on(floor){
        return floor===0||floor===id||Math.abs(id-floor)%3===2;
    }
    function can_draw_at(x,y){
        return map[x]&&map[x][y]===0;
    }
    //"2D" arrays in JS are so annoying
    function fill_2d_array(array,value){
        array.forEach(column=>column.fill(value));
    }
    function make_2d_array(width,height){
        return Array(width).fill().map(()=>Array(height));
    }
}

```

# Answer
> 1 votes
# bounce 2

Usually just moves diagonally around the map, bouncing off the edges. If another bot hasn't moved for 50 rounds, this bot will move towards it. Commits suicide if trollbot is following it.

```
function([id,x,y],map,bots,[round]){
    var {length:width,0:{length:height}}=map;
    if(round===1){
        this.last_pos=Array(bots.length);
        this.waiting=Array(bots.length).fill(0);
        this.following=Array(bots.length).fill(0);
    }
    var stuck;
    for(var [bot,bx,by] of bots){
        if(id!==bot && this.last_pos[bot]){
            //check for trollbot
            if(dist(x,y,bx,by)<3 && this.waiting[bot]<50 && this.waiting[bot]>=0){
                if(this.following[bot]++>5){
                    //commit suicide by leading trollbot to this bot's colored areas
                    var target=manhattan_search(x,y,width,(x,y)=>
                        map[x] && map[x][y]===id
                    );
                    if(target)
                        return ["left",["up","wait","down"][Math.sign(target.y-y)+1],"right"][Math.sign(target.x-x)+1];
                }
            }else
                this.following[bot]=0;
            //check for bots that haven't moved in a while
            if(x==bx && y==by)
                this.waiting[bot]=-200;
            else if(this.last_pos[bot].x==bx && this.last_pos[bot].y==by){
                //move towards stuck bot
                if(this.waiting[bot]++>50){
                    stuck=bot;
                    this.vx=Math.sign(bx-x);
                    this.vy=Math.sign(by-y);
                }
            }else
                this.waiting[bot]=0;
        }
        this.last_pos[bot]={x:bx,y:by};
    }
    if(!stuck){
        //always move "diagonally"
        if(!this.vx)
            this.vx=1
        if(!this.vy)
            this.vy=-1
    }

    //bounce off edges
    if(x===0)
        this.vx=1
    else if(x===width-1)
        this.vx=-1
    if(y===0)
        this.vy=1
    else if(y===height-1)
        this.vy=-1
    //choose direction
    return round%2?
    ["left",["up","wait","down"][this.vy+1],"right"][this.vx+1]:
    ["up",["left","wait","right"][this.vx+1],"down"][this.vy+1];

    function manhattan_search(x,y,board_size,callback){
        var dest_x,dest_y;
        if(callback(x,y))
            return {x:x,y:y};
        try{
            for(var dist=1;dist<board_size*2;dist++){
                check(0, dist); //x+
                check(0,-dist); //x-
                check( dist,0); //y+
                check(-dist,0); //y-
                for(var i=1;i<dist;i++){
                    check( i,  dist-i ); //++
                    check(-i,  dist-i ); //-+
                    check( i,-(dist-i)); //+-
                    check(-i,-(dist-i)); //--
                }
            }
            return undefined;
        }catch(e){
            return {x:dest_x,y:dest_y};
        }
        function check(vx,vy){
            dest_x=x+vx;
            dest_y=y+vy;
            if(callback(dest_x,dest_y))
                throw undefined;
        }
    }

    function dist(x,y,x2,y2){
        return Math.abs(x-x2)+Math.abs(y-y2);
    }
}

```

# Answer
> 1 votes
# Unfollowable Follower

```
function(myself, grid, bots, gameInfo) {
    var dirs;
    if(gameInfo[0] == 1) {
      window.localStorage._FCOLOR = 0;
      window.localStorage._FDIR = "";
      window.localStorage._FTR = 0;
      window.localStorage._TROLLC = 0;
      window.localStorage._FDX = 0;
      window.localStorage._FDY = 0;
    }

    var c = myself[0];
    var x = myself[1];
    var y = myself[2];
    var n = grid.length;

    var TROLL_COUNT = 100;
    var TROLL_DIST = 1;

    if(window.localStorage._FTR == 1) {
        return "wait";
    }

    if(window.localStorage._TROLLC >= TROLL_COUNT && window.localStorage._FTR == 0) {
        console.log("UNFOLLOWABLE FOLLOWER: SUICIDE");
        window.localStorage._FTR = 1;
        return "wait";
    }

    function result(color) {
        if(color == 0) return c;
        else return [c, 0, color][Math.abs(c - color)%3];
    }

    var PREV_C = window.localStorage._TROLLC;

    for(var i = 0; i < bots.length; i++) {
        var bot = bots[i];
        if(bot[0] != c && Math.abs(bot[1]-x) + Math.abs(bot[2]-y) <= TROLL_DIST && (result(bot[0]) == 0 || result(bot[0]) == c)) {
            window.localStorage._TROLLC++;
            break;
        }
    }

    if(PREV_C == window.localStorage._TROLLC) {
        window.localStorage._TROLLC = 0;
    }

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 5; _++) {
        var dir = _ == 0 ? window.localStorage._FDIR : dirs.splice(Math.random() * dirs.length |0, 1);
        if(window.localStorage._FCOLOR != 0 && dir == "left" && x != 0 && grid[x-1][y] == window.localStorage._FCOLOR) {
            window.localStorage._FDIR = dir;
            return "left";
        }
        if(window.localStorage._FCOLOR != 0 && dir == "right" && x != n-1 && grid[x+1][y] == window.localStorage._FCOLOR) {
            window.localStorage._FDIR = dir;
            return "right";
        }
        if(window.localStorage._FCOLOR != 0 && dir == "up" && y != 0 && grid[x][y-1] == window.localStorage._FCOLOR) {
            window.localStorage._FDIR = dir;
            return "up";
        }
        if(window.localStorage._FCOLOR != 0 && dir == "down" && y != n-1 && grid[x][y+1] == window.localStorage._FCOLOR) {
            window.localStorage._FDIR = dir;
            return "down";
        }
    }

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 4; _++) {
        var dir = dirs.splice(Math.random() * dirs.length |0, 1);
        if(dir == "left" && x != 0 && grid[x-1][y] != 0 && grid[x-1][y] != c && result(grid[x-1][y]) == c) {
            window.localStorage._FCOLOR = grid[x-1][y];
            window.localStorage._FDIR = dir;
            return "left";
        }
        if(dir == "right" && x != n-1 && grid[x+1][y] != 0 && grid[x+1][y] != c && result(grid[x+1][y]) == c) {
            window.localStorage._FCOLOR = grid[x+1][y];
            window.localStorage._FDIR = dir;
            return "right";
        }
        if(dir == "up" && y != 0 && grid[x][y-1] != 0 && grid[x][y-1] != c && result(grid[x][y-1]) == c) {
            window.localStorage._FCOLOR = grid[x][y-1];
            window.localStorage._FDIR = dir;
            return "up";
        }
        if(dir == "down" && y != n-1 && grid[x][y+1] != 0 && grid[x][y+1] != c && result(grid[x][y+1]) == c) {
            window.localStorage._FCOLOR = grid[x][y+1];
            window.localStorage._FDIR = dir;
            return "down";
        }
    }

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 4; _++) {
        var dir = dirs.splice(Math.random() * dirs.length |0, 1);
        if(dir == "left" && x != 0 && grid[x-1][y] != 0 && grid[x-1][y] != c && result(grid[x-1][y]) == 0) {
            window.localStorage._FDIR = dir;
            return "left";
        }
        if(dir == "right" && x != n-1 && grid[x+1][y] != 0 && grid[x+1][y] != c && result(grid[x+1][y]) == c) {
            window.localStorage._FDIR = dir;
            return "right";
        }
        if(dir == "up" && y != 0 && grid[x][y-1] != 0 && grid[x][y-1] != c && result(grid[x][y-1]) == c) {
            window.localStorage._FDIR = dir;
            return "up";
        }
        if(dir == "down" && y != n-1 && grid[x][y+1] != 0 && grid[x][y+1] != c && result(grid[x][y+1]) == c) {
            window.localStorage._FDIR = dir;
            return "down";
        }
    }

    //window.localStorage._FCOLOR = 0;
    window.localStorage._FDIR = "";

    dirs = ["left", "right", "up", "down"];
    for(var _ = 0; _ < 4; _++) {
        var dir = dirs.splice(Math.random() * dirs.length |0, 1);
        if(dir == "left" && x != 0 && grid[x-1][y] == 0) return "left";
        if(dir == "right" && x != n-1 && grid[x+1][y] == 0) return "right";
        if(dir == "up" && y != 0 && grid[x][y-1] == 0) return "up";
        if(dir == "down" && y != n-1 && grid[x][y+1] == 0) return "down";
    }

    if(window.localStorage._FDX == 0 && window.localStorage._FDY == 0) {
      window.localStorage._FDX = Math.random() * n | 0;
      window.localStorage._FDY = Math.random() * n | 0;
      //console.log("DESTINATION: " + window.localStorage._FDX + ", " + window.localStorage._FDY);
    }

    if(x > window.localStorage._FDX) return "left";
    if(x < window.localStorage._FDX) return "right";
    if(y > window.localStorage._FDY) return "up";
    if(y < window.localStorage._FDY) return "down";
    window.localStorage._FDX = window.localStorage._FDY = 0;
    if(x != 0) return "left";
    if(x != n-1) return "right";
    if(y != 0) return "up";
    if(y != n-1) return "down";
    return "wait";
}

```

A slight upgrade from "The Follower", with these additions:

* Pure randomness is no longer a step. It is replaced by going towards a random spot.
* There is now a step for erasure, right before movement towards the goal
* Logs the state that it is in.

# Answer
> 1 votes
# Drone-FA57

```
function(myData, gridData, botData, gameInfoData) {
  function customSetup(fThis) {
    fThis.botUID = 2;
    fThis.swarm = new Array(3);
    fThis.matchedSize = 0;
    bots.forEach(b => { b.failedSignal = 0; b.trespass = 0; b.desecrate = 0; });
    delete fThis.connected;
    delete fThis.target;
    delete fThis.chaser;
    delete fThis.cleaners;
    delete fThis.roamers;
  }

  let XY = this.xyClass;
  let Bot = this.botClass;
  let Cell = this.cellClass;

  function at(pos, usedGrid = grid) { // NEVER EVER THINK ABOUT PUTTING THIS ON THE GRID ITSELF
    return pos.withinBounds() ? usedGrid[pos.toIndex()] : new Cell(null);
  }

  if (gameInfoData[0] === 1) {
    XY = this.xyClass = (class XY {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }

      static fromIndex(index) {
        return new XY(Math.floor(index / gridSize), index % gridSize);
      }
      toIndex() {
        return this.x * gridSize + this.y;
      }

      add(other) {
        return new XY(this.x + other.x, this.y + other.y);
      }
      sub(other) {
        return new XY(this.x - other.x, this.y - other.y);
      }
      div(value) {
        return new XY(Math.round(this.x / v), Math.round(this.y / v));
      }
      mul(value) {
        return new XY(Math.round(this.x * m), Math.round(this.y * m));
      }
      equals(other) {
        return this.x === other.x && this.y === other.y;
      }

      distance(other) {
        return Math.abs(other.x - this.x) + Math.abs(other.y - this.y);
      }
      chebyshevDistance(other) {
        return Math.max(Math.abs(other.x - this.x), Math.abs(other.y - this.y));
      }

      withinBounds() {
        return this.x >= 0 && this.x < gridSize && this.y >= 0 && this.y < gridSize;
      }

      getNeighbors() {
        return neighbors.map(p => this.add(p));
      }
      getRealNeighbors() {
        return this.getNeighbors().filter(p => p.withinBounds());
      }
    });
    Bot = this.botClass = (class Bot extends XY {
      constructor(botData) {
        super(botData[1], botData[2]);
        this.id = botData[0];
        this.score = 0;
        this.dead = true;
      }
    });
    Cell = this.cellClass = (class Cell {
      constructor(id, xy) {
        this.id = id;
        this.pos = xy;
      }
    });

    this.botMap = [];
    this.botIDs = [];
    botData.forEach(d => { this.botMap[d[0]] = new Bot(d); this.botIDs.push(d[0]); });
    this.currentRound = 0;

    delete this.prevGrid;
  }

  const gridSize = gridData.length;
  const gridSizeSqr = gridSize * gridSize;
  const grid = new Array(gridSize * gridSize);
  for (var x = 0; x < gridSize; x++) {
    for (var y = 0; y < gridSize; y++) {
      grid[x * gridSize + y] = new Cell(gridData[x][y], new XY(x, y));
    }
  }
  const prevGrid = this.prevGrid;
  this.prevGrid = grid;

  const bots = [];
  const botMap = this.botMap;
  this.botIDs.forEach(id => botMap[id].dead = true);
  botData.forEach(d => {
    const r = botMap[d[0]];
    r.dead = false;
    r.lastPosition = new XY(r.x, r.y);
    r.x = d[1];
    r.y = d[2];
    r.score = grid.reduce((sum, cell) => sum + (cell.id === r.id), 0);
    bots.push(r);
    at(r).bot = r;
  });
  const me = botMap[myData[0]];

  const currentRound = this.currentRound++;
  const maxRound = gameInfoData[1] - 1;

  const zero = new XY(0, 0);
  const neighbors = [new XY(1, 0), new XY(0, 1), new XY(-1, 0), new XY(0, -1)];
  const moves = ["right", "down", "left", "up", "wait"];

  if (gameInfoData[0] === 1) {
    customSetup(this);
  }

  function rand(max = 1, min = 0) {
    return min + Math.random() * (max - min);
  }
  function randInt(max, min = 0) {
    return Math.floor(rand(max, min));
  }
  function roll(chance = 0.5) {
    return Math.random() < chance;
  }

  function separation(id1, id2) {
    return Math.abs(id1 - id2) % 3;
  }

  function value(id, bot = me) {
    return id === bot.id ? 1 : id === 0 ? 4 : id === null ? 0 : [5, 3, 2][separation(bot.id, id)];
  }

  function travelTo(goal, start = me) {
    const relative = goal.sub(start);
    return Math.abs(relative.x) > Math.abs(relative.y) ? (
      relative.x > 0 ? 0 : 2
    ) : (
      relative.y > 0 ? 1 : relative.y < 0 ? 3 : 4
    );
  }
  function travelToList(goal, start = me) {
    const relative = goal.sub(start);
    return [...start.getRealNeighbors(), start].sort((a, b) => (a.chebyshevDistance(goal) - b.chebyshevDistance(goal)) * gridSizeSqr + (a.distance(goal) - b.distance(goal)));
  }

  const swarm = this.swarm;
  const swarmSize = swarm.length;
  const botUID = this.botUID;

  const signalPatterns = [[3, 0, 1, 1, 0], [0, 1, 2, 2, 2, 3, 3, 2, 2, 1], [2, 3, 2, 3, 0, 0, 1, 0, 3, 3]];
  function patternMove(pos, round, ...pattern) {
    const e = pattern[round % pattern.length];
    const f = (e + 2) % 4;
    function calcPos(d) { return pos.add(neighbors[d]); }
    if (calcPos(e).withinBounds()) {
      return e;
    } else {
      return f;
    }
  }
  function signal(uid = botUID, pos = me, round = currentRound) {
    return patternMove(pos, round, ...signalPatterns[uid]);
  }

  if (currentRound) {
    for (var i = 0; i < swarmSize; i++) {
      if (!swarm[i]) {
        const consideredBots = bots.filter(b => !(b.failedSignal & (1 << i)));
        const matchedBots = consideredBots.filter(b => {
          const prevPos = b.lastPosition;
          const expected = neighbors[signal(i, prevPos, currentRound - 1)];
          const performed = b.sub(prevPos);
          const matched = performed.equals(expected);
          if (!matched) {
            b.failedSignal |= (1 << i);
          }
          return matched;
        });
        if (matchedBots.length === 1) {
          swarm[i] = matchedBots[0];
          swarm[i].member = true;
          this.matchedSize++;
          console.log("Swarm member", i, "found!");
        }
      }
    }
  }

  function findTarget() {
    const lists = [];
    lists.unshift(bots.filter(b => b.removal.candidate));
    lists.unshift(lists[0].filter(b => b.removal.separations[0] === 0));
    lists.unshift(lists[0].filter(b => b.removal.speed === 3));
    lists.unshift(lists[2].filter(b => b.removal.separations[0] === 2));
    const bestList = lists.find(l => l.length);
    if (!bestList) {
      console.log("No more targets!");
      return undefined;
    }
    const bestTarget = bestList.sort((a, b) => b.trespass - a.trespass)[0]; // TODO: Remove sort. TODO: Improve.
    console.log("Best target:", bestTarget);
    return bestTarget;
  }

  if (this.matchedSize === swarmSize) {
    if (!this.connected) {
      bots.forEach(b => {
        const separations = swarm.map(m => separation(b.id, m.id));
        const speed = Math.floor(separations.reduce((sum, val) => sum + (val < 2 ? 1 : 0.5), 0));
        b.removal = {separations: separations, speed: speed, candidate: speed > 1 && !b.member};
      });
      console.log("All connections established.");
      this.connected = true;
    }

    bots.forEach(b => {
      if (b.removal.separations[0] !== 2 && at(b, prevGrid).id === swarm[0].id) {
        b.desecrate++;
      }
      swarm.forEach((m, i) => {
        if (b.removal.separations[i] !== 2 && at(b, prevGrid).id === m.id) {
          b.trespass++;
        }
      });
    });

    if (!this.target || this.target.dead) {
      this.target = findTarget();

      swarm.forEach(b => {
        delete b.partner;
      });

      const sep = this.target.removal.separations;
      const overwriters = [];
      const eraser = [];
      const helpers = [];
      for (var i = 0; i < swarmSize; i++) {
        if (swarm[i].partner) {
          continue;
        }
        if (sep[i] === 0) {
          overwriters.push(swarm[i]);
        } else if (sep[i] === 1) {
          eraser.push(swarm[i]);
        } else if (sep[i] === 2) {
          for (var j = i + 1; j < swarmSize; j++) {
            if (sep[j] === 2) {
              swarm[j].partner = swarm[i];
              swarm[i].partner = swarm[j];
              eraser.push(swarm[i]);
              break;
            }
          }
          if (!swarm[i].partner) {
            helpers.push(swarm[i]);
          }
        }
      }

      this.chaser = eraser.pop() || overwriters.pop();
      this.cleaners = [...overwriters, ...eraser];
      this.roamers = helpers; // TODO: Make helpers more useful by making them simply target the next guy?
    }

    function findImmediate(target, bot = me) {
      const list = travelToList(target, bot);
      return list.find(p => !at(p).reserved) || list[0];
    }

    grid.forEach(c => c.reserved = 0);
    function reserve(bot, target) {
      if (!bot.target) {
        bot.immediateTarget = findImmediate(target, bot);
        bot.target = target;
        at(bot.immediateTarget).reserved++;
        at(target).reserved++;
      }
    }
    function unreserve(bot) {
      if (bot.target) {
        at(bot.immediateTarget).reserved--;
        at(bot.target).reserved--;
        delete bot.immediateTarget;
        delete bot.target;
      }
    }

    reserve(this.chaser, chase(this.target));

    for (var i = 0; i < swarmSize; i++) {
      const emergency = preserveLife(swarm[i]);
      if (emergency) {
        unreserve(swarm[i]);
        reserve(swarm[i], emergency);
      }
    }

    this.cleaners.forEach(b => reserve(b, clean(b, this.target, this.cleaners)));
    this.roamers.forEach(b => reserve(b, roam(b)));

    const immediateTarget = me.immediateTarget || findImmediate(me.partner.target);
    swarm.forEach(b => unreserve(b));

    return moves[travelTo(immediateTarget)];
  } else {
    return moves[signal()];
  }

  function chase(target) {
    return target;
  }
  function clean(bot, target, cleaners) {
    return grid.filter(c => {
      return c.id === target.id && !c.reserved;
    }).reduce((best, c) => {
      const closest = Math.min(...cleaners.map(b => b.distance(c.pos)));
      const distance = bot.distance(c.pos);
      const wrongness = distance - closest;
      const distanceFromTarget = target.distance(c.pos);
      if (wrongness < best.wrongness || (wrongness === best.wrongness && (distance < best.distance || (distance === best.distance && distanceFromTarget > best.distanceFromTarget)))) {
        return {wrongness: wrongness, distance: distance, distanceFromTarget: distanceFromTarget, pos: c.pos};
      } else {
        return best;
      }
    }, {wrongness: Infinity, distance: Infinity, distanceFromTarget: -Infinity, pos: bot}).pos;
  }
  function roam(bot) {
    const dangerousBots = bots.filter(b => !b.member && separation(b.id, bot.id) !== 2);
    return grid.filter(c => {
      return value(c.id, bot) >= 4 && !c.bot && !c.reserved && !swarm.find(m => m.id === c.id);
    }).reduce((best, c) => {
      const val = value(c.id, bot);
      const distance = bot.distance(c.pos);
      const comfyness = c.pos.getNeighbors().reduce((sum, next) => sum + (value(at(next).id, bot) <= 2), 0);
      const closestBotDist = Math.min(...dangerousBots.map(b => b.distance(c.pos)));
      if (distance < best.distance || (distance === best.distance && (val > best.val || (val === best.val && (comfyness > best.comfyness || (comfyness === best.comfyness && closestBotDist > best.closestBotDist)))))) {
        return {distance: distance, val: val, comfyness: comfyness, closestBotDist: closestBotDist, pos: c.pos};
      } else {
        return best;
      }
    }, {distance: Infinity, val: -Infinity, comfyness: -Infinity, closestBotDist: -Infinity, pos: bot}).pos;
  }
  function preserveLife(bot) {
    if (bot.score < 20) {
      return roam(bot);
    }
  }
}

```

See the main post for more information.

# Answer
> 1 votes
# Jake

```
function([mc, mx, my], grid, bots, [round, maxRound]) {const ID = 1;
  var S = this;
  const botAm = 3;
  function log(...args) {
    //if (round > 1) console.log(ID+" "+args[0], ...args.slice(1));
    return true;
  }
  if (round == 1) {
    var all = new Array(bots.length).fill().map((_,i)=>i+1);
    S.fs = new Array(botAm).fill().map(c =>
      [all.slice(), all.slice(), all.slice(), all.slice()]
    );
    S.doneSetup = false;
    var center = grid.length/2;
    // UL=0; DL=1; DR=2; UR=3
    S.dir = mx<center? (my<center? 0 : 1) : (my<center? 3 : 2);
    S.job = 0;
    S.setupFail = S.finished = false;
    S.tbotc = undefined;
    S.botAm = bots.length;
    S.botEvilness = new Array(bots.length+1).fill(0);
    S.keys = [[1,1,0,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,0,0],
              [0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,0,0,0,1,1],
              [1,0,0,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,0,1,1,1,1,0,1,1,1,0]];
    /*if (ID == 2) */{
      S.chased = 0;
      S.ignore = [];
      S.badMoves = 0;
      S.pastMoves = new Array(100).fill("-1;0");
      S.timer = 0;
      S.jimFn = function([mc, mx, my], grid, bots, [round, maxRound]) { // ---------- BEGIN JIM ---------- \\
        var output;
        var allowRetracing = false;

        var checkSize = 3;
        var eatSize = 5;
        var myScore;
        var scoreboard;

        if (grid[mx][my] == 0 && !bots.some(([col, bx, by])=> col != mc && bx==mx && by==my)) return "wait"; // collect those sweet points

        // rescore every now and then
        if (S.timer > 200) rescore();

        S.pastMoves.push(mx+";"+my);
        S.pastMoves.shift();

        var orth = [[-1,0],[0,-1],[1,0],[0,1]];
        if (S.atTarget
        || S.targetX === undefined || S.targetY === undefined
        || S.targetX === mx && S.targetY === my
        || orth.map(([x,y])=>[mx+x,my+y]).filter(c=>get(c)==0 && inbounds(c)).length > 2) {

          S.atTarget = true;
          var neighbors = orth
            .map(([x,y]) => [x+mx, y+my])
            .filter(inbounds)
            .filter(([x,y]) => !bots.some(([bid, bx, by]) => bx==x && by==y))
            .map(c=>[c,get(c)]);

          let test = (neighbors, f, msg) => {
            return bestOf(neighbors.filter(f).map(c=>c[0])) && log(msg);
          }

          if (test(neighbors, ([,c]) => c===0, "good")) return output;
          if (test(neighbors, ([,c]) => overMap(c, 1) && S.BCs,  "sad")) return output;

          S.atTarget = false;
          S.targetX = S.targetY = undefined;
          let bestScore = 7;
          let bfscore = 0;

          for (let dist = 4; dist < 8; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds([cx, cy]) && grid[cx][cy] === 0  ) {
                  let score = scoreOf(cx, cy, 1, false);
                  if(score>bfscore)bfscore=score;
                  if (score > bestScore) {
                    bestScore = score;
                    S.targetX = cx;
                    S.targetY = cy;
                  }
                }
              }
            }
          }
          if (S.targetX) {
            log("short goto", S.targetX, S.targetY,"(rel",S.targetX-mx, S.targetY-my,") score", bestScore);
            return to([S.targetX, S.targetY]);
          } else log("long goto",bfscore);

          rescore();
          return to([S.targetX, S.targetY]);
        } else log("going to target", S.targetX, S.targetY);

        return to([S.targetX, S.targetY]);

        function myScore() {
          if (!myScore) calculateScoreboard();
          return myScore;
        }
        function calculateScoreboard() {
          scoreboard = grid.map(column=> {
            var arr = new Int16Array(grid.length);
            column.forEach((c, x) => (
              myScore+= c==mc,
              arr[x] = overMap(c, 1, 0, 0, 0, 5)
            ));
            return arr;
          });
          for (let [bc, bx, by] of bots) if (bc != mc) {
            scoreboard[bx][by] = -100;
            if (inbounds([bx-2, by])) scoreboard[bx-2][by] = -50;
            if (inbounds([bx+2, by])) scoreboard[bx+2][by] = -50;
            if (inbounds([bx, by-2])) scoreboard[bx][by-2] = -50;
            if (inbounds([bx, by+2])) scoreboard[bx][by+2] = -50;
          }
        }
        function scoreOf (x, y, size, includeEnemies) {
          if (!scoreboard) calculateScoreboard();
          let score = 0;
          for (let dx = -size; dx <= size; dx++) {
            let cx = dx + x;
            if (cx < 1 || cx >= grid.length-1) continue;
            for (let dy = -size; dy <= size; dy++) {
              let cy = dy + y;
              if (cy < 1 || cy >= grid.length-1) continue;
              let cs = scoreboard[cx][cy];
              if (cs > 0 || includeEnemies) score+= cs;
            }
          }
          return score;
        }
        function rescore() { // heatmap of best scoring places
          //log(JSON.stringify(scoreboard));
          S.bestScore = -Infinity;
          var blur = grid.map((column, x)=>column.map((c, y) => {
            let score = scoreOf(x, y, checkSize, true);
            if (score > S.bestScore) {
              S.bestScore = score;
              S.targetX = x;
              S.targetY = y;
            }
            return score;
          }));
          S.atTarget = false;
          S.timer = 0;
          S.bestScore = scoreOf(S.targetX, S.targetY, eatSize);
          S.badMoves = 0;
          // log("scored to", S.targetX, S.targetY, S.bestScore);
        }
        function over(col) { // 1 if overrides happen, -1 if overrides don't happen, 0 if override = 0
          let res = Math.abs(mc-col) % 3;
          return res==1? 0 : res==0? 1 : -1;
        }
        function overMap(col, best = 0, good = 0, bad = 0, mine = 0, zero = 0) { // best if overrides happen, bad if overrides don't happen, good if override = 0
          let res = Math.abs(mc-col) % 3;
          return col == 0? zero : col == mc? mine : res==1? good : res==0? best : bad;
        }
        function iwin   (col) { return over(col) == 1; }
        function zeroes (col) { return over(col) == 0; }
        function to([x, y]) {
          //debugger
          var LR = x > mx? [mx+1, my] : x < mx? [mx-1, my] : null;
          var UD = y > my? [mx, my+1] : y < my? [mx, my-1] : null;
          if (LR && UD) {
            var LRScore = overMap(LR, 1, 0, 0, 0, 3);
            var UDScore = overMap(UD, 1, 0, 0, 0, 3);
            if (LRScore == UDScore) return toPos([LR, UD][Math.random()>.5? 1 : 0])
            else if (LRScore > UDScore) return toPos(LR);
            else return toPos(UD);
          } else return toPos(LR || UD || [x, y]);
        }
        function toPos([x,y]) {
            if (x > mx) return "right";
            if (x < mx) return "left";
            if (y < my) return "up";
            if (y > my) return "down";
            return 'wait';
        }
        function inbounds([x, y]) {
          // if (x<grid.length && y<grid.length && x>=0 && y>=0) return true;
          if (x<grid.length-1 && y<grid.length-1 && x>=1 && y>=1) return true;
          return false;
        }
        function get([x,y]) {
          if (inbounds([x, y])) return grid[x][y];
          return 0;
        }
        function bestOf (arr) {
          if (arr.length == 0) return false;
          var bestScore = -Infinity;
          var bestPos;
          for (var [x, y] of arr) {
            let score = 0;
            for (var [bcol, bx, by] of bots) {
              let dist = Math.sqrt((x-bx)**2 + (y-by)**2);
              let res = over(bcol);
              let power = res==0? 1 : res==1? 0.4 : 1.4;
              score+= power * dist;
            }
            score-= Math.sqrt((x-S.targetX)**2 + (y-S.targetY)**2);
            if (S.pastMoves.includes(x+";"+y)) score-= 1000000;

            if (score > bestScore) {
              bestScore = score;
              bestPos = [x,y];
            }
          }
          if (bestScore < -500000) {
            if (allowRetracing) log("RETRACING");
            else return false;
          }
          output = to(bestPos);
          return true;
        }
      } // ---------- END JIM ---------- \\
    }
  }
  const dirs = ['up','left','down','right'];

  if (!S.doneSetup && round < 37) { // ---------- HANDSHAKE ---------- \\
    let finished = 0;
    if (round != 1) {
      for (let id = 0; id < botAm; id++) {
        let f = S.fs[id];
        let remaining = f.map(c=>c.length).reduce((a,b)=>a+b);
        if (remaining == 1) {
          finished++;
          continue;
        }
        if (remaining == 0) {
          // mourn the loss of a good friend
          finished++;
          continue;
        }
        for (let dir = 0; dir < 4; dir++) {
          let possible = f[dir];

          for (let i = possible.length-1; i >= 0; i--) {
            let bc = possible[i];
            let curr =       bots.find(c=>c[0]==bc);
            let prev = S.pastBots.find(c=>c[0]==bc);
            if (!curr || !prev) {
              possible.splice(i,1);
              continue;
            }
            let dx = curr[1]-prev[1];
            let dy = curr[2]-prev[2];
            let move;
            if (dy == 0) {
              if (dx == 1) move = 'right';
              else         move =  'left';
            } else {
              if (dy == 1) move =  'down';
              else         move =    'up';
            }
            let omove = rotate(move, dir);
            let expected = ['down','right'][S.keys[id][round-1]];
            // if (id == 0 && dir == S.dir) log();
            if (omove != expected) possible.splice(i,1);
          }
        }
      }
    }
    S.pastBots = bots;
    if (finished == botAm) {
      S.doneSetup = true;
      S.pastBots = undefined;
      S.BCs = new Array(botAm).fill().map((_,i) => (S.fs[i].find(c=>c.length > 0) || [-1])[0]); // AKA idtoc
      S.fighters = S.BCs.slice(0,2);
      S.ctoid = {[S.BCs[0]]:0, [S.BCs[1]]:1, [S.BCs[2]]:2};
      log("identified", S.BCs);
      if (ID == 2) {
        log("can beat", bots.filter(c=>S.fighters.filter(b=>Math.abs(b-c[0])%3 != 2).length > 0).map(c=>c[3]));
      }
    } else {
      // log(ID,S.fs);
      return rotate(['down','right'][S.keys[ID][round]], S.dir);
    }
  }
  if (!S.doneSetup) { // HANDSHAKE FAILED
    S.setupFail = true;
    S.BCs=[];
    S.fighters = [];
    S.ctoid = {};
  }

  if (S.pastGrid) for (let [bc, bx, by] of bots) { // calculate bot evilness
    let prev = S.pastGrid[bx][by];
    let fID = S.BCs.indexOf(prev);
    if (fID === 2) S.botEvilness[bc]+= 10;
    else if (fID !== -1) S.botEvilness[bc]+= 5;
    else {
      let over = Math.abs(bc - prev) % 3;
      if (over === 0) S.botEvilness[bc]+= 1;
      else if (over === 1) S.botEvilness[bc]+= 2;
    }

  }

  S.pastGrid = grid;

  if (ID == 2) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]);

  if (S.setupFail || !bots.find(c=>c[0]==S.fighters[1-ID])) return 'wait'; // for my demise
  // TODO yeah no

  if (round < 50 || !bots.find(c=>c[0]==S.BCs[2])) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]); // if Jim's dead or if it's early game, be Jim so others don't win needlessly/scoreboard becomes more clear

  let tbot = bots.find(c=>c[0] == S.tbotc);

  // ---------- NEW TARGET ---------- \\
  let tried;

  // {
  //   let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
  //   for (let column of grid) for (let item of column) scores[item]++;
  //   log("scores", scores.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  //   log("evilness", S.botEvilness.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  // }

  let makeSureImNotStupidAgain = 0;
  while ((!S.tbotc || !tbot) && !S.finished) {
    makeSureImNotStupidAgain++;
    if (makeSureImNotStupidAgain > 100) {
      console.log("dzaima is stupid");
      S.finished = true;
      break;
    }
    if (!tried) tried = S.BCs.slice();
    S.gotoX = S.gotoY = undefined;
    let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
    for (let column of grid) for (let item of column) scores[item]++;
    var bbc, bbs=-Infinity;
    for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && !tried.includes(i)) {
      bbs = scores[i];
      bbc = i;
    }
    S.tbotc = bbc;
    tbot = bots.find(c=>c[0] == bbc);
    if (!tbot) {
      tried.push(bbc);
    } else {
      S.jobs = [0,0];
      let executers = S.fighters.filter(c=>Math.abs(c-bbc)%3 == 1).concat(S.fighters.filter(c=>Math.abs(c-bbc)%3 == 0));
      if (executers.length > 1) {
        S.jobs[S.ctoid[executers.pop()]] = 1;
        S.jobs[S.ctoid[executers.pop()]] = 2;
        //S.jobs.forEach((c,id) => c==0? S.jobs[id]=2 : 0);
        log("targetting", botName(bbc),"jobs",S.jobs);
      } else {
        // cry
        tried.push(bbc);
        S.tbotc = tbot = undefined;
      }
      S.job = S.jobs[ID];
    }
    if (tried.length >= bots.length) {
      // everyone is dead
      S.job = 0;
      S.jobs = new Array(2).fill(0);
      S.finished = true;
      break;
    }
  }

  if (tbot && !S.finished) {
    let [_, tx, ty] = tbot;

    switch (S.job) {
      case 1: // follow
        return to(tx, ty, S.tbotc);
      break;
      case 2: // erase
        let endingClearing = false;
        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          S.gotoX = undefined;
          var ending = [S.tbotc, ...S.fighters.filter(c=>c != mc)].map(c => bots.find(b=>b[0]==c)).filter(I=>I);
          search: for (let dist = 1; dist < grid.length*2+2; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds(cx, cy)) {
                  if (grid[cx][cy] == S.tbotc && ending.every(([_,bx,by]) => (bx-cx)**2 + (by-cy)**2 > Math.random()*10)) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
          if (S.gotoX === undefined) {
            let available = [];
            grid.forEach((column, x) => column.forEach((c, y) => c==S.tbotc? available.push([x,y]) : 0));
            [S.gotoX, S.gotoY] = available[Math.floor(Math.random()*available.length)];
            endingClearing = true;
          }
        }
        return to(S.gotoX, S.gotoY, endingClearing? undefined : S.tbotc);
      break;
      case 0: // exercise

        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          let scores = new Uint32Array(S.botAm+1);
          for (let column of grid) for (let item of column) scores[item]++;
          var bbc, bbs=-Infinity;
          for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && Math.abs(mc-i)%3 == 0 && !S.BCs.includes(i)) {
            bbs = scores[i];
            bbc = i;
          }
          if (bbc) {
            S.gotoX = undefined;
            search: for (let dist = 1; dist < grid.length*2+2; dist++) {
              for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
                for (let i = 0; i < dist; i++) {
                  let cx = dx*i + dsx*dist + mx;
                  let cy = dy*i + dsy*dist + my;
                  if (inbounds(cx, cy) && grid[cx][cy] == bbc) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
        }
        if (S.gotoX !== undefined) return to(S.gotoX, S.gotoY);
        return dirs[Math.floor(Math.random()*4)];
      break;
    }
  }

  function to (x, y, col) {
    if  (x == mx&&y== my) return 'wait';
    let dx =   x    - mx ;
    let dy =      y - my ;
    let ax = Math.abs(dx);
    let ay = Math.abs(dy);
    var          diag;
    if   (     ax==ay   ) {
      if (col&&ax+ ay==2) {
        let i=[[x, my], [mx, y]].findIndex(c=>grid[c[0]][c[1]]==col);
        if (i<0) diag = Math.random()>=.5;
        else     diag =           i  == 0;
      } else     diag = Math.random()>=.5;
    }
    if (ax==ay?  diag :  ax>ay) {
      if (dx>0) return 'right';
      else      return  'left';
    } else {
      if (dy>0) return  'down';
      else      return    'up';
    }
  }

  function rotate (move, dir) {
    if ((move == 'up' || move == 'down') && (dir && dir<3)) {
      if (move == 'up') return 'down';
      else return 'up';
    }
    if ((move == 'left' || move == 'right') && dir>1) {
      if (move == 'left') return 'right';
      else return 'left';
    }
    return move;
  }
  function botName(id) {
    let bot = bots.find(c=>c[0]==id);
    if (!bot) return id.toString();
    return bot[3] + "/" + id;
  }
  function inbounds(x, y) { return x<grid.length && y<grid.length && x>=0 && y>=0 }
}

```

# Answer
> 1 votes
# Bernard Szumborski

```
function (me, board, painters, info) {
    let id = me[0], meX = me[1], meY = me[2], size = board.length, sectionSize = Math.ceil(size / 3), paintersLength = painters.length, round = info[0], storage, storageKey = 'jijdfoadofsdfasz', s1, s2, i;

    if (round === 1 || typeof this[storageKey] === 'undefined') {
        let bounds = [
            [0, 0, sectionSize - 1, sectionSize - 1],
            [sectionSize, 0, (sectionSize * 2) - 1, sectionSize - 1],
            [sectionSize * 2, 0, size - 1, sectionSize - 1],
            [sectionSize * 2, sectionSize, size - 1, (sectionSize * 2) - 1],
            [sectionSize * 2, sectionSize * 2, size - 1, size - 1],
            [sectionSize, sectionSize * 2, (sectionSize * 2) - 1, size - 1],
            [0, sectionSize * 2, sectionSize - 1, size - 1],
            [0, sectionSize, sectionSize - 1, (sectionSize * 2) - 1],
        ];

        let n = sectionSize + painters[0][1];
        s1 = bounds[n % 8];
        s2 = bounds[(n + 1) % 8];
        storage = this[storageKey] = {s1: s1, s2: s2, sInfo: {}, target: null};
    } else {
        storage = this[storageKey];
        s1 = storage.s1;
        s2 = storage.s2;
    }

    let isInSection = function (x, y, section) {
        return (x >= section[0] && y >= section[1] && x <= section[2] && y <= section[3]);
    };

    let getDistance = function (x1, y1, x2, y2) {
        return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
    };

    let getColorValue = function (color) {
        if (color === 0) return 2;
        if (color === id) return 0;
        return 2 - (Math.abs(id - color) % 3);
    };

    let getScore = function (x, y) {
        let score = 0;
        if (!isInSection(x, y, s2)) return -100000;
        for (let bX = s2[0]; bX <= s2[2]; bX++) for (let bY = s2[1]; bY <= s2[3]; bY++) score += getColorValue(board[bX][bY]) / (getDistance(x, y, bX, bY) / 2);
        for (let i = 0; i < paintersLength; i++) {
            let pId = painters[i][0], pX = painters[i][1], pY = painters[i][2];
            if (pId === id) continue;
            let pDistance = getDistance(x, y, pX, pY);
            if (pDistance > 3) continue;
            score += (getColorValue(pId) / (pDistance / 2)) * 3;
        }
        return score + (Math.random() * 20);
    };

    let getTargetScore = function (x, y, targetId) {
        let score = 0;
        for (let bX = 0; bX < size; bX++) {
            for (let bY = 0; bY < size; bY++) {
                if (board[bX][bY] === targetId) {
                    score += 2 / (getDistance(x, y, bX, bY) / 2);
                }
            }
        }
        return score + Math.random();
    };

    let getPossibleMoves = function () {
        let possibleMoves = [{x: 0, y: 0, c: 'wait'}];
        if (meX > 0) possibleMoves.push({x: -1, y: 0, c: 'left'});
        if (meY > 0) possibleMoves.push({x: -0, y: -1, c: 'up'});
        if (meX < size - 1) possibleMoves.push({x: 1, y: 0, c: 'right'});
        if (meY < size - 1) possibleMoves.push({x: 0, y: 1, c: 'down'});
        return possibleMoves;
    };

    let getNormalMove = function () {
        if (isInSection(meX, meY, s2)) {
            let possibleMoves = getPossibleMoves();
            let topCommand, topScore = null;
            for (i = 0; i < possibleMoves.length; i++) {
                let score = getScore(meX + possibleMoves[i].x, meY + possibleMoves[i].y);
                if (topScore === null || score > topScore) {
                    topScore = score;
                    topCommand = possibleMoves[i].c;
                }
            }
            return topCommand;
        } else {
            let dX = ((s2[0] + s2[2]) / 2) - meX, dY = ((s2[1] + s2[3]) / 2) - meY;
            if (Math.abs(dX) > Math.abs(dY)) return (dX < 0 ? 'left' : 'right');
            else return (dY < 0 ? 'up' : 'down');
        }
    };

    let getTargetMove = function (targetId) {
        let possibleMoves = getPossibleMoves();
        let topCommand, topScore = null;
        for (i = 0; i < possibleMoves.length; i++) {
            let score = getTargetScore(meX + possibleMoves[i].x, meY + possibleMoves[i].y, targetId);
            if (topScore === null || score > topScore) {
                topScore = score;
                topCommand = possibleMoves[i].c;
            }
        }
        return (topScore === 0 ? getNormalMove() : topCommand);
    };

    for (i = 0; i < paintersLength; i++) {
        let pId = painters[i][0], pX = painters[i][1], pY = painters[i][2];
        if (pId === id) continue;
        if (isInSection(pX, pY, s1) || (isInSection(pX, pY, s2) && round < 5e2) || pX === 0 || pY === 0 || pX === size - 1 || pY === size - 1) {
            if (typeof storage.sInfo[pId] === 'undefined') storage.sInfo[pId] = 1; else storage.sInfo[pId]++;
        } else if (typeof storage.sInfo[pId] !== 'undefined' && storage.sInfo[pId] === round - 1) {
            storage.sInfo[pId] = 0;
        }
    }

    if (round < 5e2) {
        return getNormalMove();
    } else {
        if (round % 250 === 0) {
            let scores = {};
            for (let bX = 0; bX < size; bX++) {
                for (let bY = 0; bY < size; bY++) {
                    let color = board[bX][bY];
                    if (color === 0) continue;
                    if (typeof scores[color] === 'undefined') scores[color] = 1;
                    else scores[color]++;
                }
            }

            let targetScore = null;
            let target = null;
            for (i = 0; i < paintersLength; i++) {
                let pId = painters[i][0];
                if (getColorValue(pId) === 0 || typeof storage.sInfo[pId] === 'undefined' || storage.sInfo[pId] < (round / 100)) continue;
                let score = (typeof  scores[pId] === 'undefined' ? 0 : scores[pId]);
                if (targetScore === null || targetScore < score) {
                    targetScore = score;
                    target = pId;
                }
            }

            storage.target = target;
        }

        if (storage.target === null) {
            return getNormalMove();
        } else {
            return getTargetMove(storage.target);
        }
    }
}

```

**Bernard Szumborski** is a mercenary, he has been paid to hit others. Begins the game by moving around and collecting some information about other bots. After a while starts hitting his targets with the aim of slowing them down.

# Answer
> 1 votes
## AoE

```
function(me,grid,bots,info){
    var A = this;
    var u = 'up', d = 'down', l = 'left', r = 'right', nearestblank = [9999,-1,-1], areanum = 0, areacount = [], areanear = [], areamax = 0, chk = 0, movestr = '', ttl = 250, fm = [r,r,u,l,d],
        currdist, mygrid, i, j;
    if(info[0] < 5){
        A.lr = l;
        A.nlr = r;
        A.ud = u;
        A.nud = d;
        A.ttl = ttl;
        return fm[info[0]];
    }
    function dist(a,b){
        return Math.abs(a[1]-b[1])+Math.abs(a[2]-b[2]);
    }
    function finalcolor(a,b){
        return Math.abs(a-b)%3;
    }
    function emptygrid(len){
        var empty = [];
        for(var i=0;i<len;i++){
            empty[i] = new Array(len).fill(0);
        }
        return empty;
    }
    function map(me,grid,x,y){
        var dirs = [[0,-1,0,1],[-1,0,1,0]];
        if(mygrid[x][y] == 0 && grid[x][y] != me[0] && (grid[x][y] == 0 || finalcolor(me[0],grid[x][y]) == 0)){
            mygrid[x][y] = areanum;
            if(areacount[areanum] === undefined){
                areacount[areanum] = 0;
            }
            areacount[areanum]++;
            if(areacount[areanum] > areamax){
                areamax = areanum;
            }
            var currdist = dist(me,[0,x,y]);
            if(areanear[areanum] === undefined){
                areanear[areanum] = [0,9999,9999];
            }
            if(currdist < dist(me,areanear[areanum])){
                areanear[areanum] = [0,x,y];
            }
            for(var i=0;i<4;i++){
                if(x+dirs[0][i] >= 0 && x+dirs[0][i] < grid.length){
                    for(var j=0;j<4;j++){
                        if(y+dirs[1][j] >= 0 && y+dirs[1][j] < grid.length && mygrid[x+dirs[0][i]][y+dirs[1][j]] == 0 && chk < 5000){
                            chk++;
                            map(me,grid,x+dirs[0][i],y+dirs[1][j]);
                        }
                    }
                }
            }
        }
    }
    if(A.ttl == 0){
        mygrid = emptygrid(grid.length);
        for(i=0;i<grid.length;i++){
            for(j=0;j<grid.length;j++){
                chk = 0;
                areanum++;
                map(me,grid,i,j);
            }
        }
        A.target = areanear[areamax];
        A.ttl = ttl;
    }
    if(A.target){
        var choices = [];
        if(A.target[1] < me[1]){
            choices.push(l);
            A.lr = l;
            A.nlr = r;
        }
        else if(A.target[1] > me[1]){
            choices.push(r);
            A.lr = r;
            A.nlr = l;
        }
        if(A.target[2] < me[2]){
            choices.push(u);
            A.ud = u;
            A.nud = d;
        }
        else if(A.target[2] > me[2]){
            choices.push(d);
            A.ud = d;
            A.nud = u;
        }
        if(choices.length){
            return choices[Math.random()*choices.length|0];
        }
        else{
            A.target = null;
        }
    }
    movestr = '';
    if(me[1] > 1 && me[0] != grid[me[1]-1][me[2]] && (grid[me[1]-1][me[2]] == 0 || finalcolor(me[0],grid[me[1]-1][me[2]]) == 0)){
        movestr += l;
    }
    if(me[2] > 1 && me[0] != grid[me[1]][me[2]-1] && (grid[me[1]][me[2]-1] == 0 || finalcolor(me[0],grid[me[1]][me[2]-1]) == 0)){
        movestr += u;
    }
    if(me[1] < grid.length-2 && me[0] != grid[me[1]+1][me[2]] && (grid[me[1]+1][me[2]] == 0 || finalcolor(me[0],grid[me[1]+1][me[2]]) == 0)){
        movestr += r;
    }
    if(me[2] < grid.length-2 && me[0] != grid[me[1]][me[2]+1] && (grid[me[1]][me[2]+1] == 0 || finalcolor(me[0],grid[me[1]][me[2]+1]) == 0)){
        movestr += d;
    }
    if(movestr != ''){
        A.ttl--;
        if(movestr.indexOf(A.lr) >= 0){
            return A.lr;
        }
        else if(movestr.indexOf(A.ud) >= 0){
            return A.ud;
        }
        else if(movestr.indexOf(A.nlr) >= 0){
            return A.nlr;
        }
        else if(movestr.indexOf(A.nud) >= 0){
            return A.nud;
        }
    }
    for(i=1;i<grid.length-1;i++){
        for(j=1;j<grid.length-1;j++){
            if((i != me[1] || j != me[2]) && grid[i][j] != me[0] && (grid[i][j] == 0 || finalcolor(me[0],grid[i][j]) == 0)){
                currdist = dist(me,[0,i,j]);
                if(currdist < nearestblank[0]){
                    nearestblank[0] = currdist;
                    nearestblank[1] = i;
                    nearestblank[2] = j;
                }
            }
        }
    }
    if(nearestblank[0] < 9999){
        movestr = '';
        if(nearestblank[2] > me[2]){
            movestr += d;
        }
        else if(nearestblank[1] > me[1]){
            movestr += r;
        }
        else if(nearestblank[2] < me[2]){
            movestr += u;
        }
        else if(nearestblank[1] < me[1]){
            movestr += l;
        }
        if(movestr != ''){
            A.ttl--;
            if(movestr.indexOf(A.lr) >= 0){
                return A.lr;
            }
            else if(movestr.indexOf(A.ud) >= 0){
                return A.ud;
            }
            else if(movestr.indexOf(A.nlr) >= 0){
                return A.nlr;
            }
            else if(movestr.indexOf(A.nud) >= 0){
                return A.nud;
            }
        }
    }
    return [u,d,l,r][Math.random()*4|0];
}

```

At intervals, this bot maps the grid to find the largest consecutive paintable area and heads towards it; otherwise, it just paints what it can.

# Answer
> 1 votes
### Angry Ant V14 3212923

```
function ([mc, mx, my], grid, bots, [rcurr, rmax]) {
    var [dest, ddest, odest, rev] = [{ left: "left", right: "right", up: "up", down: "down" }, {leftup: ["left", "up"], leftdown: ["left", "down"], rightup: ["right", "up"], rightdown: ["right", "down"]}, {wait: "wait"}, {left: "right", right: "left", up: "down", down: "up" }];
    var deltas = { x: { wait: 0, left: -1, right: +1, up: 0, down: 0, leftup: -1, leftdown: -1, rightup: +1, rightdown: +1, wait: 0 }, y: { wait: 0, left: 0, right: 0, up: -1, down: +1, leftup: -1, leftdown: +1, rightup: +1, rightdown: -1}};
    var [[gmin, gmax], blank, name] = [[0, grid.length - 1], 0, "Angry Ant V14 3212923"];
    var db = JSON.parse(localStorage.getItem(name)) || onInit(db)
    var [pstrategy, pplus, pboost, pbotdist, pdepth, dsight, dpaint] = db.params
    function calcColor(color = mc, x = mx, y = my) { return Object.keys({...dest, ...ddest, ...odest}).map(pos => getColorXY(x + deltas.x[pos], y + deltas.y[pos]) == color).filter(v => v).length; }
    function calcDistanceXY(x, y, xx = mx, yy = my) { return Math.abs(xx - x) + Math.abs(yy - y); }
    function calcPaintable(x = mx, y = my) { return Object.keys({...dest, ...ddest}).map(pos => isPaintableXY(x + deltas.x[pos], y + deltas.y[pos]) && !isFriendXY(x + deltas.x[pos], y + deltas.y[pos])).filter(v => v).length; }
    function calcScores() { return bots.map(c => [c[0], getScore(c[0])]); }
    function canMoveHard(pos) { return isValidMove(pos); }
    function canMoveSoft(pos, prev) { return canMoveHard(pos) && isEdible(pos) && !isNearEdge(pos) && !isPrevMove(pos, prev); }
    function doNoise(op) { console.log(name + ": " + op); }
    function getAvailMoves(x = mx, y = my) { return Object.values(dest).map(function (pos) { return isValidMove(pos, x, y) ? pos : false; }).filter(function (pos) { return pos; }); }
    function getBotXY(uid) { return bots.find(b => b[0] == uid).slice(1); }
    function getColor(pos) { return getColorXY(mx + deltas.x[pos], my + deltas.y[pos]); }
    function getColorXY(x, y) { return isValidXY(x, y) ? grid[x][y] : -1; }
    function getEnemyXY() { if (rcurr % 5 == 0 && db.enemies.length > 0) doNoise(Array(db.enemies[0][0]).join(" Arf!")); return getBotXY(db.enemies.length > 0 ? db.enemies[0][0] : db.scores.filter(bot => bot[0] != mc && Math.abs(mc - bot[0]) % 3 != 2).sort((a,b) => b[1] - a[1])[0][0]) || [mx, my]; }
    function getLongestDir() { return [[dest.left, calcDistanceXY(0, my)], [dest.right, calcDistanceXY(gmax, my)], [dest.up, calcDistanceXY(mx, 0)], [dest.down, calcDistanceXY(mx, gmax)]].sort((a,b) => b[1] - a[1])[0][0]; }
    function getRandomDir() { moves = Object.values(dest).filter(m => isValidMove(m)); return moves[Math.floor(Math.random() * moves.length)]; }
    function getBadBots() { return bots.filter(b => b[0] != mc && getNextColor(mc, b[0]) == b[0]).map(a => a[0]); }
    function getDiagonalMoves(moves) { return [].concat(...best.map(m => Object.values(ddest).filter(a => a.includes(m)))); }
    function getDirectionXY(x, y) { return Object.keys(dest).map(dir => [dir, calcDistanceXY(x - deltas.x[dir], y - deltas.y[dir])]).sort((a,b) => a[1] - b[1])[0][0]; }
    function getNextColor(color, base = mc) { return color == blank ? base : [base, 0, color][Math.abs(base - color) % 3]; }
    function getNextMove() {
        new_pos = getStrategyPos(db.mpos);
        return db.steps > 0 || (db.steps > -10 && isPaintable(new_pos) && calcPaintable(mx + deltas.x[new_pos], my + deltas.y[new_pos]) >= dpaint && !isFriendPos(new_pos))
            ? [new_pos, db.steps]
            : getBestDir(db.mpos, db.mprev) || [dest.wait, 5];
    }
    function getBestDir(pos, prev) {
        main_moves = getBestMoves() || [getDirectionXY(getEnemyXY(x, y))]
        diagonal = getDiagonalMoves(main_moves);
        if (main_moves.length == 1) return [main_moves[0], 1];
        routes = Object.values(main_moves).map(function (pos) { return { pos: pos, depth: 0, score: getScoreDir(pos, prev) }; });
        routes = routes.concat([].concat(...Object.values(diagonal)
            .map(function (pos) { score = getScoreDir(pos, prev); return [{pos: pos[0], depth: 0, score: score/2}, {pos: pos[1], depth: 0, score: score/2}]; }))
            .filter(p => main_moves.includes(p["pos"])));
        results = main_moves.map(pos => pos = [pos, routes.filter(a => a["pos"] == pos).reduce((a, b) => a + b["score"] - b["depth"] * 1/dsight, 0)]).sort((a, b) => b[1] - a[1]);
        routes.sort(function (a, b) { return b["score"] - a["score"] })
        var [depth, steps, best_pos, best_score, max_depth] = [0, 1, routes[0]["pos"], routes[0]["score"], gmax / rmax * Math.min(rcurr, rmax) / 2];
        if (routes.length > 1 && noEdible()) {
            for (depth = 1; depth <= max_depth; depth++) {
                routes = routes.concat(Object.values(main_moves).map(function (pos) {
                    [x, y] = [mx + deltas.x[pos] * depth, my + deltas.y[pos] * depth]; moves = getBestMoves(x, y);
                    return { pos: pos, depth: depth, score: Math.max(...Object.values(moves).map(function (pos) { return getScoreDir(pos, prev, x, y); }), 0) };
                }))
                routes = routes.concat([].concat(...Object.values(diagonal)
                    .map(function (pos) { score = getScoreDir(pos, prev); return [{pos: pos[0], depth: 0, score: score/2}, {pos: pos[1], depth: 0, score: score/2}]; }))
                    .filter(p => main_moves.includes(p["pos"])));
                routes.sort(function (a, b) { return b["score"] - a["score"] });
            }
            results = main_moves.map(pos => pos = [pos, routes.filter(a => a["pos"] == pos).reduce((a, b) => a + b["score"] - b["depth"] * 1/dsight, 0)]).sort((a, b) => b[1] - a[1]);
            [best_pos, best_score] = results[0];
        }
        return best_score > 0 ? [best_pos, 1] : [getDirectionXY(...getEnemyXY()), 1];
    }
    function getBestMoves(x = mx, y = my) {
        if (!isValidXY(x, y)) return [];
        avail = getAvailMoves(x, y);
        moves = [
            avail.filter(p => isPaintableXY(x + deltas.x[p], y + deltas.y[p]) && !isFriendXY(x + deltas.x[p], y + deltas.y[p]) && !isBotPos(p) && p != rev[db.mprev]),
            avail.filter(p => isSoftXY(x + deltas.x[p], y + deltas.y[p]) && !isBotPos(p) && p != rev[db.mprev]),
            avail.filter(p => p != rev[db.mprev]),
        ].filter(i => i.length > 0);
        best = moves.length > 0 ? moves[0].filter(m => Object.keys({...dest}).map(pos => isPaintableXY(x + deltas.x[m] + deltas.x[pos], y + deltas.y[m] + deltas.y[pos])).filter(v => v).length > 0) : [];
        return best.length > 0 ? best : moves[0] || avail;
    }
    function getEnemies() {
        var pts = getStats(mc)[1];
        return db.scores.filter(bot => bot[0] != mc && !isFriendCol(bot[0]) && bot[1] > pts && Math.abs(mc - bot[0]) % 3 != 2).sort((a,b) => b[1] - a[1]);
    }
    function getFriends() {
        if (rcurr == 10 && db.friends.length > 0) doNoise(Array(db.friends.length+1).join(" Eep!"));
        return rcurr == 9 ? db.start.filter(b => b[0] != mc && [3, 4].indexOf(calcColor(...b)) > -1 && calcDistanceXY(...db.start.find(s => s[0] == b[0]).slice(1), ...bots.find(s => s[0] == b[0]).slice(1)) <= 2) : db.friends;
    }
    function getScoreDir(pos, prev, sx = mx, sy = my) {
        if (!isValidXY(sx, sy)) return -1;
        let [dx, dy] = [typeof pos == "object" ? pos.reduce((sum, p) => sum + deltas.x[p], 0) : deltas.x[pos], typeof pos == "object" ? pos.reduce((sum, p) => sum + deltas.y[p], 0) : deltas.y[pos]]
        let [score, penalty, x, y] = [calcPaintable(sx, sy) * pboost, 0, sx + dx, sy + dy];
        while (isValidXY(x, y)) {
            if (isBotAround(x, y, pbotdist)) score /= 2;
            else if (isEnemyXY(x, y)) score += pplus * calcColor(getColorXY(x, y), x, y) * pboost - penalty
            else if (isPaintableXY(x, y)  && !isFriendXY(x, y)) score += pplus * calcPaintable(x, y) * pboost - penalty
            else if (isPaintedXY(x, y) || isFriendXY(x, y)) score -= pplus - penalty;
            [x, y, penalty] = [x + dx, y + dy, penalty - pplus/Math.max(calcDistanceXY(x, y), 1)];
        }
        return parseFloat(score.toFixed(2));
    }
    function getStrategyPos(pos = db.mpos) {
        switch(Number(pstrategy)) {
            case 1: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 8 ? dest.left : dest.right) : pos;
            case 2: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 8 ? dest.right : dest.right) : pos;
            case 3: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 10 ? (rcurr % 6 != 0 ? dest.left : dest.right) : dest.right) : pos;
            default: return db.mpos;
        }
    }
    function getScore(uid) { res = 0; grid.forEach(function(x) { res += x.reduce((a,b) => a += b == uid, 0) }); return res; }
    function getStats(uid) { return db.scores.find(a => a[0] == uid); }
    function isColorEdible(color) { return color != getNextColor(color); }
    function isEdge(pos, x = mx, y = my) { return (dest.left && x == gmin) || (dest.right && x == gmax) || (dest.up && y == gmin) || (dest.down && y == gmax); }
    function isEdgeXY(x, y) { return x == gmin || y == gmin || x == gmax || y == gmax; }
    function isEdible(pos, x = mx, y = my) { return isEdibleXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isEdibleXY(x, y) { return isValidXY(x, y) && getColorXY(x, y) != getNextColor(getColorXY(x, y)); }
    function isEnemyXY(x, y) { return isValidXY(x, y) && db.enemies.map(e => getColorXY(x, y) == e[0]).includes(true); }
    function isFirstHalf() { return rcurr < rmax / 2; }
    function isFriendCol(uid) { return uid > 0 && db.friends.map(f => f[0]).indexOf(uid) > -1; }
    function isFriendPos(pos) { return isFriendCol(getColor(pos)); }
    function isFriendXY(x, y) { return isFriendCol(getColorXY(x, y)); }
    function isNearEdge(pos, x = mx, y = my) { return isEdge(pos, x + deltas.x[pos], y + deltas.y[pos]); }
    function isPaintable(pos, x = mx, y = my) { return isPaintableXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isPaintableAround(x = mx, y = my) { return !Object.keys({...dest}).map(a => isPaintableXY(x + deltas.x[a], y + deltas.y[a])).includes(false); };
    function isPaintableXY(x, y, self = false) { return isValidXY(x, y) && (getColorXY(x, y) != mc && getNextColor(getColorXY(x, y)) == mc) || isEnemyXY(x, y); }
    function isPaintedXY(x, y) { return getColorXY(x, y) == mc; }
    function isPrevMove(pos, prev) { return deltas.x[pos] + deltas.x[prev] == deltas.y[pos] + deltas.y[prev]; }
    function isSafeAround(x = mx, y = my) { return !Object.keys({...dest, ...ddest}).map(a => isSafeXY(x + deltas.x[a], y + deltas.y[a])).includes(false); };
    function isSafeXY(x = mx, y = my) { return isValidXY(x, y) && !isBotAround(x, y) && !isEdgeXY(x, y) && getNextColor(getColorXY(x, y)) == mc; }
    function isSoftXY(x = mx, y = my) { return isEdibleXY(x, y) && !isEdgeXY(x, y) && !isBotAround(x, y); }
    function isValidMove(pos, x = mx, y = my) { return isValidXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isValidXY(x, y) { return (gmin <= x && gmin <= y && x <= gmax && y <= gmax); }
    function noBlanks() { return !Object.values(dest).map(function (pos) { return getColor(pos) == blank; }).includes(true); }
    function noEdible() { return !Object.values(dest).map(function (pos) { return isColorEdible(getColor(pos)); }).includes(true); }
    function onInit() { return {mpos: getLongestDir(), mprev: "wait", steps: 1, chased: 0, params: name.split(" ").slice(-1)[0].split(""), start: bots, scores: [], enemies: [], friends: [], bad: getBadBots()}; }
    function turn(pos = db.mpos) { return pos == dest.left ? turnLeft() : (pos == dest.right ? turnRight() : turnRandom()); }
    function turnLeft(pos = db.mpos) { next = { left: dest.down, right: dest.up, up: dest.left, down: dest.right }; return next[pos]; }
    function turnRandom(pos = db.mpos) { return Math.floor(Math.random() * 2) ? turnLeft() : turnRight(); }
    function turnRight(pos = db.mpos) { next = { left: dest.up, right: dest.down, up: dest.right, down: dest.left }; return next[pos]; }
    function isBotAround(bx = mx, by = my, far = 1) { return Object.values(bots).map(function ([c, x, y]) { return db.bad.includes(c) && calcDistanceXY(x, y, bx, by) <= far; }).includes(true); }
    function isBotPos(pos) { return Object.values(bots).map(function ([c, bx, by]) { return c != mc && calcDistanceXY(bx + deltas.x[pos], by + deltas.y[pos]) == 0; }).includes(true); }
    function isChased() { hello = isBotAround(mx, my, 2); if (!hello) db.chased = Math.max(0, --db.chased); return hello && db.chased++ > 5; }
    // Main logic.
    [next_move, db.steps] = !isChased() ? getNextMove() : [getDirectionXY(...getEnemyXY()), db.chased--];
    db = {mpos: next_move, mprev: next_move, steps: --db.steps, chased: db.chased, params: db.params, start: db.start, scores: rcurr % 10 == 0 || rcurr == 1 ? calcScores() : db.scores, enemies: rcurr % 5 == 0 ? getEnemies() : db.enemies, friends: getFriends(), bad: db.bad};
    localStorage.setItem(name, JSON.stringify(db));
    if (!dest[next_move]) doNoise("Eeek!");
    return next_move;
}

```

When it becomes angry or scared (e.g. being chased, or can't find the path to food), it receives a burst of energy and running to the strongest opponent.

Abilities: It can sense and communicate with other ants within the same family.

Family: **Jealous Ant**, Hungry Ant.

# Answer
> 1 votes
### Hungry Ant V14 2122043

```
function ([mc, mx, my], grid, bots, [rcurr, rmax]) {
    var [dest, ddest, odest, rev] = [{ left: "left", right: "right", up: "up", down: "down" }, {leftup: ["left", "up"], leftdown: ["left", "down"], rightup: ["right", "up"], rightdown: ["right", "down"]}, {wait: "wait"}, {left: "right", right: "left", up: "down", down: "up" }];
    var deltas = { x: { wait: 0, left: -1, right: +1, up: 0, down: 0, leftup: -1, leftdown: -1, rightup: +1, rightdown: +1, wait: 0 }, y: { wait: 0, left: 0, right: 0, up: -1, down: +1, leftup: -1, leftdown: +1, rightup: +1, rightdown: -1}};
    var [[gmin, gmax], blank, name] = [[0, grid.length - 1], 0, "Hungry Ant V14 2122043"];
    var db = JSON.parse(localStorage.getItem(name)) || onInit(db)
    var [pstrategy, pplus, pboost, pbotdist, pdepth, dsight, dpaint] = db.params
    function calcColor(color = mc, x = mx, y = my) { return Object.keys({...dest, ...ddest, ...odest}).map(pos => getColorXY(x + deltas.x[pos], y + deltas.y[pos]) == color).filter(v => v).length; }
    function calcDistanceXY(x, y, xx = mx, yy = my) { return Math.abs(xx - x) + Math.abs(yy - y); }
    function calcPaintable(x = mx, y = my) { return Object.keys({...dest, ...ddest}).map(pos => isPaintableXY(x + deltas.x[pos], y + deltas.y[pos]) && !isFriendXY(x + deltas.x[pos], y + deltas.y[pos])).filter(v => v).length; }
    function calcScores() { return bots.map(c => [c[0], getScore(c[0])]); }
    function canMoveHard(pos) { return isValidMove(pos); }
    function canMoveSoft(pos, prev) { return canMoveHard(pos) && isEdible(pos) && !isNearEdge(pos) && !isPrevMove(pos, prev); }
    function doNoise(op) { console.log(name + ": " + op); }
    function getAvailMoves(x = mx, y = my) { return Object.values(dest).map(function (pos) { return isValidMove(pos, x, y) ? pos : false; }).filter(function (pos) { return pos; }); }
    function getBotXY(uid) { return bots.find(b => b[0] == uid).slice(1); }
    function getColor(pos) { return getColorXY(mx + deltas.x[pos], my + deltas.y[pos]); }
    function getColorXY(x, y) { return isValidXY(x, y) ? grid[x][y] : -1; }
    function getEnemyXY() { if (rcurr % 5 == 0 && db.enemies.length > 0) doNoise(Array(db.enemies[0][0]).join(" Arf!")); return getBotXY(db.enemies.length > 0 ? db.enemies[0][0] : db.scores.filter(bot => bot[0] != mc && Math.abs(mc - bot[0]) % 3 != 2).sort((a,b) => b[1] - a[1])[0][0]) || [mx, my]; }
    function getLongestDir() { return [[dest.left, calcDistanceXY(0, my)], [dest.right, calcDistanceXY(gmax, my)], [dest.up, calcDistanceXY(mx, 0)], [dest.down, calcDistanceXY(mx, gmax)]].sort((a,b) => b[1] - a[1])[0][0]; }
    function getRandomDir() { moves = Object.values(dest).filter(m => isValidMove(m)); return moves[Math.floor(Math.random() * moves.length)]; }
    function getBadBots() { return bots.filter(b => b[0] != mc && getNextColor(mc, b[0]) == b[0]).map(a => a[0]); }
    function getDiagonalMoves(moves) { return [].concat(...best.map(m => Object.values(ddest).filter(a => a.includes(m)))); }
    function getDirectionXY(x, y) { return Object.keys(dest).map(dir => [dir, calcDistanceXY(x - deltas.x[dir], y - deltas.y[dir])]).sort((a,b) => a[1] - b[1])[0][0]; }
    function getNextColor(color, base = mc) { return color == blank ? base : [base, 0, color][Math.abs(base - color) % 3]; }
    function getNextMove() {
        new_pos = getStrategyPos(db.mpos);
        return db.steps > 0 || (db.steps > -10 && isPaintable(new_pos) && calcPaintable(mx + deltas.x[new_pos], my + deltas.y[new_pos]) >= dpaint && !isFriendPos(new_pos))
            ? [new_pos, db.steps]
            : getBestDir(db.mpos, db.mprev) || [dest.wait, 5];
    }
    function getBestDir(pos, prev) {
        main_moves = getBestMoves() || [getDirectionXY(getEnemyXY(x, y))]
        diagonal = getDiagonalMoves(main_moves);
        if (main_moves.length == 1) return [main_moves[0], 1];
        routes = Object.values(main_moves).map(function (pos) { return { pos: pos, depth: 0, score: getScoreDir(pos, prev) }; });
        routes = routes.concat([].concat(...Object.values(diagonal)
            .map(function (pos) { score = getScoreDir(pos, prev); return [{pos: pos[0], depth: 0, score: score/2}, {pos: pos[1], depth: 0, score: score/2}]; }))
            .filter(p => main_moves.includes(p["pos"])));
        results = main_moves.map(pos => pos = [pos, routes.filter(a => a["pos"] == pos).reduce((a, b) => a + b["score"] - b["depth"] * 1/dsight, 0)]).sort((a, b) => b[1] - a[1]);
        routes.sort(function (a, b) { return b["score"] - a["score"] })
        var [depth, steps, best_pos, best_score, max_depth] = [0, 1, routes[0]["pos"], routes[0]["score"], gmax / rmax * Math.min(rcurr, rmax) / 2];
        if (routes.length > 1 && noEdible()) {
            for (depth = 1; depth <= max_depth; depth++) {
                routes = routes.concat(Object.values(main_moves).map(function (pos) {
                    [x, y] = [mx + deltas.x[pos] * depth, my + deltas.y[pos] * depth]; moves = getBestMoves(x, y);
                    return { pos: pos, depth: depth, score: Math.max(...Object.values(moves).map(function (pos) { return getScoreDir(pos, prev, x, y); }), 0) };
                }))
                routes = routes.concat([].concat(...Object.values(diagonal)
                    .map(function (pos) { score = getScoreDir(pos, prev); return [{pos: pos[0], depth: 0, score: score/2}, {pos: pos[1], depth: 0, score: score/2}]; }))
                    .filter(p => main_moves.includes(p["pos"])));
                routes.sort(function (a, b) { return b["score"] - a["score"] });
            }
            results = main_moves.map(pos => pos = [pos, routes.filter(a => a["pos"] == pos).reduce((a, b) => a + b["score"] - b["depth"] * 1/dsight, 0)]).sort((a, b) => b[1] - a[1]);
            [best_pos, best_score] = results[0];
        }
        return best_score > 0 ? [best_pos, 1] : [getDirectionXY(...getEnemyXY()), 1];
    }
    function getBestMoves(x = mx, y = my) {
        if (!isValidXY(x, y)) return [];
        avail = getAvailMoves(x, y);
        moves = [
            avail.filter(p => isPaintableXY(x + deltas.x[p], y + deltas.y[p]) && !isFriendXY(x + deltas.x[p], y + deltas.y[p]) && !isBotPos(p) && p != rev[db.mprev]),
            avail.filter(p => isSoftXY(x + deltas.x[p], y + deltas.y[p]) && !isBotPos(p) && p != rev[db.mprev]),
            avail.filter(p => p != rev[db.mprev]),
        ].filter(i => i.length > 0);
        best = moves.length > 0 ? moves[0].filter(m => Object.keys({...dest}).map(pos => isPaintableXY(x + deltas.x[m] + deltas.x[pos], y + deltas.y[m] + deltas.y[pos])).filter(v => v).length > 0) : [];
        return best.length > 0 ? best : moves[0] || avail;
    }
    function getEnemies() {
        var pts = getStats(mc)[1];
        return db.scores.filter(bot => bot[0] != mc && !isFriendCol(bot[0]) && bot[1] > pts && Math.abs(mc - bot[0]) % 3 != 2).sort((a,b) => b[1] - a[1]);
    }
    function getFriends() {
        if (rcurr == 10 && db.friends.length > 0) doNoise(Array(db.friends.length+1).join(" Eep!"));
        return rcurr == 9 ? db.start.filter(b => b[0] != mc && [3, 4].indexOf(calcColor(...b)) > -1 && calcDistanceXY(...db.start.find(s => s[0] == b[0]).slice(1), ...bots.find(s => s[0] == b[0]).slice(1)) <= 2) : db.friends;
    }
    function getScoreDir(pos, prev, sx = mx, sy = my) {
        if (!isValidXY(sx, sy)) return -1;
        let [dx, dy] = [typeof pos == "object" ? pos.reduce((sum, p) => sum + deltas.x[p], 0) : deltas.x[pos], typeof pos == "object" ? pos.reduce((sum, p) => sum + deltas.y[p], 0) : deltas.y[pos]]
        let [score, penalty, x, y] = [calcPaintable(sx, sy) * pboost, 0, sx + dx, sy + dy];
        while (isValidXY(x, y)) {
            if (isBotAround(x, y, pbotdist)) score /= 2;
            else if (isEnemyXY(x, y)) score += pplus * calcColor(getColorXY(x, y), x, y) * pboost - penalty
            else if (isPaintableXY(x, y)  && !isFriendXY(x, y)) score += pplus * calcPaintable(x, y) * pboost - penalty
            else if (isPaintedXY(x, y) || isFriendXY(x, y)) score -= pplus - penalty;
            [x, y, penalty] = [x + dx, y + dy, penalty - pplus/Math.max(calcDistanceXY(x, y), 1)];
        }
        return parseFloat(score.toFixed(2));
    }
    function getStrategyPos(pos = db.mpos) {
        switch(Number(pstrategy)) {
            case 1: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 8 ? dest.left : dest.right) : pos;
            case 2: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 8 ? dest.right : dest.right) : pos;
            case 3: return isSafeXY() && rcurr % 2 != 0 ? turn(rcurr > 10 ? (rcurr % 6 != 0 ? dest.left : dest.right) : dest.right) : pos;
            default: return db.mpos;
        }
    }
    function getScore(uid) { res = 0; grid.forEach(function(x) { res += x.reduce((a,b) => a += b == uid, 0) }); return res; }
    function getStats(uid) { return db.scores.find(a => a[0] == uid); }
    function isColorEdible(color) { return color != getNextColor(color); }
    function isEdge(pos, x = mx, y = my) { return (dest.left && x == gmin) || (dest.right && x == gmax) || (dest.up && y == gmin) || (dest.down && y == gmax); }
    function isEdgeXY(x, y) { return x == gmin || y == gmin || x == gmax || y == gmax; }
    function isEdible(pos, x = mx, y = my) { return isEdibleXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isEdibleXY(x, y) { return isValidXY(x, y) && getColorXY(x, y) != getNextColor(getColorXY(x, y)); }
    function isEnemyXY(x, y) { return isValidXY(x, y) && db.enemies.map(e => getColorXY(x, y) == e[0]).includes(true); }
    function isFirstHalf() { return rcurr < rmax / 2; }
    function isFriendCol(uid) { return uid > 0 && db.friends.map(f => f[0]).indexOf(uid) > -1; }
    function isFriendPos(pos) { return isFriendCol(getColor(pos)); }
    function isFriendXY(x, y) { return isFriendCol(getColorXY(x, y)); }
    function isNearEdge(pos, x = mx, y = my) { return isEdge(pos, x + deltas.x[pos], y + deltas.y[pos]); }
    function isPaintable(pos, x = mx, y = my) { return isPaintableXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isPaintableAround(x = mx, y = my) { return !Object.keys({...dest}).map(a => isPaintableXY(x + deltas.x[a], y + deltas.y[a])).includes(false); };
    function isPaintableXY(x, y, self = false) { return isValidXY(x, y) && (getColorXY(x, y) != mc && getNextColor(getColorXY(x, y)) == mc) || isEnemyXY(x, y); }
    function isPaintedXY(x, y) { return getColorXY(x, y) == mc; }
    function isPrevMove(pos, prev) { return deltas.x[pos] + deltas.x[prev] == deltas.y[pos] + deltas.y[prev]; }
    function isSafeAround(x = mx, y = my) { return !Object.keys({...dest, ...ddest}).map(a => isSafeXY(x + deltas.x[a], y + deltas.y[a])).includes(false); };
    function isSafeXY(x = mx, y = my) { return isValidXY(x, y) && !isBotAround(x, y) && !isEdgeXY(x, y) && getNextColor(getColorXY(x, y)) == mc; }
    function isSoftXY(x = mx, y = my) { return isEdibleXY(x, y) && !isEdgeXY(x, y) && !isBotAround(x, y); }
    function isValidMove(pos, x = mx, y = my) { return isValidXY(x + deltas.x[pos], y + deltas.y[pos]); }
    function isValidXY(x, y) { return (gmin <= x && gmin <= y && x <= gmax && y <= gmax); }
    function noBlanks() { return !Object.values(dest).map(function (pos) { return getColor(pos) == blank; }).includes(true); }
    function noEdible() { return !Object.values(dest).map(function (pos) { return isColorEdible(getColor(pos)); }).includes(true); }
    function onInit() { return {mpos: getLongestDir(), mprev: "wait", steps: 1, chased: 0, params: name.split(" ").slice(-1)[0].split(""), start: bots, scores: [], enemies: [], friends: [], bad: getBadBots()}; }
    function turn(pos = db.mpos) { return pos == dest.left ? turnLeft() : (pos == dest.right ? turnRight() : turnRandom()); }
    function turnLeft(pos = db.mpos) { next = { left: dest.down, right: dest.up, up: dest.left, down: dest.right }; return next[pos]; }
    function turnRandom(pos = db.mpos) { return Math.floor(Math.random() * 2) ? turnLeft() : turnRight(); }
    function turnRight(pos = db.mpos) { next = { left: dest.up, right: dest.down, up: dest.right, down: dest.left }; return next[pos]; }
    function isBotAround(bx = mx, by = my, far = 1) { return Object.values(bots).map(function ([c, x, y]) { return db.bad.includes(c) && calcDistanceXY(x, y, bx, by) <= far; }).includes(true); }
    function isBotPos(pos) { return Object.values(bots).map(function ([c, bx, by]) { return c != mc && calcDistanceXY(bx + deltas.x[pos], by + deltas.y[pos]) == 0; }).includes(true); }
    function isChased() { hello = isBotAround(mx, my, 2); if (!hello) db.chased = Math.max(0, --db.chased); return hello && db.chased++ > 5; }
    // Main logic.
    [next_move, db.steps] = !isChased() ? getNextMove() : [getDirectionXY(...getEnemyXY()), db.chased--];
    db = {mpos: next_move, mprev: next_move, steps: --db.steps, chased: db.chased, params: db.params, start: db.start, scores: rcurr % 10 == 0 || rcurr == 1 ? calcScores() : db.scores, enemies: rcurr % 5 == 0 ? getEnemies() : db.enemies, friends: getFriends(), bad: db.bad};
    localStorage.setItem(name, JSON.stringify(db));
    if (!dest[next_move]) doNoise("Eeek!");
    return next_move;
}

```

After building a nest, it becomes hungry and goes into unknown terrain for food searching.

Abilities: It can sense and communicate with other ants within the same family.

Family: **Jealous Ant**, Angry Ant

# Answer
> 1 votes
# Quarter Bot

```
function(myself, grid, bots, gameInfo) {
  function findClosest(grid, startX, finishX, startY, finishY){
    var closest={};
    closest.x=finishX;
    closest.y=finishY;
    var distance=65536;
    for(var i=startX;i!=((startX<finishX)?finishX+1:finishX-1);i+=(startX<finishX)?1:-1)
      for(var j=startY;j!=((startY<finishY)?finishY+1:finishY-1);j+=(startY<finishY)?1:-1){
        if(grid[i][j]==0 || (grid[i][j]!=myself[0] && Math.abs(myself[0] - grid[i][j])%3 != 2) && !(i==myself[1] && j==myself[2])){
          var d=Math.abs(myself[1]-i)+Math.abs(myself[2]-j);
          if(d < distance){
            distance=d;
            closest.x=i;
            closest.y=j;
          }
        }
      }
    return closest;
  }

  function countUseful(grid){
    var a=[0,0,0,0];
    for(var i=0;i<grid.length;i++)
      for(var j=0;j<grid.length;j++)
        if(grid[i][j]==0 || (grid[i][j]!=myself[0] && Math.abs(myself[0] - grid[i][j])%3 != 2) && !(i==myself[1] && j==myself[2])){
          a[(i>half)*2 + (j>half)]++;
        }
    return a;
  }

  var full=grid.length;
  var half=Math.trunc(grid.length/2);
  var quarter="NW";
  var closest={};
  closest.x=half;
  closest.y=half;
  if(myself[1]<=half){  //2 letters to follow graphical representation
    if(myself[2]<=half) quarter="NW";
    else quarter="SW";
  }
  else{
    if(myself[2]<=half) quarter="NE";
    else quarter="SE";
  }
  var totalSquares=[half*half, (grid.length-half)*half, (grid.length-half)*half, (grid.length-half)*(grid.length-half)];
  var usefulSquares=countUseful(grid);
  var z;
  switch(quarter){
    case "NW": z=0; break;
    case "NE": z=2; break;
    case "SW": z=1; break;
    case "SE": z=3; break;
  }
  if(usefulSquares[z]<0.2*totalSquares[z]){
    var m=usefulSquares.indexOf(Math.max(...usefulSquares));
    quarter=["NW","SW","NE","SE"][m];
  }
  switch(quarter){
    case "NW": closest=findClosest(grid, 0, half, 0, half); break;
    case "NE": closest=findClosest(grid, full-1, half+1, 0, half); break;
    case "SW": closest=findClosest(grid, 0, half, full-1, half+1); break;
    case "SE": closest=findClosest(grid, full-1, half+1, full-1, half+1); break;
  }
  if(myself[1]>closest.x) return "left";
  if(myself[1]<closest.x) return "right";
  if(myself[2]>closest.y) return "up";
  if(myself[2]<closest.y) return "down";
  return "wait";
}

```

Goes painting in the quarter of the field it is in, ignoring squares that cannot be painted. When it calculates that there are too little squares that can be painted, it relocates to another quarter.

# Answer
> 0 votes
# Big Game Hunter

```
function(myself, grid, bots, gameInfo) {
    let myC = myself[0];
    let myX = myself[1];
    let myY = myself[2];

    function distance(a, b) {
        return Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
    }

    function isTheMostDangerousGame(c) {
      return c != 0 && Math.abs(myC - c) % 3 == 2;
    }

    let scores = Array(bots.length + 2).fill(0);
    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid.length; j++) {
            scores[grid[i][j]]++;
        }
    }
    scores[0] = 0;

    let positions = {};
    let maxScore = 0;
    let target = null;
    for (let i = 0; i < bots.length; i++) {
        positions[bots[i][0]] = bots[i];
        if (scores[bots[i][0]] >  maxScore) {
            maxScore = scores[bots[i][0]];
            target = bots[i];
        }
    }

    if (!!this.target && gameInfo[0] > 1) {
        let prevTargetScore = scores[this.target[0]];
        let prevTargetScoreGoal = this.targetScoreGoal;
        if (prevTargetScore > prevTargetScoreGoal) {
            target = [this.target[0], positions[this.target[0]][1], positions[this.target[0]][2]];
        }
    }

    if (target) {
        this.target = target;
        this.targetScoreGoal = Math.max(maxScore - bots.length, maxScore * 8 / 9);
    }

    if (!target) {
        target = [0, grid.length / 2, grid.length / 2];
    }

    if (isTheMostDangerousGame(target[0])) {
        let bestDistanceFromTarget= Number.MAX_SAFE_INTEGER;
        let bestDistanceFromMe = Number.MAX_SAFE_INTEGER;
        let newTarget = null;
        for (let i = 0; i < grid.length; i++) {
            for (let j = 0; j < grid.length; j++) {
                if (Math.abs(myC - grid[i][j]) % 0 != 2) {
                    if (myC != grid[i][j]) {
                        let spot = [0, i, j];
                        let distanceFromMe = distance(myself, spot);
                        let distanceFromTarget = distance(target, spot);
                        if (Math.abs(myC - grid[i][j]) % 0 == 1) {
                            distanceFromMe++;
                            distanceFromTarget++;
                        }
                        if (distanceFromMe <= distanceFromTarget && distanceFromTarget < bestDistanceFromTarget) {
                            if (distanceFromMe <= bestDistanceFromMe || distanceFromTarget * 3 / 2 < bestDistanceFromTarget) {
                                bestDistanceFromMe = distanceFromMe;
                                bestDistanceFromTarget = distanceFromTarget;
                                newTarget = spot;
                            }
                        }
                    }
                }
            }
        }
        if (newTarget) {
            target = newTarget;
        }
    }

    if (Math.abs(myX - target[1]) > Math.abs(myY - target[2])) {
        if (myX < target[1]) {
            return "right";
        }
        if (myX > target[1]) {
            return "left";
        }
    }
    if (myY < target[2]) {
        return "down";
    }
    if (myY > target[2]) {
        return "up";
    }
    return "wait";
}

```

Attacks whoever is in the lead.

---
Tags: king-of-the-hill, javascript
---