Skip to content

Instantly share code, notes, and snippets.

@Radvylf
Last active May 16, 2019 02:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Radvylf/1facc0afe24c5dfd3ada8b8a2c493242 to your computer and use it in GitHub Desktop.
Save Radvylf/1facc0afe24c5dfd3ada8b8a2c493242 to your computer and use it in GitHub Desktop.
/*
This file contains a template for uploading blobs to the Hungry Bots KoTH by Redwolf Programs.
If you have any suggestions, let me know!
*/
var Blobs = [ //The Blob Loader requires all blobs to be stored in an array called "Blobs"
{ //Each blob gets an object
data: { //Contains name and color of blob
name: "ExampleBot", //All blobs require a name
color: "#bbbbbb" //Optional color value, defaults to #888888
},
run: function(map, near, me, storage) { //Blob function
if (!near.pellets.length)
return;
var targ = near.pellets[0].pos;
if (targ[0] == me.pos[0])
return targ[1] < me.pos[1] ? North : South;
return targ[0] < me.pos[0] ? West : East;
}
}
];
<!DOCTYPE html>
<html>
<head>
<title>Hungry Blobs KoTH</title>
<!--
Import your blobs here:
<script src="example-blobs.js"></script>
-->
<script src="hungry-blobs.js"></script>
</head>
<body onload="loadScreen()" style="margin: 0; font-family: Roboto, Arial, sans-serif" onkeydown="keyPress(event.code)">
<canvas id="disp" style="position: absolute; width: 100vh; height: 100vh"></canvas>
<div id="ctrls" style="position: absolute; width: calc(100vw - 100vh - 1px); height: 100vh; right: 0; border-left: 1px solid #444444; overflow: auto">
<p style="margin: 10px; margin-top: 30px"><b>TPS:</b> <span id="fps" contenteditable>5</span></p>
<button id="start" style="margin: 10px; width: 90px; height: 40px; font-size: 15px; background-color: #44aa44; border: none; color: #ffffff; cursor: pointer" onclick="play = !play; this.innerText = ({Start:'Stop',Stop:'Start'})[this.innerText]; this.style.backgroundColor = ({Start:'#44aa44',Stop:'#dd4444'})[this.innerText]">Start</button>
<button style="margin: 10px; width: 90px; height: 40px; font-size: 15px; background-color: #4444dd; border: none; color: #ffffff; cursor: pointer" onclick="restart = true">Restart</button>
<button style="margin: 10px; width: 90px; height: 40px; font-size: 15px; background-color: #995500; border: none; color: #ffffff; cursor: pointer" onclick="verbose = !verbose; this.innerText = verbose ? 'Verbose' : 'Info'">Info</button>
<div style="margin: 10px">
<b>Log:</b>
<div id="log" style="font-size: 12px; font-family: Inconsolata, monospace"></div>
</div>
</div>
</body>
</html>
/*
If you're reading this because you want to make a modification to this, and you think it would benefit everyone, be sure to tell me (Redwolf Programs)!
I'll happily update this in any way that will make it easier to use.
If you want to upload your blobs, I'd suggest using the blob loader. To use it, simply link to another .js file in the <head>, following the template blobs.js in the Github Gist.
*/
var play = false, restart = false, verbose = false;
var North = "north", East = "east", South = "south", West = "west";
var SplitNorth = "splitn", SplitEast = "splite", SplitSouth = "splits", SplitWest = "splitw";
var SendNorth = amt => ({amt: amt, dir: [0, -1]}),
SendEast = amt => ({amt: amt, dir: [1, 0]}),
SendSouth = amt => ({amt: amt, dir: [0, 1]}),
SendWest = amt => ({amt: amt, dir: [-1, 0]})
var Game = {
blobData: [],
split: {
splitn: [0, -1],
splite: [1, 0],
splits: [0, 1],
splitw: [-1, 0]
},
map: 0,
cs: [],
blobs: [],
pellets: [],
round: -1,
turns: -1
};
function taxiDist(pt1, pt2, step = 1) {
var x = Math.abs(pt1[0] - pt2[0]) / step;
var y = Math.abs(pt1[1] - pt2[1]) / step;
return x + y;
}
function hypotDist(pt1, pt2) {
var x = pt1[0] - pt2[0];
var y = pt1[1] - pt2[1];
return Math.sqrt(x ** 2 + y ** 2);
}
function modDir(dir, amt) {
var start = [North, East, South, West].indexOf(dir);
var end = start + amt + 256;
return [North, East, South, West][end % 4];
}
function log(msg) {
var ctrls = document.getElementById("ctrls");
if (Math.abs(ctrls.scrollTop - (ctrls.scrollHeight - ctrls.offsetHeight)) < 5)
var scroll = true;
var turn = Game.turns == -1 ? "---" : Game.turns;
document.getElementById("log").innerHTML += "[" + turn + "] " + msg + "<br>";
if (scroll)
ctrls.scrollTop = ctrls.scrollHeight;
}
function loadScreen() {
var c = document.getElementById("disp");
var ctx = c.getContext("2d");
var sdim = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
c.width = sdim;
c.height = sdim;
if (window.Blobs)
for (let i = 0; i < Blobs.length; i++)
Game.blobData.push({
name: Blobs[i].data.name,
color: Blobs[i].data.color || "#888888",
score: 0,
run: Blobs[i].run
});
prepareRound();
drawRound();
turnLoop();
}
function prepareRound() {
document.getElementById("log").innerHTML = "[---] Round Number: " + (Game.round + 1) + "<br>";
Game.map = Math.max(9, Math.ceil(0.25 * Game.blobData.length) * 2 + 1);
Game.round += 1;
Game.turns = -1;
Game.uids = [];
for (let i = 0; i < Game.blobData.length * 10; i++)
Game.uids.push(i);
Game.cuid = 0;
Game.cs = [];
for (let j, x, i = Game.uids.length - 1; i >= 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = Game.uids[i];
Game.uids[i] = Game.uids[j];
Game.uids[j] = x;
}
var pos = [];
for (let i = 0; i < (Game.map + 1) / 2; i++) {
pos.push([0, i * 2]);
pos.push([Game.map - 1, i * 2]);
if (i > 0 && i < (Game.map - 1) / 2) {
pos.push([i * 2, 0]);
pos.push([i * 2, Game.map - 1]);
}
}
for (let j, x, i = pos.length - 1; i >= 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = [...pos[i]];
pos[i] = [...pos[j]];
pos[j] = x;
}
Game.blobs = [];
for (let i = 0; i < Game.blobData.length; i++) {
Game.blobs.push({
name: Game.blobData[i].name,
color: Game.blobData[i].color,
run: Game.blobData[i].run,
uid: Game.uids[Game.cuid++],
oid: i,
energy: 100,
pos: pos[Game.cuid - 1],
storage: {},
csid: Game.cuid - 1
});
log(Game.blobs[i].name + " (" + Game.blobs[i].uid + ") placed at [" + Game.blobs[i].pos.join(", ") + "]");
Game.cs.push({});
}
Game.pellets = [];
for (let i = 0; i < Math.max(Game.map, Game.blobData.length * 2); i++) {
Game.pellets.push({
energy: (Math.random() * 11 | 0) + 5,
pos: [(Math.random() * (Game.map - 2) | 0) + 1, (Math.random() * (Game.map - 2) | 0) + 1]
});
log("Pellet placed at [" + Game.pellets[i].pos.join(", ") + "] with " + Game.pellets[i].energy + " energy");
}
}
function runTurn() {
Game.turns += 1;
var outs = [], pos = [];
for (let b, c, i = 0; i < Game.blobs.length; i++) {
b = Game.blobs[i];
if (b.energy > 0) {
try {
outs.push(c = b.run(Game.map, {
pellets: Game.pellets.filter(el => hypotDist(el.pos, b.pos) <= 4).map(el => ({
pos: [...el.pos],
energy: el.energy
})),
blobs: Game.blobs.filter(el => el.uid !== b.uid && hypotDist(el.pos, b.pos) <= 4).map(el => ({
pos: [...el.pos],
energy: el.energy,
uid: el.uid
}))
}, {
pos: [...b.pos],
energy: b.energy,
uid: b.uid
}, {
self: b.storage,
communal: Game.cs[b.csid]
}));
} catch (e) {
console.warn("[" + b.name + "] " + "\n" + e.stack);
outs.push(c = null);
}
} else {
outs.push(c = null);
}
if ([North, East, South, West].includes(c)) {
if (c == North && b.pos[1] > 0)
pos.push([b.pos[0], b.pos[1] - 1]);
else if (c == East && b.pos[0] < Game.map - 1)
pos.push([b.pos[0] + 1, b.pos[1]]);
else if (c == South && b.pos[1] < Game.map - 1)
pos.push([b.pos[0], b.pos[1] + 1]);
else if (b.pos[0] > 0)
pos.push([b.pos[0] - 1, b.pos[1]]);
else
pos.push([b.pos[0], b.pos[1]]);
b.energy -= 1;
if (b.energy <= 0)
log(b.name + "(" + b.uid + ") starved to death");
} else if (b.energy > 0) {
pos.push([...b.pos]);
b.energy -= 0.25;
if (b.energy <= 0)
log(b.name + "(" + b.uid + ") withered away");
} else {
pos.push(null);
}
if (verbose)
log(b.name + "(" + b.uid + ") returned " + (c == Object(c) ? ({
north: "North",
east: "East",
south: "South",
west: "West",
splitn: "Split [North]",
splite: "Split [East]",
splits: "Split [South]",
splitw: "Split [West]",
none: "(None)"
})[c || "none"] : "Send Energy"));
}
var map = [];
for (let f, i = 0; i < pos.length; i++) {
if (!pos[i])
continue;
if (f = map.find(el => el.pos[0] == pos[i][0] && el.pos[1] == pos[i][1]))
f.blobs.push(i);
else
map.push({
pos: pos[i],
blobs: [i]
});
}
for (let m, i = 0; i < map.length; i++) {
m = map[i];
if (m.blobs.length > 1) {
let sort = m.blobs.sort((a, b) => Game.blobs[a].energy - Game.blobs[b].energy);
let energy = sort.map(el => Game.blobs[el].energy);
if (energy.slice(-2)[0] != energy.slice(-1)[0]) {
let eater = Game.blobs[sort.slice(-1)[0]];
for (let e, n = 0; n < sort.length - 1; n++) {
e = Game.blobs[sort[n]];
log(eater.name + " (" + eater.uid + ") ate " + e.name + " (" + e.uid + ") and gained " + e.energy + " energy");
eater.energy += e.energy;
if (eater.oid != e.oid)
Game.blobData[eater.oid].score += e.energy;
e.energy = 0;
}
} else {
let names = [];
for (let b, n = 0; n < sort.length; n++) {
b = Game.blobs[sort[n]];
names.push(b.name + " (" + b.uid + ") and ");
b.energy = 0;
}
log(names.join("").slice(0, -4) + "engaged in a fatal collision");
}
}
for (let n = 0; n < m.blobs.length; n++) {
Game.blobs[m.blobs[n]].pos = [...pos[m.blobs[n]]];
}
}
for (let p, i = 0; i < Game.pellets.length; i++) {
p = Game.pellets[i];
for (let b, n = 0; n < Game.blobs.length; n++) {
b = Game.blobs[n];
if (b.energy > 0 && b.pos[0] == p.pos[0] && b.pos[1] == p.pos[1]) {
b.energy += p.energy;
log(b.name + " (" + b.uid + ") ate a pellet at [" + p.pos.join(", ") + "] and earned " + p.energy + " energy");
Game.blobData[b.oid].score += p.energy;
Game.pellets[i] = null;
}
}
}
Game.pellets = Game.pellets.filter(el => el);
for (let b, i = Game.blobs.length - 1; i >= 0; i--) {
b = Game.blobs[i];
if (b.energy <= 0 || !outs[i])
continue;
if ([SplitNorth, SplitEast, SplitSouth, SplitWest].includes(outs[i]) && b.energy > 50) {
b.energy = (b.energy - 50) / 2;
log(b.name + " (" + b.uid + ") split to the " + ({
splitn: "North",
splite: "East",
splits: "South",
splitw: "West"
})[outs[i]] + " and created " + b.name + " (" + Game.uids[Game.cuid + 1] + ") with " + b.energy + " energy");
Game.blobs.push({
name: b.name,
color: b.color,
run: b.run,
energy: b.energy,
uid: Game.uids[Game.cuid++],
oid: b.oid,
pos: [b.pos[0] + Game.split[outs[i]][0], b.pos[1] + Game.split[outs[i]][1]],
storage: {},
csid: b.csid
});
} else if (outs[i].amt && outs[i].amt <= b.energy) {
let target = Game.blobs.find(el => el.pos[0] == b.pos[0] + outs[i].dir && el.pos[1] == b.pos[1] + outs[i].dir);
if (target) {
target.energy += outs[i].amt;
b.energy -= outs[i].amt;
}
}
}
if (!(Game.turns % 30) && Game.turns > 0 && Game.turns < Math.max(Game.map, Game.blobData.length * 2) * 30)
for (let i = 0; i < Math.max(Game.map, Game.blobData.length * 2) - Game.turns / 30; i++) {
Game.pellets.push({
energy: (Math.random() * 11 | 0) + 5,
pos: [(Math.random() * (Game.map - 2) | 0) + 1, (Math.random() * (Game.map - 2) | 0) + 1]
});
log("Pellet placed at [" + Game.pellets.slice(-1)[0].pos.join(", ") + "] with " + Game.pellets.slice(-1)[0].energy + " energy");
}
drawRound();
}
function turnLoop() {
if (play)
runTurn();
if (restart) {
prepareRound();
drawRound();
restart = false;
}
let fps = Number(document.getElementById("fps").innerText);
setTimeout(turnLoop, 1000 / fps);
}
function keyPress(code) {
if (code == "Space" || code == "Escape" || code == "KeyS") {
play = !play;
var button = document.getElementById("start");
button.innerText = ({
Start: 'Stop',
Stop:'Start'
})[button.innerText];
button.style.backgroundColor = ({
Start: '#44aa44',
Stop: '#dd4444'
})[button.innerText];
} else if (code == "KeyR" || code == "Backspace") {
restart = true;
}
}
function drawRound() {
var c = document.getElementById("disp");
var ctx = c.getContext("2d");
var sdim = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
var units = sdim / Game.map;
ctx.clearRect(0, 0, sdim, sdim);
ctx.fillStyle = "#ffd833";
for (let p, i = 0; i < Game.pellets.length; i++) {
p = Game.pellets[i];
ctx.beginPath();
ctx.arc(units * (p.pos[0] + 0.5), units * (p.pos[1] + 0.5), units / 2, 0, 2 * Math.PI);
ctx.fill();
}
for (let p, i = 0; i < Game.blobs.length; i++) {
p = Game.blobs[i];
if (p.energy <= 0)
continue;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(units * (p.pos[0] + 0.5), units * (p.pos[1] + 0.5), units / 2, 0, 2 * Math.PI);
ctx.fill();
}
ctx.strokeStyle = "#aaaaaa";
ctx.beginPath();
for (let i = 1; i < Game.map; i++) {
ctx.moveTo(i * units, 0);
ctx.lineTo(i * units, sdim);
ctx.moveTo(0, i * units);
ctx.lineTo(sdim, i * units);
}
ctx.stroke();
}
@TheRamenChef
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment