61
\$\begingroup\$

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

\$\endgroup\$
22
  • 7
    \$\begingroup\$ Could you translate [botColour, 0, floorColour][Math.abs(botColour - floorColour)%3] into English, please? \$\endgroup\$
    – anon
    Aug 21, 2018 at 0:04
  • 8
    \$\begingroup\$ @NicHartley Bots that have ids that are a multiple of 3 apart can cover each other's paint directly. Bots that are 1 more than a multiple of 3 apart can erase each other's paint, but it takes another round to repaint. Bots that are 2 more than a multiple of 3 apart can't affect each other's paint at all. \$\endgroup\$
    – user48543
    Aug 21, 2018 at 1:02
  • 4
    \$\begingroup\$ @RushabhMehta I'd assume it's much more to do with the [botColour, 0, floorColour][Math.abs(botColour - floorColour)%3] formula, whether or not does a bot get lucky & get to paint over big competitors (or gets painted over). Also take in account the trolls/hunters which can single-handedly destroy a bot of their choosing. Either way, it'll average out on the 10000 games. \$\endgroup\$
    – dzaima
    Aug 21, 2018 at 16:17
  • 9
    \$\begingroup\$ Are there any news on the progress of the tournament? \$\endgroup\$ Aug 31, 2018 at 11:13
  • 3
    \$\begingroup\$ I'm hosting it now if anyone wants to try it. cypressf.com/art-attack github.com/cypressf/art-attack \$\endgroup\$ Sep 25, 2018 at 23:25

50 Answers 50

1
2
2
\$\begingroup\$

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!

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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 }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I just saw John trying to kill Jim... \$\endgroup\$
    – 12Me21
    Aug 23, 2018 at 21:32
2
\$\begingroup\$

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;

    Note: Number of noises corresponds to the number of ants found.

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

    Note: Number of noises corresponds to the number of opponent (uid) responsible for the situation.

  • Eeek! - something got seriously wrong and ant can't move (should never happen);

Family: Angry Ant, Hungry Ant.

\$\endgroup\$
1
\$\begingroup\$

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";
}
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ Why noy use grid.length in the first place? bots.length changes if bots are eliminated. \$\endgroup\$
    – Neil
    Aug 22, 2018 at 18:44
  • \$\begingroup\$ Don't you need to use dir%4 instead of dir in the array index? (Did you actually test this? :p ) \$\endgroup\$
    – tomsmeding
    Aug 22, 2018 at 18:48
  • \$\begingroup\$ @tomsmeding the other problem with this code was that it took too long due to the grid being too large. \$\endgroup\$ Aug 23, 2018 at 18:45
  • \$\begingroup\$ Hmm... someone should make a bot that controls this one by manipulating the colors in the center of the grid. \$\endgroup\$
    – 12Me21
    Aug 24, 2018 at 19:23
  • \$\begingroup\$ @12Me21 Trying to control this bot would be hard because it depends on the ID of the bots that have drawn in the center. \$\endgroup\$ Aug 24, 2018 at 19:26
1
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ With the board getting bigger and bigger and Borderline only getting around maybe 5 times, i feel like the territory to defend should be proportional to the size of the board, since bots at the other end of the board aren't going to set out for Territorial.. The optimal number per size is probably found in infinite simulations. \$\endgroup\$
    – logicBV
    Aug 23, 2018 at 7:26
  • \$\begingroup\$ Perhaps you could automatically increase the size of the territory once it's filled, then at some point you'll find yourself constantly defending your existing territory and so it will stop growing. \$\endgroup\$
    – Neil
    Aug 23, 2018 at 12:29
1
\$\begingroup\$

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.
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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 }
}
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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);
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ How does this determine which bot is TrollBot? \$\endgroup\$
    – Adalynn
    Aug 26, 2018 at 16:52
  • \$\begingroup\$ It detects if a bot is less than 3 units away for more than 5 frames. This can be triggered accidentally, but in that case the other bot will almost always eventually move away as the movement pattern changes, which ends "suicide mode". \$\endgroup\$
    – 12Me21
    Aug 26, 2018 at 17:14
1
\$\begingroup\$

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));
    }
}
\$\endgroup\$
0
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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

\$\endgroup\$
0
\$\begingroup\$

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.

\$\endgroup\$
1
2

Your Answer

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

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