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 }
}
[botColour, 0, floorColour][Math.abs(botColour - floorColour)%3]
into English, please? \$\endgroup\$[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\$