Skip to content

Instantly share code, notes, and snippets.

@TheRamenChef
Forked from Radvylf/example-blobs.js
Last active May 15, 2019 19:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TheRamenChef/c8417420a087034dedadda2c6dce3a3b to your computer and use it in GitHub Desktop.
Save TheRamenChef/c8417420a087034dedadda2c6dce3a3b 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: [
{
name: "Example",
color: "#888888",
score: 0,
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;
}
}
],
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;
for (let i = 1; i < 16; i++)
Game.blobData[i] = Game.blobData[0];
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 + ": " + e.toString());
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]]);
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("") + "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)
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] && 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();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment