-
-
Save Radvylf/4d9915632fed1b4d7a7e9407e2a38edf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// To include a bot, place it in the botData object. | |
// To run a game, use the function runGame(rounds, turns, log). | |
// Rounds is the number of rounds, turns is the max turns (default is 100000), and log is the log level (0=none, 1=round overview, 2=all collisions/workers) | |
// To draw a round, use the function drawRound(turns, log, fps, zoom). Defaults to log level 2. | |
// Example bot is included. | |
var botData = [ | |
{ | |
name: "ExampleBot", | |
color: "#aaaaaa", | |
run: () => dirTo(chars().sort((a, b) => dist(center(), a.pos) - dist(center(), b.pos))[0].pos) | |
}, | |
{ | |
name: "Honnold", | |
color: "#0000FF", | |
run:()=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos) | |
}, | |
{ | |
name: "The Caveman", | |
color: "#FF0000", | |
run:()=>{w=bots().sort((a,b)=>a.score-b.score)[0];return self().score<=w.score?dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos):dirTo(w.pos)} | |
}, | |
{ | |
name: "True Neutral", | |
color: "#400000", | |
run: _=>dirTo(chars().sort((a,b)=>dist(a.pos,[0,0])-dist(b.pos,[0,0])+distTo(a.pos)-distTo(b.pos))[0].pos) | |
}, | |
{ | |
name: "Centrist", | |
color: "#666666", | |
run:_=>dirTo(center()) | |
}, | |
{ | |
name: "Rabbit", | |
color: "#FFC0CB", | |
run:_=>turn()%1e3?dirTo(chars()[0].pos):build(self().source) | |
} | |
]; | |
// _=>{var r,d,e,l,I,N,P,D=distTo,s=self(),p=s[P="pos"],w="_=>dirTo(chars().sort((a,b)=>distTo(a.pos)-distTo(b.pos))[0].pos)",n=w.split``;s.chars.map(c=>n[n[N="indexOf"](c)]="");if(!n.find(_=>_))return build(w);r=chars().filter(c=>!bots().find(b=>b.uid!=s.uid&&dist(b[P],c[P])-D(c[P])<1));if(r.length)return dirTo(r.sort((a,b)=>D(a[P])-D(b[P])+(n[I="includes"](b)-n[I](a))*10)[0][P]);l={n:-p[1],e:p[0],s:p[1],w:-p[0]};d="nesw".split``.sort((a,b)=>l[a]-l[b]);e=bots().filter(b=>b.uid!=s.uid&&b.score>=s.score&&D(b[P])<5).map(b=>d[d[N](dirTo(b[P])[0][0])]="");return[north,east,south,west]["nesw"[N](d.find(x=>x))]()} | |
var game = { | |
randPos: (center, any = !1, uid = 0, owner = 0, p = 0.1) => { | |
var theta, radius, pos; | |
do { | |
theta = Math.random() * Math.PI * 2; | |
radius = 0; | |
while (Math.random() > p) | |
radius++; | |
pos = [Math.trunc(center[0] + Math.cos(theta) * radius), Math.trunc(center[1] + Math.sin(theta) * radius)]; | |
} while (!any && game.bots.find(a => a && a.uid != uid && Math.abs(a.pos[0] - pos[0]) + Math.abs(a.pos[1] - pos[1]) < (a.uid == owner ? 3 : 4))); | |
return pos; | |
}, | |
debug: function(){}, | |
log: 0 // 0 = NONE, 1 = SUMMARY, 2 = ALL | |
}; | |
var north = () => ["north"]; | |
var east = () => ["east"]; | |
var south = () => ["south"]; | |
var west = () => ["west"]; | |
var build = code => ["worker", code]; | |
var drop = { | |
north: char => ["drop.north", char], | |
east: char => ["drop.east", char], | |
south: char => ["drop.south", char], | |
west: char => ["drop.west", char] | |
}; | |
var bots = () => game.bots.map(a => ({ | |
uid: a.uid, | |
owner: a.owner, | |
original: a.original, | |
score: a.score, | |
pos: [...a.pos], | |
chars: game.uid == a.uid ? [...a.chars] : undefined, | |
source: game.uid == a.uid ? a.source : undefined | |
})).sort((a, b) => a.uid - b.uid); | |
var chars = () => game.chars.map(a => ({ | |
char: a.char, | |
pos: [...a.pos] | |
})); | |
var self = () => { | |
var bot = game.bots.find(a => a.uid == game.uid); | |
return bot ? { | |
uid: bot.uid, | |
owner: bot.owner, | |
original: bot.original, | |
score: bot.score, | |
pos: [...bot.pos], | |
chars: [...bot.chars], | |
source: bot.source | |
} : null; | |
}; | |
var owner = () => { | |
var bot = game.bots.find(a => a.uid == game.bots.find(b => b.uid == game.uid).owner); | |
return bot ? { | |
uid: bot.uid, | |
owner: bot.owner, | |
original: bot.original, | |
score: bot.score, | |
pos: [...bot.pos], | |
chars: [...bot.chars], | |
source: bot.source | |
} : null; | |
}; | |
var center = () => game.center; | |
var turn = () => game.turns; | |
var at = pos => ({ | |
bot: (game.bots.find(b => b.pos[0] == pos[0] && b.pos[1] == pos[1]) || {uid: null}).uid, | |
chars: chars().filter(c => c.pos[0] == pos[0] && c.pos[1] == pos[1]) | |
}); | |
var dir = (posFrom, pos) => { | |
if (Math.abs(posFrom[0] - pos[0]) <= Math.abs(posFrom[1] - pos[1])) | |
return posFrom[1] < pos[1] ? ["north"] : ["south"]; | |
else | |
return posFrom[0] < pos[0] ? ["west"] : ["east"]; | |
}; | |
var dirTo = pos => { | |
var bot = game.bots.find(a => a.uid == game.uid); | |
if (Math.abs(pos[0] - bot.pos[0]) <= Math.abs(pos[1] - bot.pos[1])) | |
return pos[1] < bot.pos[1] ? ["north"] : ["south"]; | |
else | |
return pos[0] < bot.pos[0] ? ["west"] : ["east"]; | |
}; | |
var dist = (posFrom, pos) => { | |
return Math.abs(posFrom[0] - pos[0]) + Math.abs(posFrom[1] - pos[1]); | |
}; | |
var distTo = pos => { | |
var bot = game.bots.find(a => a.uid == game.uid); | |
return Math.abs(pos[0] - bot.pos[0]) + Math.abs(pos[1] - bot.pos[1]); | |
}; | |
async function runRound(turns = 100000) { | |
var uids = []; | |
game.perf = performance.now(); | |
for (let i = 1; i <= botData.length; i++) | |
uids[i - 1] = i; | |
for (let j, i = uids.length - 1; i > 0; i--) { | |
j = Math.floor(Math.random() * (i + 1)); | |
[uids[i], uids[j]] = [uids[j], uids[i]]; | |
} | |
game.bots = []; | |
game.chars = []; | |
game.records = game.records || []; | |
game.uids = []; | |
for (let i = 0; i < botData.length; i++) { | |
game.bots[i] = { | |
uid: uids[i], | |
owner: uids[i], | |
original: uids[i], | |
score: Math.floor(botData[i].run.toString().length * -1 / 2), | |
chars: [], | |
pos: game.randPos([0, 0]), | |
source: botData[i].run.toString(), | |
run: botData[i].run, | |
storage: {}, | |
name: botData[i].name || "Bot", | |
color: botData[i].color || "#000000" | |
}; | |
game.uids[uids[i]] = i; | |
game.records[i] = game.records[i] || 0; | |
} | |
game.center = [ | |
game.bots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0), | |
game.bots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / game.bots.reduce((a, b) => a + (b.score + 1), 0) | |
]; | |
game.charPool = game.bots.map(a => a.source).join(""); | |
for (let i = 0; i < botData.length * 4; i++) | |
game.chars.push({ | |
char: game.charPool[Math.random() * game.charPool.length | 0], | |
pos: game.randPos([0, 0]), | |
game: !0 | |
}); | |
game.cuid = botData.length + 1; | |
game.turns = 0; | |
if (!game.fps) { | |
while (game.chars.length && game.bots.length && game.turns < turns) { | |
runTurn(); | |
game.turns++; | |
} | |
} else { | |
game.debug(); | |
while (game.chars.length && game.bots.length && game.turns < turns) { | |
await new Promise(function(resolve) { | |
setTimeout(resolve, 1000 / game.fps); | |
}); | |
if (!game.pause) { | |
runTurn(); | |
game.debug(); | |
game.turns++; | |
} | |
} | |
} | |
game.bots.map(b => game.records[game.uids[b.original]] += b.score); | |
if (game.log) | |
console.log("Round Completed (" + ((performance.now() - game.perf) / 1000).toFixed(3) + "s):\n" + game.bots.map(a => a).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join("\n")); | |
} | |
function runTurn() { | |
var cbots = []; | |
var npos = []; | |
var nposl = []; | |
var nbots = []; | |
for (let b, p, m, i = 0; i < game.bots.length; i++) { | |
b = game.bots[i]; | |
game.uid = b.uid; | |
try { | |
m = b.run(b.storage); | |
} catch(e) { | |
m = ["dead"]; | |
if (game.log == 2) | |
console.warn("[" + game.turns + "] Error: " + b.name + "\n" + (e.stack || e.message)); | |
for (let j = 0; j < b.chars.length; j++) | |
game.chars.push({ | |
char: b.chars[j], | |
pos: game.randPos(b.pos, !0, 0, 0, 0.2), | |
game: !1 | |
}); | |
} | |
if (!Array.isArray(m)) | |
m = []; | |
if (m[0] == "north") | |
p = [b.pos[0], b.pos[1] - 1]; | |
else if (m[0] == "east") | |
p = [b.pos[0] + 1, b.pos[1]]; | |
else if (m[0] == "south") | |
p = [b.pos[0], b.pos[1] + 1]; | |
else if (m[0] == "west") | |
p = [b.pos[0] - 1, b.pos[1]]; | |
else | |
p = [...b.pos]; | |
if (m[0] != "dead") | |
npos.push({ | |
bot: b.uid, | |
pos: p | |
}); | |
if (m[0] == "worker" && m[1].split("").reduce((c, d, e) => c && d && (e = c.indexOf(d)) != -1 ? c.filter((f, g) => g != e) : null, [...b.chars])) { | |
p = game.randPos(b.pos, !1, 0, b.uid); | |
try { | |
cbots.push({ | |
uid: game.cuid, | |
owner: b.uid, | |
original: b.original, | |
score: 0, | |
chars: [], | |
pos: p, | |
source: m[1], | |
run: eval(m[1]), | |
storage: {}, | |
name: b.name + "*", | |
color: b.color | |
}); | |
npos.push({ | |
bot: game.cuid++, | |
pos: p | |
}); | |
b.score -= Math.floor(m[1].length / 2); | |
for (let n, j = 0; j < m[1].length; j++) { | |
n = b.chars.indexOf(m[1][j]); | |
b.chars = b.chars.slice(0, n).concat(b.chars.slice(n + 1)); | |
} | |
if (game.log == 2) | |
console.log("[" + game.turns + "] New Worker: " + b.name); | |
} catch(e) { | |
if (game.log == 2) | |
console.warn("[" + game.turns + "] Invalid Worker: " + b.name + "\n" + (e.stack || e.message)); | |
} | |
} | |
if (typeof m[0] == "string" && m[0].match(/^drop.(north|east|south|west)$/) && b.chars.includes(m[1])) { | |
b.score--; | |
for (let j = 0; j < b.chars.length; j++) { | |
if (b.chars[j] == m[1]) { | |
b.chars = b.chars.slice(0, j) + b.chars.slice(j + 1); | |
break; | |
} | |
} | |
if (m[0] == "drop.north") | |
p = [b.pos[0], b.pos[1] - 1]; | |
else if (m[0] == "drop.east") | |
p = [b.pos[0] + 1, b.pos[1]]; | |
else if (m[0] == "drop.south") | |
p = [b.pos[0], b.pos[1] + 1]; | |
else if (m[0] == "drop.west") | |
p = [b.pos[0] - 1, b.pos[1]]; | |
game.chars.push({ | |
char: m[1], | |
pos: p, | |
game: !1 | |
}); | |
} | |
} | |
game.bots.push(...cbots); | |
for (let f, i = 0; i < npos.length; i++) { | |
if (!(f = nposl.find(a => a.pos[0] == npos[i].pos[0] && a.pos[1] == npos[i].pos[1]))) | |
nposl.push(f = { | |
pos: [...npos[i].pos], | |
bots: [] | |
}); | |
f.bots.push(npos[i].bot); | |
} | |
for (let n, m, b, i = 0; i < nposl.length; i++) { | |
n = nposl[i]; | |
if (n.bots.length > 1) { | |
m = Math.max(...n.bots.map(a => game.bots.find(b => b.uid == a).score)); | |
if (game.bots.filter(a => n.bots.includes(a.uid) && a.score == m).length > 1) { | |
m += 1; | |
if (game.log == 2) | |
console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", ")); | |
} else { | |
if (game.log == 2) | |
console.log("[" + game.turns + "] Collision: " + n.bots.map(a => game.bots.find(b => a == b.uid)).sort((a, b) => b.score - a.score).map(a => a.name + " [" + a.score + "]").join(", ")); | |
} | |
for (let j = 0; j < n.bots.length; j++) { | |
b = game.bots.find(a => a.uid == n.bots[j]); | |
if (b.score < m) { | |
for (let k = 0; k < b.chars.length; k++) | |
game.chars.push({ | |
char: b.chars[k], | |
pos: game.randPos(b.pos, !0, 0, 0, 0.2), | |
game: !1 | |
}); | |
game.records[game.uids[b.original]] += b.score; | |
} else { | |
nbots.push({ | |
uid: b.uid, | |
owner: b.owner, | |
original: b.original, | |
score: b.score, | |
chars: [...b.chars], | |
pos: n.pos, | |
source: b.source, | |
run: b.run, | |
storage: b.storage, | |
name: b.name, | |
color: b.color | |
}); | |
} | |
} | |
} else { | |
b = game.bots.find(a => a.uid == n.bots[0]); | |
nbots.push({ | |
uid: b.uid, | |
owner: b.owner, | |
original: b.original, | |
score: b.score, | |
chars: [...b.chars], | |
pos: n.pos, | |
source: b.source, | |
run: b.run, | |
storage: b.storage, | |
name: b.name, | |
color: b.color | |
}); | |
} | |
} | |
game.center = [ | |
nbots.reduce((a, b) => a + b.pos[0] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0), | |
nbots.reduce((a, b) => a + b.pos[1] * (b.score + 1), 0) / nbots.reduce((a, b) => a + (b.score + 1), 0) | |
]; | |
game.charPool = nbots.map(a => a.source).join(""); | |
for (let b, c, i = 0; i < game.chars.length; i++) { | |
c = game.chars[i]; | |
if (b = nbots.find(a => a.pos[0] == c.pos[0] && a.pos[1] == c.pos[1])) { | |
b.score++; | |
b.chars.push(c.char); | |
if (c.game && game.chars.filter(a => a && a.game).length < nbots.length * 4 && game.bots.map(a => a.original).reduce((a, b) => a.includes(b) ? a : a.concat(b), []).length > 1) | |
game.chars.push({ | |
char: game.charPool[Math.random() * game.charPool.length | 0], | |
pos: game.randPos([0, 0]), | |
game: !0 | |
}); | |
game.chars[i] = null; | |
} | |
} | |
game.chars = game.chars.filter(a => a); | |
game.bots = nbots; | |
}; | |
function drawRound(turns = 100000, log = 2, fps = 5, zoom = 50) { | |
var c, ctx, wdim, scale; | |
document.body.innerHTML = "<canvas></canvas>"; | |
c = document.body.firstChild; | |
c.style.position = "absolute"; | |
c.style.top = "0"; | |
c.style.left = "0"; | |
c.style.zIndex = "2"; | |
ctx = c.getContext("2d"); | |
game.records = new Array(botData.length).fill(0); | |
game.log = log; | |
game.pause = !1; | |
game.fps = fps; | |
(window.onresize = function() { | |
wdim = [window.innerWidth || 600, window.innerHeight || 400]; | |
scale = Math.ceil(wdim[1] / zoom); | |
c.width = wdim[0]; | |
c.height = wdim[1]; | |
})(); | |
window.onkeydown = function() { | |
var key = event.code; | |
if (key == "Escape") | |
game.pause = !game.pause; | |
if (key == "ArrowLeft" && game.fps > 1) | |
game.fps -= 1; | |
if (key == "ArrowRight") | |
game.fps += 1; | |
}; | |
game.debug = function() { | |
ctx.clearRect(0, 0, wdim[0], wdim[1]); | |
ctx.textBaseline = "middle"; | |
ctx.textAlign = "center"; | |
ctx.font = Math.floor(scale * 0.6) + "px monospace"; | |
for (let x = -Math.ceil(wdim[0] / 2 / scale), i = wdim[0] / 2 - (Math.ceil(wdim[0] / 2 / scale) - 0.5) * scale; i <= wdim[0]; i += scale, x++) { | |
for (let b, c, y = -Math.ceil(wdim[1] / 2 / scale), j = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; j <= wdim[1]; j += scale, y++) { | |
if ((c = game.chars.filter(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))).length) { | |
for (let k = 0; k < c.length; k++) | |
ctx.fillText(JSON.stringify(c[k].char).slice(1, -1).replace(/\\"/, "\"").replace(/\\\\/, "\\").replace(/ /, "_"), i + scale / 2, j + scale / 2); | |
} | |
if (b = game.bots.find(a => a.pos[0] == Math.floor(x) && a.pos[1] == Math.floor(y))) { | |
ctx.fillStyle = b.color; | |
ctx.fillRect(i, j, scale, scale); | |
ctx.fillStyle = "#000000"; | |
} | |
} | |
ctx.beginPath(); | |
ctx.moveTo(i, 0); | |
ctx.lineTo(i, wdim[1]); | |
ctx.stroke(); | |
} | |
for (let i = wdim[1] / 2 - (Math.ceil(wdim[1] / 2 / scale) - 0.5) * scale; i <= wdim[1]; i += scale) { | |
ctx.beginPath(); | |
ctx.moveTo(0, i); | |
ctx.lineTo(wdim[0], i); | |
ctx.stroke(); | |
} | |
ctx.fillRect(wdim[0] / 2 - 3, wdim[1] / 2 - 3, 7, 7); | |
}; | |
runRound(turns); | |
} | |
function runGame(rounds = 1, turns = 100000, log = 0) { | |
game.records = new Array(botData.length).fill(0); | |
game.log = log; | |
for (let i = 0; i < rounds; i++) | |
runRound(turns, 0); | |
console.log("Game Conclusion:\n" + botData.map((a, b) => [a.name, game.records[b]]).sort((a, b) => b[1] - a[1]).map(a => "[" + a[1] + "] " + a[0]).join("\n")); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
frick