Skip to content

Instantly share code, notes, and snippets.

@beta-decay
Last active August 11, 2018 22:21
Show Gist options
  • Save beta-decay/5ccb090f4d5ec7174ba4659726ab2c45 to your computer and use it in GitHub Desktop.
Save beta-decay/5ccb090f4d5ec7174ba4659726ab2c45 to your computer and use it in GitHub Desktop.
//Controller for Gold Collector KoTH by RedwolfPrograms, tested and (mostly) functional
//
//Use botSetup() to prepare, and runBots() to run one move.
/* Unused colors:
#00d6e6
#ace2e6
#003340
#0077b3
#80d5ff
#007ae6
#203980
#070033
#968fbf
#a280ff
#aa00ff
#3d3040
#770080
#47004d
#e673de
#a6296c
#592d39
#e53950
#d9a3aa
*/
var botData =
[
{
name: "Potentially Victorious",
color: "#1600a6",
run: function (me, others, coins)
{
let huntingTimer = botNotes.getData("huntingTimer");
let huntedIndex = botNotes.getData("huntedIndex");
if(!huntingTimer)
huntingTimer = 0;
else if(huntingTimer >0)
huntingTimer--;
else if(huntingTimer == -1)
huntingTimer = 20;
else
huntingTimer++;
function U(x, y)
{
function distance(X, Y) { return Math.abs(X-x) + Math.abs(Y-y); }
function gravitation(k, X, Y) { return - k / ( distance(X, Y) + .2 ); }
function exponential(k, q, X, Y) { return - 5*k * Math.exp(- q * distance(X,Y)); }
// No going away from the arena.
if(!((0 <= x) && (x < me.arenaLength) && (0 <= y) && (y < me.arenaLength)))
{
return Infinity;
}
let potential = gravitation(40, coins[0][0], coins[0][1]); // Gold
// Silver
for(let i = 1; i < 5; i++)
{
potential += gravitation(10, coins[i][0], coins[i][1]);
}
others.sort((a, b) => b[2] - a[2]);
// Other bots
for(let i = 0; i < others.length; i++)
{
if(
((Math.abs(me.locationX - others[i][0]) + Math.abs(me.locationY - others[i][1])) == 1) &&
(huntingTimer == 0) &&
(me.coins > 25) && (me.coins < 100) &&
(me.coins < (others[0][2] - 5)) &&
(others[i][2] < me.coins-5) && (others[i][2] >= (me.coins-5)/2)
)
{
huntingTimer = -10;
huntedIndex = i;
}
if((huntingTimer < 0) && (huntedIndex == i))
potential += exponential(30, 1, others[i][0], others[i][1]);
if(others[i][2] >= me.coins)
{
// Otherwise, they could eat us, and we will avoid them.
potential += exponential(-1400, 3, others[i][0], others[i][1]);
}
}
// Wall
// let wallDistance = Math.min(x, y, me.arenaLength - x, me.arenaLength - y);
// potential += 10 * Math.exp(1 - wallDistance);
return potential;
}
// All possible squares we can move to, with their names.
let movements = [
[ "north", U(me.locationX, me.locationY - 1)],
[ "south", U(me.locationX, me.locationY + 1)],
[ "east", U(me.locationX + 1, me.locationY)],
[ "west", U(me.locationX - 1, me.locationY)],
[ "none", U(me.locationX, me.locationY)]
];
botNotes.storeData("huntingTimer", huntingTimer);
botNotes.storeData("huntedIndex", huntedIndex);
// Sort them according to the potential U and go wherever the potential is lowest.
movements.sort((a, b) => a[1] - b[1]);
return movements[0][0];
}
},
{
name: "Feudal Noble",
color: "#268299",
run: function (noble, peasants, coins) {
var getDistance = function (x1, y1, x2, y2) {
return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
};
var center = (noble.arenaLength - 1) / 2, centerSize = noble.arenaLength / 4, peasantsCount = peasants.length;
var centerMin = center - centerSize, centerMax = center + centerSize;
var isAtCenter = function (x, y) {
return (x > centerMin && x < centerMax && y > centerMin && y < centerMax);
};
var getScore = function (x, y) {
var score = 0, i;
for (i = 0; i < peasantsCount; i++) {
var peasantCoins = peasants[i][2], peasantDistance = getDistance(x, y, peasants[i][0], peasants[i][1]);
if (noble.coins > peasantCoins && isAtCenter(x, y)) {
score += Math.min(100, peasantCoins) / peasantDistance;
} else if (noble.coins <= peasantCoins && peasantDistance <= 3) {
score -= (peasantDistance === 3 ? 50 : 2000);
}
}
for (i = 0; i < coins.length; i++) {
if (isAtCenter(coins[i][0], coins[i][1])) {
var coinDistance = getDistance(x, y, coins[i][0], coins[i][1]),
coinValue = (i === 0 ? 500 : 200),
coinCloserPeasants = 1;
for (var j = 0; j < peasantsCount; j++) {
var coinPeasantDistance = getDistance(peasants[j][0], peasants[j][1], coins[i][0], coins[i][1]);
if (coinPeasantDistance<= coinDistance && peasants[j][2] >= noble.coins) coinCloserPeasants++;
}
score += (coinValue / coinCloserPeasants) / (coinDistance / 3);
}
}
score -= getDistance(x, y, center, center) * 10;
return score;
};
var possibleMoves = [{x: 0, y: 0, c: 'none'}];
if (noble.locationX > 0) possibleMoves.push({x: -1, y: 0, c: 'west'});
if (noble.locationY > 0) possibleMoves.push({x: -0, y: -1, c: 'north'});
if (noble.locationX < noble.arenaLength - 1) possibleMoves.push({x: 1, y: 0, c: 'east'});
if (noble.locationY < noble.arenaLength - 1) possibleMoves.push({x: 0, y: 1, c: 'south'});
var topCommand, topScore = null;
for (var i = 0; i < possibleMoves.length; i++) {
var score = getScore(noble.locationX + possibleMoves[i].x, noble.locationY + possibleMoves[i].y);
if (topScore === null || score > topScore) {
topScore = score;
topCommand = possibleMoves[i].c;
}
}
return topCommand;
}
},
{
name: "Bait Bot",
color: "#660000",
run: function baitBot(me, others, coins) {
let directions = ['none','east','south','west','north']
function distanceTo(a) {
return (Math.abs(a[0] - me.locationX) + Math.abs(a[1] - me.locationY))
}
function distanceBetween(a, b){
return (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]))
}
function adjacentDir(a) {
//0 = no, 1,2,3,4 = ESWN
if(distanceTo(a) == 1) {
if(a[0] > me.locationX){ return 1}
else if(a[0] < me.locationX) {return 3}
else if(a[1] > me.locationY) {return 2}
else{ return 4}
}
else {return 0}
}
function edibility(a) {
return me.coins - a[2]
}
//Find nearest coin and get next to it
let closestCoin = coins.sort((a,b) => distanceTo(a) - distanceTo(b))[0]
if(distanceTo(closestCoin) > 1) {
if(closestCoin[0] > me.locationX){ return 'east'}
else if(closestCoin[0] < me.locationX){ return 'west'}
else if(closestCoin[1] > me.locationY){ return 'north'}
else if(closestCoin[1] < me.locationY){ return 'south'}
}
//If we're next to a coin and there's a threat close, just grab it
let nearestThreat = others.filter(a => edibility(a) < 0).sort((a,b) => distanceTo(a) - distanceTo(b))[0]
if(nearestThreat && (distanceTo(nearestThreat) <= 2)) {
return directions[adjacentDir(closestCoin)]
}
//Otherwise, wait until there's a target also next to the coin
let targets = others.filter(a => edibility(a) > 0 && distanceBetween(closestCoin, a) == 1)
if(targets.length > 0){
return directions[adjacentDir(closestCoin)]
}
return directions[0]
}
},
{
name: "1st Gen Algorithm",
color: "#330d0d",
run: function () {
return ['north','east','south','west'][(Math.random()*4)|0];
}
},
{
name: "GUT",
color: "#a65e53",
run: function gut(me, others, coins) {
// Prepare values for the calculation
var x = me.locationX;
var y = me.locationY;
var cMe = me.coins+1;
var arenaLen = me.arenaLength;
var objects = [];
// Add bots to objects
for (var i = 0; i < others.length; i++) {
objects.push([others[i][0],others[i][1],others[i][2]/cMe]);
}
// Add coins to objects
for (var j = 0; j < coins.length; j++) {
var coinVal = 0;
if (j == 0) {
// Gold has a higher coin value
coinVal = -10;
} else {
// Silver has a lower coin value
coinVal = -5;
}
objects.push([coins[j][0],coins[j][1],coinVal/cMe]);
}
// Perform the calculation
// x acceleration
var x_acceleration = 0;
for (var k=0; k < objects.length; k++) {
var kval = objects[k][2];
var xval = objects[k][0];
x_acceleration += 200*kval/cMe*(x-xval)*Math.exp(Math.pow(kval,2)-50*Math.pow(x-xval,2));
}
// y acceleration
var y_acceleration = 0;
for (var l=0; l < objects.length; l++) {
var kval = objects[l][2];
var yval = objects[l][1];
y_acceleration += 200*kval/cMe*(y-yval)*Math.exp(Math.pow(kval,2)-50*Math.pow(y-yval,2));
}
// Compare the values
if (Math.abs(y_acceleration)>Math.abs(x_acceleration)) {
if (y_acceleration < 0) {
// Don't fall off the edge
if (y>0) {
return "north";
} else {
return "none";
}
} else {
if (y<arenaLen-1) {
return "south";
} else {
return "none";
}
}
} else if (Math.abs(y_acceleration)<Math.abs(x_acceleration)) {
if (x_acceleration < 0) {
if (x>0) {
return "west";
} else {
return "none";
}
} else {
if (x<arenaLen-1) {
return "east";
} else {
return "none";
}
}
} else {
return "none";
}
}
},
{
name: "Anti-Capitalist",
color: "#453c39",
run: function (me, capitalists, coins){
function acquireTargets(capitalists){
capitalists.sort((a, b) => a[2] < b[2]);
let previousCapitalist;
for(let i in capitalists){
let capitalist = capitalists[i];
if(capitalist[2] === 0){
return false;
}
if(previousCapitalist && capitalist[2] === previousCapitalist[2]){
return [previousCapitalist, capitalist];
}
previousCapitalist = capitalist;
}
return false;
}
function move(){
const targets = acquireTargets(capitalists);
if(!targets){
return 'none';
}
const coordinates = [Math.floor((targets[0][0] + targets[1][0]) / 2), Math.floor((targets[0][1] + targets[1][1]) / 2)];
if(me.locationX !== coordinates[0]){
return me.locationX < coordinates[0] ? 'east' : 'west';
}
else if(me.locationX !== coordinates[1]){
return me.locationY < coordinates[1] ? 'south' : 'north';
}
else {
return 'none';
}
}
return move();
}
},
{
name: "Quantum Gnat",
color: "#4d3c39",
run: function (me, others, coins) {
let quantumCoefficient = .2
let turn = botNotes.getData('turn')
botNotes.storeData('turn', turn+1)
botNotes.storeData('test', [2, 5, 7])
botNotes.getData('test')
let dG = {'none': [0, 0, -2, -2], 'east': [1, 0, me.arenaLength-1, -2], 'south': [0, 1, -2, me.arenaLength-1], 'west': [-1, 0, 0, -2], 'north': [0, -1, -2, 0]}
function randomInt(a) {
return Math.floor(Math.random() * a);
}
function getRandomDirection() {
return ['east', 'west', 'north', 'south'][randomInt(4)]
}
function distanceBetween(a, b){
return (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]))
}
function isTargetSafe(a) {
for (let i = 0; i < others.length; i++) {
if (others[i][2] >= me.coins && distanceBetween(a, others[i]) <= 1) {
return false
}
}
return true
}
function isEnemySquare(a) {
for (let i = 0; i < others.length; i++) {
if (distanceBetween(a, others[i]) == 0) {
return true
}
}
return false
}
function getSafeDirections() {
let candidates = {'none': true, 'east': true, 'south': true, 'west': true, 'north': true}
for (let key in dG) {
if (me.locationX == dG[key][2] || me.locationY == dG[key][3] || !isTargetSafe([me.locationX+dG[key][0], me.locationY+dG[key][1]])) {
candidates[key] = false
}
}
//alert('Safe: ' + candidates['north'] + ', ' + candidates['east'] + ', ' + candidates['south'] + ', ' + candidates['west'])
return candidates
}
function getThreatDirections() {
let candidates = {'none': false, 'east': false, 'south': false, 'west': false, 'north': false}
for (let key in dG) {
if (isEnemySquare([me.locationX+dG[key][0], me.locationY+dG[key][1]])) {
candidates[key] = true
}
}
return candidates
}
function getTargetDirections() {
let targetBot = null
let candidates = {'none': false, 'east': false, 'south': false, 'west': false, 'north': false}
for (let i = 0; i < others.length; i++) {
if (distanceBetween([me.locationX, me.locationY], others[i]) > 2 && (!targetBot || targetBot[2] < others[i][2])) {
targetBot = others[i]
}
}
if (targetBot[0] < me.locationX) {
candidates['west'] = true
} else if (targetBot[0] > me.locationX) {
candidates['east'] = true
}
if (targetBot[1] > me.locationY) {
candidates['south'] = true
} else if (targetBot[1] < me.locationY) {
candidates['north'] = true
}
//alert('Chasing ' + targetBot[0] + ', ' + targetBot[1] + ' (' + targetBot[2] + ')')
//alert('Path: ' + candidates['north'] + ', ' + candidates['east'] + ', ' + candidates['south'] + ', ' + candidates['west'])
return candidates
}
let safeDirections = getSafeDirections()
let threatDirections = getThreatDirections()
let targetDirections = getTargetDirections()
let chosenDir = null
let choices = []
for (key in safeDirections) {
if (safeDirections[key] && targetDirections[key]) {
choices.push(key)
}
}
if (choices.length == 0) {
//alert('Best options are blocked...')
for (key in safeDirections) {
if (safeDirections[key]) {
choices.push(key)
}
}
}
for (key in threatDirections) {
if (threatDirections[key] && Math.random() < quantumCoefficient) {
//alert('Chance for quantum swap!')
choices.push(key)
}
}
if (choices.length > 0) {
chosenDir = choices[randomInt(choices.length)]
} else {
//alert('No options? Guess we spin the wheel.')
chosenDir = getRandomDirection()
}
return chosenDir
}
},
{
name: "Safety Coin",
color: "#ffa280",
run: function (myself,others,coins) {
x=myself.locationX;
y=myself.locationY;
power=myself.coins;
arenaSize=myself.arenaLength;
dist=0;
optimalCoin=7;
optimalDist=11*arenaSize;
for(i=0;i<coins.length;i++){
enemyDist=3*arenaSize;
dist=Math.abs(x-coins[i][0])+Math.abs(y-coins[i][1])
for(j=0;j<others.length;j++){
if(i==0){
if(others[j][2]+5>=power){
enemyDist=Math.min(enemyDist,Math.abs(others[j][0]-coins[i][0])+Math.abs(others[j][1]-coins[i][1]))
}
}
else{
if(others[j][2]+2>=power){
enemyDist=Math.min(enemyDist,Math.abs(others[j][0]-coins[i][0])+Math.abs(others[j][1]-coins[i][1]))
}
}
}
if(enemyDist>dist){
if(i==0){
if(dist/5<optimalDist){
optimalDist=dist/5;
optimalCoin=i;
}
}
else{
if(dist/2<optimalDist){
optimalDist=dist/2;
optimalCoin=i;
}
}
}
}
if(optimalCoin==7){
safeDir=15;
if(x==0){safeDir-=8;}
if(x==arenaSize-1){safeDir-=2;}
if(y==0){safeDir-=1;}
if(y==arenaSize-1){safeDir-=4;}
for(i=0;i<others.length;i++){
if(others[i][2]>=power){
if(Math.abs(x-others[i][0])+Math.abs(y-others[i][1])==2){
if(x-others[i][0]>0){safeDir-=8;}
if(x-others[i][0]<0){safeDir-=2;}
if(y-others[i][1]>0){safeDir-=1;}
if(y-others[i][1]<0){safeDir-=4;}
}
}
}
directions=["north","east","south","west"];
if(safeDir!=0){
tmp="";
tmp+="0".repeat(Math.max(Math.sqrt(arenaSize)/2|0,y-(arenaSize/2|0)));
tmp+="2".repeat(Math.max(Math.sqrt(arenaSize)/2|0,(arenaSize/2|0)-y));
tmp+="1".repeat(Math.max(Math.sqrt(arenaSize)/2|0,(arenaSize/2|0)-x));
tmp+="3".repeat(Math.max(Math.sqrt(arenaSize)/2|0,x-(arenaSize/2|0)));
rnd=tmp[Math.random()*tmp.length|0];
while(!(2**rnd&safeDir)){rnd=tmp[Math.random()*tmp.length|0];}
return directions[rnd];
}
return "none";//the only safe move is not to play :P
}
distX=coins[optimalCoin][0]-x;
distY=coins[optimalCoin][1]-y;
if(Math.abs(distX)>Math.abs(distY)){
if(distX>0){return "east";}
else{return "west";}
}
else{
if(distY>0){return "south";}
else{return "north";}
}
}
},
{
name: "Firebird",
color: "#b2622d",
run: function () {
var pos = botNotes.getData("pos");
if (pos === null || pos === "up") {
botNotes.storeData("pos", "mid");
return "south";
} else if (pos === "mid") {
botNotes.storeData("pos", "down");
return "south";
} else if (pos === "down") {
botNotes.storeData("pos", "center");
return "north";
} else {
botNotes.storeData("pos", "up");
return "north";
}
}
},
{
name: "Proton",
color: "#ffd9bf",
run:function (myself,others,coins){
x=myself.locationX;
y=myself.locationY;
power=myself.coins;
arenaSize=myself.arenaLength;
forceX=0;
forceY=0;
prevState=botNotes.getData("proton_velocity");
if(prevState){
velocity=prevState[0];
direction=prevState[1];
}
else{
velocity=0;
direction=0;
}
for(i=0;i<coins.length;i++){
if(Math.abs(x-coins[i][0])+Math.abs(y-coins[i][1])==1){
velocity=0;
direction=0;
botNotes.storeData("proton_velocity",[velocity,direction]);
if(x-coins[i][0]==1){return "west";}
if(coins[i][0]-x==1){return "east";}
if(y-coins[i][1]==1){return "north";}
if(coins[i][1]-y==1){return "south";}
}
else{
dist=Math.sqrt(Math.pow(x-coins[i][0],2)+Math.pow(y-coins[i][1],2));
if(i==0){
forceX+=(x-coins[i][0])*5/Math.pow(dist,3);
forceY+=(y-coins[i][1])*5/Math.pow(dist,3);
}
else{
forceX+=(x-coins[i][0])*2/Math.pow(dist,3);
forceY+=(y-coins[i][1])*2/Math.pow(dist,3);
}
}
}
for(i=0;i<others.length;i++){
if(Math.abs(x-others[i][0])+Math.abs(y-others[i][1])==1&&power>others[i][2]){
velocity=0;
direction=0;
botNotes.storeData("proton_velocity",[velocity,direction]);
if(x-others[i][0]==1){return "west";}
if(others[i][0]-x==1){return "east";}
if(y-others[i][1]==1){return "north";}
if(others[i][1]-y==1){return "south";}
}
else{
dist=Math.sqrt(Math.pow(x-others[i][0],2)+Math.pow(y-others[i][1],2));
forceX+=(x-others[i][0])*others[i][2]/Math.pow(dist,3);
forceY+=(y-others[i][1])*others[i][2]/Math.pow(dist,3);
}
}
vX=velocity*Math.cos(direction)+10*forceX/Math.max(1,power);
vY=velocity*Math.sin(direction)+10*forceY/Math.max(1,power);
velocity=Math.sqrt(vX*vX+vY*vY);
if(velocity==0){return "none"}
retval="none";
if(Math.abs(vX)>Math.abs(vY)){
if(vX>0){
if(x<arenaSize-1){retval="east";}
else{vX=-vX;retval="west";}
}
else{
if(x>0){retval="west";}
else{vX=-vX;retval="east";}
}
}
else{
if(vY>0){
if(y<arenaSize-1){retval="south";}
else{vY=-vY;retval="north";}
}
else{
if(y>0){retval="north";}
else{vY=-vY;retval="south";}
}
}
direction=Math.atan2(-vY,vX);
botNotes.storeData("proton_velocity",[velocity,direction]);
return retval;
}
},
{
name: "Weighted Motion",
color: "#735839",
run:function (myself,others,coins){
x=myself.locationX;
y=myself.locationY;
power=myself.coins;
arenaSize=myself.arenaLength;
dirX=0;
dirY=0;
for(i=0;i<coins.length;i++){
if(i==0){
dirX+=5/(x-coins[i][0]);
dirY+=5/(y-coins[i][1]);
}
else{
dirX+=2/(x-coins[i][0]);
dirY+=2/(y-coins[i][1]);
}
}
for(i=0; i<others.length;i++){
dirX+=(power-others[i][2])/(2*(x-others[i][0]));
dirY+=(power-others[i][2])/(2*(y-others[i][1]));
}
if(Math.abs(dirX)>Math.abs(dirY)){
if(dirX>0){
if(x>0){return "west";}
else{
if(dirY>0){if(y>0)return "north";}
else if(dirY<0){if(y<arenaSize-1)return "south";}
}
}
else if(x<arenaSize-1){return "east";}
else{
if(dirY>0){if(y>0)return "north";}
else if(dirY<0){if(y<arenaSize-1)return "south";}
}
}
else{
if(dirY>0){
if(y>0){return "north";}
else{
if(dirX>0){if(x>0)return "west";}
else if(dirX<0){if(x<arenaSize-1)return "east";}
}
}
else if(y<arenaSize-1){return "south";}
else{
if(dirX>0){if(x>0)return "west";}
else if(dirX<0){if(x<arenaSize-1){return "east";}
}
}
return "none";
}
}
},
{
name: "Blindly",
color: "#ffaa00",
run:function(myself, others, coins){
mx = myself.locationX
my = myself.locationY
l="west"
r="east"
u="north"
d="south"
n="none"
if(coins.length == 0)
return n
var closestCoin = coins.sort(a=>Math.sqrt(Math.pow(mx-a[0],2) + Math.pow(my-a[1],2))).pop()
cx = closestCoin[0]
cy = closestCoin[1]
return mx>cx?l:mx<cx?r:my>cy?u:my<cy?d:n
}
},
{
name: "Damacy",
color: "#997a00",
run: function (me, others, coin) {
let xdist = t => Math.abs(t[0] - me.locationX)
let ydist = t => Math.abs(t[1] - me.locationY)
function distanceCompare(a, b, aWt, bWt) {
aWt = aWt || 1
bWt = bWt || 1
return (xdist(a) + ydist(a)) / aWt - (xdist(b) + ydist(b)) / bWt
}
function hasThreat(loc) {
let threat = others.filter(b => b[0] == loc[0] && b[1] == loc[1] && b[2] >= me.coins)
return (threat.length > 0)
}
function inArena(loc) { // probably unnecessary for this bot
return loc[0] >= 0 && loc[1] >= 0 && loc[0] < me.arenaLength && loc[1] < me.arenaLength
}
function sortedCoins() {
coinsWithValues = coin.map((coords, i) => coords.concat((i == 0) ? 5 : 2))
coinsWithValues.sort((a, b) => distanceCompare(a, b, a[2], b[2]))
return coinsWithValues.map(c => c.slice(0, 2))
}
othersPrev = botNotes.getData('kata_others_pos')
botNotes.storeData('kata_others_pos', others)
if (othersPrev) {
for(let i = 0; i < others.length; i++) {
let bot = others[i]
let matchingBots = othersPrev.filter(function (b) {
let diff = Math.abs(b[0] - bot[0]) + Math.abs(b[1] - bot[1])
if (diff >= 2)
return false // bot can't have jumped
return [0, 2, 5].includes(bot[2] - b[2])
})
if (matchingBots.length > 0) {
let botPrev = matchingBots.shift()
// remove matched bot so it doesn't get matched again later
othersPrev = othersPrev.filter(b => b[0] != botPrev[0] || b[1] != botPrev[1])
bot[0] = Math.min(Math.max(bot[0] + bot[0] - botPrev[0], 0), me.arenaLength-1)
bot[1] = Math.min(Math.max(bot[1] + bot[1] - botPrev[1], 0), me.arenaLength-1)
}
}
}
let eatables = others.filter(b => b[2] < me.coins && b[2] > 0)
let targets
if (eatables.length > 0) {
targets = eatables.sort(distanceCompare)
}
else {
targets = sortedCoins()
}
let done, newLoc, dir
while (!done && targets.length > 0) {
t = targets.shift()
if ((xdist(t) <= ydist(t) || ydist(t) == 0) && xdist(t) != 0) {
let xmove = Math.sign(t[0] - me.locationX)
dir = xmove < 0 ? 'west' : 'east'
newLoc = [me.locationX + xmove, me.locationY]
if (!hasThreat(newLoc) && inArena(newLoc))
done = 1
}
if (!done) {
let ymove = Math.sign(t[1] - me.locationY)
dir = ['north', 'none', 'south'][ymove + 1]
newLoc = [me.locationX, me.locationY + ymove]
if (!hasThreat(newLoc) && inArena(newLoc))
done = 1
}
}
if (!done)
dir = 'none'
return dir
}
},
{
name: "Katamari with Values",
color: "#fff2bf",
run: function (me, others, coin) {
let xdist = t => Math.abs(t[0] - me.locationX)
let ydist = t => Math.abs(t[1] - me.locationY)
function distanceCompare(a, b, aWt = 1, bWt = 1) {
return (xdist(a) + ydist(a)) / aWt - (xdist(b) + ydist(b)) / bWt
}
function hasThreat(loc) {
let threat = others.filter(b => b[0] == loc[0] && b[1] == loc[1] && b[2] >= me.coins)
return (threat.length > 0)
}
function inArena(loc) { // probably unnecessary for this bot
return loc[0] >= 0 && loc[1] >= 0 && loc[0] < me.arenaLength && loc[1] < me.arenaLength
}
function sortedCoins() {
coinsWithValues = coin.map((coords, i) => coords.concat((i == 0) ? 5 : 2))
coinsWithValues.sort((a, b) => distanceCompare(a, b, a[2], b[2]))
return coinsWithValues.map(c => c.slice(0, 2))
}
let eatables = others.filter(b => b[2] < me.coins && b[2] > 0)
let targets
if (eatables.length > 0) {
targets = eatables.sort(distanceCompare)
}
else {
targets = sortedCoins()
}
let done, newLoc, dir
while (!done && targets.length > 0) {
t = targets.shift()
if ((xdist(t) <= ydist(t) || ydist(t) == 0) && xdist(t) != 0) {
let xmove = Math.sign(t[0] - me.locationX)
dir = xmove < 0 ? 'west' : 'east'
newLoc = [me.locationX + xmove, me.locationY]
if (!hasThreat(newLoc) && inArena(newLoc))
done = 1
}
if (!done) {
let ymove = Math.sign(t[1] - me.locationY)
dir = ['north', 'none', 'south'][ymove + 1]
newLoc = [me.locationX, me.locationY + ymove]
if (!hasThreat(newLoc) && inArena(newLoc))
done = 1
}
}
if (!done)
dir = 'none'
return dir
}
},
{
name: "Goldilocks",
color: "#b2bf00",
run: function (me, others, coins) {
let target = coins[0]; // Gold
let x = target[0] - me.locationX;
let y = target[1] - me.locationY;
mymove = 'none'
if (Math.abs(x) <= Math.abs(y) && x != 0)
mymove = x < 0 ? 'west' : 'east'
else if (y != 0)
mymove = y < 0 ? 'north' : 'south'
return mymove
}
},
{
name: "BEELINE",
color: "#8a8c69",
run: function(me, others, coins) {
// Do nothing if there aren't any coins
if (coins.length == 0) return 'none';
// Sort by distance using Pythagoras' Theorem
let c = coins.sort((a, b) => (a[0] ** 2 + a[1] ** 2) - (b[0] ** 2 + b[1] ** 2));
// Closest coin
let target = c[0];
let x = target[0];
let y = target[1];
// Util function for movement
function move(pos, type) {
let moveTypes = { X: ['east', 'west'], Y: ['south', 'north'] };
if (pos > me['location'+type]) return moveTypes[type][0];
else return moveTypes[type][1];
}
// Move the shortest distance first
if (x < y && x != me.locationX) return move(x, 'X');
else if (y != me.locationY) return move(y, 'Y');
}
},
{
name: "3rd Gen Algorithm",
color: "#62731d",
run: function run(me) {
options = [];
if (me.locationX > 0) options.push('west');
if (me.locationY > 0) options.push('north');
if (me.locationX < (me.arenaLength-1)) options.push('east');
if (me.locationY < (me.arenaLength-1)) options.push('south');
return options[Math.floor(Math.random() * options.length)];
}
},
{
name: "Drunkard",
color: "#304010",
run: function (me, others, coins) {
let directions = ['none','east','south','west','north']
let drunkennessCoefficient = .2
let nearSightedness = me.arenaLength - others.length + 2
//drawCircle(me.locationX, me.locationY, nearSightedness*squareSize)
function randomInt(a) {
return Math.floor(Math.random() * a);
}
function getRandomDirection() {
return ['east', 'west', 'north', 'south'][randomInt(4)]
}
function distanceTo(a) {
return (Math.abs(a[0] - me.locationX) + Math.abs(a[1] - me.locationY))
}
function distanceBetween(a, b){
return (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]))
}
function isTargetSafe(a) {
for (let i = 0; i < others.length; i++) {
if (others[i][2] >= me.coins && distanceBetween(a, others[i]) <= distanceTo(a)) {
return false //unnecessary loop, but I don't want to split out into a function
}
}
return true
}
function amISafe() {
for (let i = 0; i < others.length; i++) {
if (others[i][2] >= me.coins && distanceTo(others[i]) == 1) {
/*let num = botNotes.getData('turnsSpentAdjacentToEnemy')
if (!num) {
console.log('politeNearSightedDrunkBot: Woops!')
botNotes.storeData('turnsSpentAdjacentToEnemy', 1)
} else if (num == 1) {
console.log('politeNearSightedDrunkBot: \'Scuse me...')
botNotes.storeData('turnsSpentAdjacentToEnemy', 2)
} else if (num == 2) {
console.log('politeNearSightedDrunkBot: D\'ye mind?')
botNotes.storeData('turnsSpentAdjacentToEnemy', 3)
} else if (num == 3) {
console.log('politeNearSightedDrunkBot: Bugger off!')
}*/
return false
}
}
return true
}
function getSafeDirections() {
let candidates = {'none': true, 'east': true, 'south': true, 'west': true, 'north': true}
if (me.locationY == 0) {
candidates['north'] = false
} else if (me.locationY == me.arenaLength - 1) {
candidates['south'] = false
}
if (me.locationX == 0) {
candidates['west'] = false
} else if (me.locationX == me.arenaLength - 1) {
candidates['east'] = false
}
if (!amISafe()) {
candidates['none'] = false
}/* else {
botNotes.storeData('turnsSpentAdjacentToEnemy', 0)
}*/
if (candidates['north'] && !isTargetSafe([me.locationX, me.locationY-1])) {
candidates['north'] = false
}
if (candidates['south'] && !isTargetSafe([me.locationX, me.locationY+1])) {
candidates['south'] = false
}
if (candidates['west'] && !isTargetSafe([me.locationX-1, me.locationY])) {
candidates['west'] = false
}
if (candidates['east'] && !isTargetSafe([me.locationX+1, me.locationY])) {
candidates['east'] = false
}
if (candidates['none']) {
}
return candidates
}
function getSafeCoins() {
let safestCoins = []
let coinSizes = [5, 2, 2, 2, 2]
for (let i = 0; i < coins.length; i++) {
let distanceToThisCoin = distanceTo(coins[i])
if (distanceToThisCoin < nearSightedness && isTargetSafe(coins[i])) {
safestCoins.push([coins[i][0], coins[i][1], coinSizes[i], distanceToThisCoin])
//alert('Coin at (' + coins[i][0] + ', ' + coins[i][1] + ') is safe!')
}
}
if (safestCoins.length == 0) {
//alert('No safe coins!')
}
return safestCoins
}
function getAdditiveBestDirectionToTargets(targets) {
let candidates = {'east': 0, 'south': 0, 'west': 0, 'north': 0}
for (let i = 0; i < targets.length; i++) {
if (targets[i][0] < me.locationX) {
candidates['west'] = candidates['west'] + targets[i][2]/targets[i][3]
} else if (targets[i][0] > me.locationX) {
candidates['east'] = candidates['east'] + targets[i][2]/targets[i][3]
}
if (targets[i][1] > me.locationY) {
candidates['south'] = candidates['south'] + targets[i][2]/targets[i][3]
} else if (targets[i][1] < me.locationY) {
candidates['north'] = candidates['north'] + targets[i][2]/targets[i][3]
}
}
for (let key in candidates) {
//alert(key + ': ' + candidates[key])
}
return candidates
}
let targetCoins = getSafeCoins()
let safeDirections = getSafeDirections()
let chosenDir = null
if (targetCoins.length > 0) {
//alert('Coins found! Exactly ' + targetCoins.length)
let weightedDirections = getAdditiveBestDirectionToTargets(targetCoins)
let bestOptionWeight = 0
let choices = []
for (let key in safeDirections) {
if (safeDirections[key] && key != 'none') {
if (weightedDirections[key] == bestOptionWeight) {
choices.push(key)
} else if (weightedDirections[key] > bestOptionWeight) {
choices = [key]
bestOptionWeight = weightedDirections[key]
}
}
}
if (choices.length > 0) {
//alert('Picking from choices, ' + choices.length + ' options and best weight is ' + bestOptionWeight)
chosenDir = choices[randomInt(choices.length)]
} else {
//alert('No safe choices!')
}
} else {
let lastDir = botNotes.getData('direction') || 'none'
if (safeDirections[lastDir] && Math.random() >= drunkennessCoefficient) {
chosenDir = lastDir
}
}
if (!chosenDir) {
//alert('indecisive!')
let choices = []
for (key in safeDirections) {
if (safeDirections[key]) {
choices.push(key)
}
}
if (choices.length > 0) {
chosenDir = choices[randomInt(choices.length)]
} else {
chosenDir = getRandomDirection()
}
}
botNotes.storeData('direction', chosenDir)
//alert('Moving ' + chosenDir)
return chosenDir
}
},
{
name: "Big King Little Hill",
color: "#364030",
run: function (me, enemies, coins) {
// Is a move safe to execute?
function isItSafe(x){
let loc = [x[0] + me.locationX,x[1] + me.locationY];
return loc[0] >= 0 && loc[0] < me.arenaLength
&& loc[1] >= 0 && loc[1] < me.arenaLength
&& enemies
.filter(enemy => me.coins <= enemy[2])
.filter(enemy => getDist(enemy,loc) == 1).length === 0;
}
// Dumb conversion of relative coord to direction string
function coordToString(coord){
if (coord[0] == 0 && coord[1] == 0) return 'none';
if (Math.abs(coord[0]) > Math.abs(coord[1]))
return coord[0] < 0 ? 'west' : 'east';
return coord[1] < 0 ? 'north' : 'south';
}
// Calculate a square's zone of control
function getZOC(x) {
let n = 0;
for(let i = 0; i < me.arenaLength;i++){
for(let j = 0; j < me.arenaLength;j++){
if (doesAControlB(x, [i,j])) n++;
}
}
return n;
}
function doesAControlB(a, b) {
return getEnemyDist(b) > getDist(a, b);
}
// Distance to nearest enemy
function getEnemyDist(x) {
return enemies.map(enemy => getWeightedDist(enemy, x)).reduce((accumulator, current) => Math.min(accumulator, current));
}
// Weights distance by whether enemy is weaker or stronger
function getWeightedDist(enemy, pos) {
return getDist(enemy, pos) + (enemy[2] < me.coins ? 1 : 0);
}
function getDist(a, b){
return (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]))
}
//check whether there are coins in our Zone of Control, if yes move towards the closest one
let loc = [me.locationX,me.locationY];
let sortedCoins = coins.sort((a,b) => getDist(loc,a) - getDist(loc,b));
for (let coin of sortedCoins) {
if (doesAControlB(loc,coin)){
return coordToString([coin[0] - loc[0],coin[1] - loc[1]]);
}
}
//sort moves by how they increase our Zone of Control
northZOC = [[0,-1], getZOC([loc[0],loc[1]-1])];
southZOC = [[0,1], getZOC([loc[0],loc[1]+1])];
westZOC = [[-1,0], getZOC([loc[0]-1,loc[1]])];
eastZOC = [[1,0], getZOC([loc[0]+1,loc[1]])];
noneZOC = [[0,0], getZOC([loc[0],loc[1]])];
let moves = [northZOC,southZOC,westZOC,eastZOC,noneZOC].sort((a,b) => b[1] - a[1]);
//check whether these moves are safe and make the highest priority safe move
for (let move of moves) {
if (isItSafe(move[0])) {
return coordToString(move[0]);
}
}
return coordToString(moves[0][0])
}
},
{
name: "TBTsefoijawwhatever",
color: "#f24100",
run: function (me, monsters, coins) {
var getDistance = function (x1, y1, x2, y2) {
return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
};
var getScore = function (x, y) {
var score = 0, i, chaseFactor = 0.75, coinFactor = 1, phaseSize = Math.round((me.arenaLength - 4) / 4), monstersCount = monsters.length, center = (me.arenaLength - 1) / 2;
if (monstersCount < phaseSize) {
chaseFactor = 0;
coinFactor = 0.25;
} else if (monstersCount < phaseSize * 2) {
chaseFactor = 0.5;
coinFactor = 0.5;
} else if (monstersCount < phaseSize * 3) {
coinFactor = 0.75;
}
for (i = 0; i < monstersCount; i++) {
var monsterCoins = monsters[i][2], monsterDistance = getDistance(x, y, monsters[i][0], monsters[i][1]);
if (me.coins > monsterCoins && monsterDistance <= 3) {
score += (Math.min(5, monsterCoins) * chaseFactor) / monsterDistance;
} else if (me.coins <= monsterCoins && monsterDistance <= 3) {
score -= (monsterDistance === 3 ? 50 : 2000);
}
}
for (i = 0; i < coins.length; i++) {
var coinDistance = getDistance(x, y, coins[i][0], coins[i][1]),
coinDistanceCenter = getDistance(center, center, coins[i][0], coins[i][1]),
coinValue = (i === 0 ? 200 : 100),
coinCloserMonsters = 0;
for (var j = 0; j < monstersCount; j++) {
var coinMonsterDistance = getDistance(monsters[j][0], monsters[j][1], coins[i][0], coins[i][1]);
monsterCoins = monsters[j][2];
if (
(coinMonsterDistance < coinDistance && monsterCoins >= me.coins / 2) ||
(coinMonsterDistance <= coinDistance && monsterCoins >= me.coins)
) {
coinCloserMonsters++;
}
}
var coinMonsterFactor = (100 - ((100 / monstersCount) * coinCloserMonsters)) / 100;
if (coinMonsterFactor < 1) coinMonsterFactor *= coinFactor;
score += ((coinValue * coinMonsterFactor) / coinDistance) - (50 / coinDistanceCenter);
}
return score;
};
var possibleMoves = [{x: 0, y: 0, c: 'none'}];
if (me.locationX > 0) possibleMoves.push({x: -1, y: 0, c: 'west'});
if (me.locationY > 0) possibleMoves.push({x: -0, y: -1, c: 'north'});
if (me.locationX < me.arenaLength - 1) possibleMoves.push({x: 1, y: 0, c: 'east'});
if (me.locationY < me.arenaLength - 1) possibleMoves.push({x: 0, y: 1, c: 'south'});
var topCommand, topScore = null;
for (var i = 0; i < possibleMoves.length; i++) {
var score = getScore(me.locationX + possibleMoves[i].x, me.locationY + possibleMoves[i].y);
if (topScore === null || score > topScore) {
topScore = score;
topCommand = possibleMoves[i].c;
}
}
return topCommand;
}
},
{
name: "Livin' on the Edge",
color: "#364030",
run:function (myself, others, coins) {
x = myself.locationX;
y = myself.locationY;
xymax = myself.arenaLength - 1;
if (x < xymax && y == 0) {
return 'east';
} else if (y < xymax && x == xymax) {
return 'south';
} else if (x > 0 && y == xymax) {
return 'west';
} else {
return 'north';
}
}
},
{
name: "Not so Blindly",
color: "3ad900",
run: function AI(me, others, coins){
var h = (a,b) => Math.abs(a[0] -b[0]) + Math.abs(a[1] -b[1])
var s = JSON.stringify;
var p = JSON.parse;
var walls = others.slice(0,2).map(s);
var start = [me.locationX, me.locationY];
var goal = coins.pop();
var is_closed = {};
is_closed[s(start)] = 0;
var open = [s(start)];
var came_from = {};
var gs = {};
gs[s(start)] = 0;
var fs = {};
fs[s(start)] = h(start, goal);
var cur;
while (open.length) {
var best;
var bestf = Infinity;
for (var i = 0; i < open.length; ++i) {
if (fs[open[i]] < bestf) {
bestf = fs[open[i]];
best = i;
}
}
cur = p(open.splice(best, 1)[0]);
is_closed[s(cur)] = 1;
if (s(cur) == s(goal)) break;
for (var d of [[0, 1], [0, -1], [1, 0], [-1, 0]]) {
var next = [cur[0] + d[0], cur[1] + d[1]];
if (next[0] < 0 || next[0] >= me.arenaLength ||
next[1] < 0 || next[1] >= me.arenaLength) {
continue;
}
if (is_closed[s(next)]) continue;
if (open.indexOf(s(next)) == -1) open.push(s(next));
var is_wall = walls.indexOf(s(next)) > -1;
var g = gs[s(cur)] + 1 + 10000 * is_wall;
if (gs[s(next)] != undefined && g > gs[s(next)]) continue;
came_from[s(next)] = cur;
gs[s(next)] = g;
fs[s(next)] = g + h(next, goal);
}
}
var path = [cur];
while (came_from[s(cur)] != undefined) {
cur = came_from[s(cur)];
path.push(cur);
}
var c = path[path.length - 1];
var n = path[path.length - 2];
if(n){
if (n[0] < c[0]) {
return "west";
} else if (n[0] > c[0]) {
return "east";
} else if (n[1] < c[1]) {
return "north";
} else {
return "south";
}
}else{
return "none";
}
}
},
{
name: "Wild Goose Chase",
color: "#b6f2be",
run: function (me, others, coins){
x = me.locationX;
y = me.locationY;
dirs = {};
dirs[(x+1)+" "+y] = "east";
dirs[(x-1)+" "+y] = "west";
dirs[x+" "+(y+1)] = "south";
dirs[x+" "+(y-1)] = "north";
mov = {};
mov["east"] = [x+1,y];
mov["west"] = [x-1,y];
mov["north"] = [x,y-1];
mov["south"] = [x,y+1];
possibleDirs = ["east","west","north","south"];
for (i = 0; i < others.length; i++){
if (others[i][0]+" "+others[i][1] in dirs){
possibleDirs.splice(possibleDirs.indexOf(dirs[others[i][0]+" "+others[i][1]]),1);
}
}
if (possibleDirs.length == 4 || Math.floor(Math.random() * 500) == 0){
return "none"
}
for (i = 0; i < possibleDirs.length; i++){
if (mov[possibleDirs[i]][0] == me.arenaLength || mov[possibleDirs[i]][0] < 0
|| mov[possibleDirs[i]][1] == me.arenaLength || mov[possibleDirs[i]][1] < 0){
var index = possibleDirs.indexOf(possibleDirs[i]);
if (index != -1) {
possibleDirs.splice(index, 1);
i--;
}
}
}
if (possibleDirs.length == 0){
return "none";
}
return possibleDirs[Math.floor(Math.random() * possibleDirs.length)];
}
},
{
name: "Coin Magnet",
color: "#238c4d",
run:function (myself,others,coins){
x=myself.locationX;
y=myself.locationY;
power=myself.coins;
arenaSize=myself.arenaLength;
dirX=0;
dirY=0;
for(i=0;i<coins.length;i++){
if(i==0){
dirX+=(coins[i][0]-x)*3
dirY+=(coins[i][1]-y)*3
}
dirX+=(coins[i][0]-x)*2
dirY+=(coins[i][1]-y)*2
}
for(i=0;i<others.length;i++){
dirX+=Math.ceil(0.85*others[i][2])*(others[i][0]-x)
dirX+=Math.ceil(0.85*others[i][2])*(others[i][1]-y)
}
if(Math.abs(dirX)>Math.abs(dirY)){
if(dirX>0){return "east";}
else{return "west";}
}
else if(dirY!=0){
if(dirY>0){return "south";}
else{return "north";}
}
return "none";
}
},
{
name: "ICE Agent",
color: "#40ffbf",
run: function(me, others, coins) {
me.arenaLength = me.arenaLength - 1;
// Calculate the average coin value of bots
var avg = 2;
for (var i = 0; i < others.length; i++) {
avg += others[i][2];
}
avg /= others.length;
// Find nearest coins
var min = [];
var min_distance = 100000
for (var j = 0; j < coins.length; j++) {
var distance = Math.sqrt(Math.pow(me.locationX - coins[j][0],2) + Math.pow(me.locationY - coins[j][1],2));
if (distance < min_distance) {
min_distance = distance;
min = coins[j];
}
}
if (me.coins <= avg || min_distance < 5) {
// If own coinage is lower than the average or a coin is very close, find some coins
// Move straight to the nearest coin
if (me.locationY != min[1]) {
if (me.locationY - min[1] > 0) {
return "north";
} else {
return "south";
}
} else {
if (me.locationX - min[0] > 0) {
return "west";
} else {
return "east";
}
}
} else {
// You have enough money to eat most bots
// Check if already on border
if (me.locationX == 0 || me.locationX == me.arenaLength || me.locationY == 0 || me.locationY == me.arenaLength) {
// Move anticlockwise around the border
if (me.locationX == 0 && me.locationY != 0 && me.locationY != me.arenaLength) {
return "south";
}
if (me.locationX == 0 && me.locationY == 0) {
return "south";
}
if (me.locationY == me.arenaLength && me.locationX != 0 && me.locationX != me.arenaLength) {
return "east";
}
if (me.locationX == 0 && me.locationY == me.arenaLength) {
return "east";
}
if (me.locationX == me.arenaLength && me.locationY != 0 && me.locationY != me.arenaLength) {
return "north";
}
if (me.locationX == me.arenaLength && me.locationY == me.arenaLength) {
return "north";
}
if (me.locationY == 0 && me.locationX != 0 && me.locationX != me.arenaLength) {
return "west";
}
if (me.locationX == me.arenaLength && me.locationY == 0) {
return "west";
}
} else {
// Find the nearest border and move to it
if (me.locationX <= me.arenaLength - me.locationX) {
// Move to left border
return "west";
} else {
// Move to right border
return "east";
}
}
}
}
},
{
name: "Seeker.Armageddon",
color: "#004d3d",
run:function(me, others, coins) {
var x = me.locationX;
var y = me.locationY;
var dirs = [];
for (var i = 0; i < others.length; i++) {
if (Math.abs(others[i][0] - x) <= 3 && Math.abs(others[i][0] - y) <= 3) {
if (others[i][3] < me.coins && others[i][3] * 0.85 > 5) {
if (Math.abs(others[i][0] - x) > Math.abs(others[i][1] - y)) {
if (others[i][0] - x < 0) {
dirs.push('west');
} else {
dirs.push('east');
}
} else if (Math.abs(others[i][0] - x) < Math.abs(others[i][1] - y)) {
if (others[i][1] - y < 0) {
dirs.push('north');
} else {
dirs.push('south');
}
} else {
if (Math.random() < 0.5) {
if (others[i][0] - x < 0) {
dirs.push('west');
} else {
dirs.push('east');
}
} else {
if (others[i][1] - y < 0) {
dirs.push('north');
} else {
dirs.push('south');
}
}
}
} else if (others[i][3] >= me.coins) {
if (Math.abs(others[i][0] - x) < Math.abs(others[i][1] - y)) {
if (others[i][0] - x < 0) {
dirs.push('east');
} else {
dirs.push('west');
}
} else if (Math.abs(others[i][0] - x) > Math.abs(others[i][1] - y)) {
if (others[i][1] - y < 0) {
dirs.push('south');
} else {
dirs.push('north');
}
} else {
if (Math.random() < 0.5) {
if (others[i][0] - x < 0) {
dirs.push('east');
} else {
dirs.push('west');
}
} else {
if (others[i][1] - y < 0) {
dirs.push('south');
} else {
dirs.push('north');
}
}
}
}
}
}
for (i = 1; i < 5; i++) {
if (Math.abs(coins[i][0] - x) <= 5 && Math.abs(coins[i][1] - y) <= 5) {
if (Math.abs(coins[i][0] - x) > Math.abs(coins[i][1] - y)) {
if (coins[i][0] - x < 0) {
dirs.push('west');
} else {
dirs.push('east');
}
} else if (Math.abs(coins[i][0] - x) < Math.abs(coins[i][1] - y)) {
if (coins[i][1] - y < 0) {
dirs.push('north');
} else {
dirs.push('south');
}
} else {
if (Math.random() < 0.5) {
if (coins[i][0] - x < 0) {
dirs.push('west');
} else {
dirs.push('east');
}
} else {
if (coins[i][1] - y < 0) {
dirs.push('north');
} else {
dirs.push('south');
}
}
}
}
}
var fin = [];
for (i = 0; i < dirs.length; i++) {
if (dirs[i] == 'north' && y !== 0 && fin.indexOf('north') == -1) {
fin.push('north');
} else if (dirs[i] == 'east' && x != me.arenaLength - 1 && fin.indexOf('east') == -1) {
fin.push('east');
} else if (dirs[i] == 'west' && x !== 0 && fin.indexOf('west') == -1) {
fin.push('west');
} else if (dirs[i] == 'south' && y != me.arenaLength && fin.indexOf('south') == -1) {
fin.push('south');
}
}
if (fin.length > 1) {
return fin[randInt(0, fin.length - 1)];
} else if (fin.length == 1) {
return fin[0];
} else {
return 'none';
}
}
}
];
var coinLocations = [];
var botCount = botData.length;
var arenaSize = botCount + 4;
var botPos = [];
var botNotes = {
storeData: function(key, value) {
var bot = botData[botNotes.storeData.caller.index];
bot.kvpData[key] = value;
},
getData: function(key) {
var bot = botData[botNotes.getData.caller.index];
return bot.kvpData[key] || null;
}
};
function randInt(min, max) {
return Math.floor(Math.random() * ((max - min) + 1)) + min;
}
function botSetup() {
var n, close;
for (var b, r, i = 0; i < botCount; i++) {
b = botData[i];
b.coins = 0;
b.loc = [];
b.kvpData = {};
b.alive = true;
b.run.index = i;
r = getValidPosition();
b.loc = r;
botPos[i] = r;
}
for (i = 0; i < 5; i++) {
r = getValidPosition();
coinLocations[i] = r;
}
}
function runBots() {
var ind, s, l, n, c, coins, botSet = [];
var botsAlive = botPos.filter(function(a){return !(arrayEqual(a, [-2,-2]))});
botsAlive.forEach(function(arr){
for (n = 0; n < botCount; n++) {
if (botPos[n] == arr) {
coins = botData[n].coins;
}
}
botSet.push([arr[0], arr[1], coins]);
});
for (var m, b, i = 0; i < botCount; i++) {
b = botData[i];
if (b.alive) {
m = b.run({locationX: b.loc[0], locationY: b.loc[1], coins: b.coins, arenaLength: arenaSize}, JSON.parse(JSON.stringify(botSet.filter((a)=>(a[0]!=b.loc[0] || a[1]!=b.loc[1])))), JSON.parse(JSON.stringify(coinLocations)));
if (m === "north") {
b.loc[1] -= 1;
} else if (m === "east") {
b.loc[0] += 1;
} else if (m === "south") {
b.loc[1] += 1;
} else if (m === "west") {
b.loc[0] -= 1;
} else {
//Bot was lazy/indecisive/made a typo
}
}
}
var pos = [];
var pbot = [];
var col = [];
var dat = [];
for (i = 0; i < botCount; i++) {
b = botData[i];
if (b.alive) {
if (arrayIndex(pos, b.loc) != -1) { //if two bots in same space
if (arrayIndex(col, b.loc) != -1) { //if three+ bots in same space
for (n = 0; n < dat.length; n++) {
l = dat[n];
if (l.loc == b.loc) {
l.count += 1;
l.indexes.push(n);
}
}
} else {
col.push(b.loc);
dat.push({loc:b.loc,count:1,indexes:[i,pbot[arrayIndex(pos, b.loc)]]});
}
} else {
pos.push(b.loc);
pbot.push(i);
}
}
}
for (i = 0; i < dat.length; i++) {
l = dat[i];
s = l.indexes;
s.sort((a,b)=>(botData[b].coins - botData[a].coins));
c = botData[s[0]];
if (c.coins == botData[s[1]].coins) {
let eatenBots = [];
for (n = 0; n < s.length; n++) {
b = botData[s[n]];
eatenBots.push(b.name);
b.alive = false;
b.mutual = b.mutual ? b.mutual+1 : 1;
}
postEvent("Mutual eating of bots: " + eatenBots.join(", ") + ".", "Eating");
} else {
let eatenBots = [];
let eatenCoins = 0;
for (n = 1; n < s.length; n++) {
b = botData[s[n]];
c.coins += Math.ceil(b.coins * 0.85);
eatenCoins += Math.ceil(b.coins * 0.85);
b.alive = false;
c.ate = c.ate ? c.ate+1 : 1;
c.ateCoins = c.ateCoins ? c.ateCoins + Math.ceil(b.coins * 0.85) : Math.ceil(b.coins * 0.85);
b.eaten = b.eaten ? b.eaten+1 : 1;
eatenBots.push(b.name);
}
if(eatenBots.length)
postEvent(c.name + " has eaten " + eatenBots.join(", ") + " for " + eatenCoins + " coins.", "Eating");
while (arrayIndex(coinLocations, c.loc) !== -1) {
ind = arrayIndex(coinLocations, c.loc);
if (ind === 0) {
c.coins += 5;
postEvent(c.name + " picked up a gold coin.", "Gold");
} else {
c.coins += 2;
postEvent(c.name + " picked up a silver coin.", "Silver");
}
coinMove(ind);
}
}
}
for (i = 0; i < botCount; i++) {
b = botData[i];
while (b.alive && arrayIndex(coinLocations, b.loc) !== -1) {
ind = arrayIndex(coinLocations, b.loc);
if (ind === 0) {
b.coins += 5;
postEvent(b.name + " picked up a gold coin.", "Gold");
} else {
b.coins += 2;
postEvent(b.name + " picked up a silver coin.", "Silver");
}
coinMove(ind);
}
botPos[i] = b.loc;
if (!(b.alive)) {
botPos[i] = [-2,-2];
}
if (b.loc[0] < 0 || b.loc[1] < 0 || b.loc[0] >= arenaSize || b.loc[1] >= arenaSize) {
if(b.alive)
{
b.flights = b.flights ? b.flights+1 : 1;
postEvent(b.name + " has fled shamefully.", "Flight");
}
b.alive = false;
botPos[i] = [-2,-2];
}
}
}
function coinMove(ind) {
var r, x, y, n, close;
do {
r = [randInt(0, arenaSize - 1), randInt(0, arenaSize - 1)];
for (n = 0; n < botPos.length; n++) {
x = botPos[n][0];
y = botPos[n][1];
if (Math.abs(x - r[0]) <= 1 && Math.abs(y - r[1]) <= 1) {
close = true;
} else {
close = false;
}
}
} while (close);
coinLocations[ind] = r;
}
function arrayIndex(arr, inc) {
for (var i = 0; i < arr.length; i++) {
if (inc instanceof Array) {
if (arr[i] instanceof Array && arr[i].length == inc.length) {
for (var c = 0, n = 0; n < inc.length; n++) {
if (arr[i][n] === inc[n]) {
c++;
}
}
if (c == inc.length) {
return i;
}
}
} else if (arr[i] === inc) {
return i;
}
}
return -1;
}
function arrayEqual(arr, equ) {
if (arr.length !== equ.length) {
return false;
}
for (var i = 0; i < arr.length; i++) {
if (arr[i] !== equ[i]) {
return false;
}
}
return true;
}
//Positioning Program, made by N.P. and tested by Sundar
function getValidPosition() {
let pos = 0;
while (pos === 0) {
let x = randInt(0, arenaSize-1);
let y = randInt(0, arenaSize-1);
let allowed = true;
for (let i = 0; i < botPos.length; i++) {
let thisX = botPos[i][0];
let thisY = botPos[i][1];
if (Math.abs(thisX-x) <= 1 && Math.abs(thisY-y) <= 1) {
allowed = false;
}
}
if (allowed) {
pos = [x, y];
}
}
return pos;
}
//Different Auto-run function, created by Ramillies, based on:
//Auto-run function, created by RedwolfPrograms
function initCanvas()
{
botSetup();
createLeaderboard();
updateCanvas();
updateLeaderboard();
turnCanvas.turns = document.getElementById("rounds").value;
turnCanvas.fps = document.getElementById("fps").value;
postEvent("Game initialized (" + turnCanvas.turns + " rounds @ " + turnCanvas.fps + " fps).", "MainGame");
document.getElementById("rounds").disabled = true;
document.getElementById("startButton").disabled = false;
document.getElementById("stepButton").disabled = false;
document.getElementById("initButton").disabled = true;
}
function activateCanvas() {
let fps = document.getElementById("fps").value;
turnCanvas.clear = setInterval(runMoves, 1000 / fps);
}
function startGame()
{
activateCanvas();
document.getElementById("startButton").disabled = true;
document.getElementById("fps").disabled = true;
document.getElementById("stepButton").disabled = true;
document.getElementById("pauseButton").disabled = false;
}
function runMoves()
{
if (turnCanvas.inc >= turnCanvas.turns) {
if(turnCanvas.clear)
clearInterval(turnCanvas.clear);
evaluateGame();
}
else
{
turnCanvas.inc++;
runBots();
if((document.getElementById("fps").value < 100) || (!(turnCanvas.inc % Math.floor(document.getElementById("fps").value/50))))
{
updateCanvas();
updateLeaderboard();
}
}
}
function pauseGame()
{
if(turnCanvas.clear)
clearInterval(turnCanvas.clear);
document.getElementById("startButton").disabled = false;
document.getElementById("stepButton").disabled = false;
document.getElementById("pauseButton").disabled = true;
document.getElementById("fps").disabled = false;
}
function resumeGame()
{
let fps = document.getElementById("fps").value;
if(turnCanvas.clear === null)
setInterval(runMoves, 1000 / fps);
}
function evaluateGame()
{
let living = botData.filter(b => b.alive);
document.getElementById("Title").innerHTML = "Display";
if(living.length == 0)
{
postEvent("Game drawn.", "MainGame");
return;
}
living.sort((a, b) => b.coins - a.coins);
postEvent("Game won by " + living[0].name + " (" + living[0].coins + " coins).", "MainGame");
if(tournament)
{
if((living.length == 1) || (living[0].coins > living[1].coins))
living[0].wins = living[0].wins ? living[0].wins+1 : 1;
if(turnCanvas.games > 1)
{
turnCanvas.turns = document.getElementById("rounds").value;
turnCanvas.fps = document.getElementById("fps").value;
turnCanvas.games = document.getElementById("games").value -1;
turnCanvas.inc = 0;
resetEverything();
document.getElementById("rounds").value = turnCanvas.turns;
document.getElementById("fps").value = turnCanvas.fps;
document.getElementById("games").value = turnCanvas.games;
initCanvas();
startGame();
}
else
{
resetEverything();
initCanvas();
}
}
}
function startTournament()
{
if(!document.getElementById("games").value)
{
alert("Please specify a number of games for the tournament.");
return;
}
tournament = true;
turnCanvas.turns = document.getElementById("rounds").value;
turnCanvas.fps = document.getElementById("fps").value;
turnCanvas.games = document.getElementById("games").value;
initCanvas();
startGame();
document.getElementById("games").disabled = true;
}
//Append HTML and CSS for display
//Display program by N. P., who is awesome beyond belief
// Despaghettized by most illustrious Ramillies, who also added a ranking system and an event log :--)
var squareSize = 16
var canvas, context;
var turnCanvas = {turns: 0, inc: 0, games: 0, clear: null};
function resetEverything()
{
document.body.innerHTML = "<h1 id='Title'>Display</h1>"+
"<div style='float: left'> <canvas id='Canvas'></canvas>"+
"<p>Rounds: <input id=rounds value=200 size=5> FPS: <input id=fps value=5 size=5> Games: <input id=games value='' size=5></p>"+
"<p><input type='button' id=initButton value='Init' onclick='initCanvas()'> <input type=button id=startButton disabled value='Start' onclick='startGame()'> <input type='button' id=pauseButton disabled value='Pause' onclick='pauseGame()'> <input type=button id=stepButton disabled value=Step onclick='runMoves()'> <input type=button value=Tournament id=tournament onclick='startTournament()'></p></div>"+
"<div style='float: left'> <table id='leaderboard'>"+
"<tr><th>C<th>Name<th>Rank<th>Ate*<th>ME†<th>Eaten<th>Fled<th>Score<th>Wins<th>Track?"+
"</table><p>*: Number of bots eaten by the bot (and coins earned from that).<br>†: Number of mutual eatings in which the bot participated.</p></div>"+
"<div style='clear: both'>"+
"<h1 id='EventLogCaption'>Event Log</h1>"+
"<textarea id='EventLog' rows=10 cols=80></textarea>"+
"<p>Annoy me with these events:</p>"+
"<input type='checkbox' id='AddGold'><label for='AddGold'>Gold pickups</label><input type='checkbox' id='AddSilver'><label for='AddSilver'>Silver pickups</label><input type='checkbox' checked id='AddEating'><label for='AddEating'>Bot(s) being eaten</label><input type='checkbox' checked id='AddFlights'><label for='AddFlights'>Bot(s) leaving arena</label>"+
"</div>";
canvas = document.getElementById("Canvas");
canvas.width = arenaSize*squareSize
canvas.height = arenaSize*squareSize
context = canvas.getContext('2d');
}
function drawCircle(x, y, color) {
var trueX = x * squareSize + squareSize/2
var trueY = y * squareSize + squareSize/2
var radius = squareSize/8*3
context.beginPath();
context.arc(trueX, trueY, radius, 0, 2 * Math.PI, false);
context.fillStyle = color;
context.fill();
context.lineWidth = '1';
context.strokeStyle = '#000000';
context.stroke();
context.closePath()
}
function drawBot(x, y, color, name) {
var lowX = x * squareSize
var lowY = y * squareSize
context.beginPath()
context.lineWidth = document.getElementById(name+"_checkbox").checked ? 4:1
context.strokeStyle = document.getElementById(name+"_checkbox").checked ? '#ff0000':'#000000'
context.rect(lowX+.5, lowY+.5, squareSize-1, squareSize-1);
context.stroke()
context.fillStyle = color;
context.fillRect(lowX+1, lowY+1, squareSize-2, squareSize-2);
context.closePath()
}
function updateCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height)
context.beginPath()
for (var i = 0; i < arenaSize; i++)
{
context.moveTo(i * squareSize, 0);
context.lineTo(i * squareSize, arenaSize * squareSize);
context.moveTo(0, i * squareSize);
context.lineTo(arenaSize * squareSize, i * squareSize);
}
context.strokeStyle = '#dddddd';
context.stroke();
context.closePath()
for (var i = 1; i < coinLocations.length; i++)
drawCircle(coinLocations[i][0], coinLocations[i][1], '#b4b4b4')
drawCircle(coinLocations[0][0], coinLocations[0][1], '#ffd700')
for (var i = 0; i < botPos.length; i++)
if (botPos[i][0] > -2)
drawBot(botPos[i][0], botPos[i][1], botData[i].color ? botData[i].color : '#ff0000', botData[i].name)
}
function createLeaderboard() {
var table = document.getElementById('leaderboard')
for (var i = 0; i < botData.length; i++) {
var newRow = document.createElement('tr')
var newColor = document.createElement('td')
var newName = document.createElement('td')
var newPlace = document.createElement('td')
var newAte = document.createElement('td')
var newEaten = document.createElement('td')
var newMutual = document.createElement('td')
var newFlights = document.createElement('td')
var newScore = document.createElement('td')
var newWins = document.createElement('td')
var track = document.createElement('td')
newRow.id = botData[i].name + 'Row'
newColor.id = botData[i].name + 'Color'
newName.id = botData[i].name + 'Name'
newPlace.id = botData[i].name + 'Place'
newAte.id = botData[i].name + 'Ate'
newEaten.id = botData[i].name + 'Eaten'
newMutual.id = botData[i].name + 'Mutual'
newFlights.id = botData[i].name + 'Flights'
newScore.id = botData[i].name + 'Score'
newWins.id = botData[i].name + 'Wins'
track.id = botData[i].name + 'Track'
newName.innerHTML = botData[i]['name']
track.innerHTML = "<input type=\"checkbox\" id=\""+botData[i].name+"_checkbox\">"
newRow.appendChild(newColor)
newRow.appendChild(newName)
newRow.appendChild(newPlace)
newRow.appendChild(newAte)
newRow.appendChild(newMutual)
newRow.appendChild(newEaten)
newRow.appendChild(newFlights)
newRow.appendChild(newScore)
newRow.appendChild(newWins)
newRow.appendChild(track)
table.appendChild(newRow)
}
}
function updateLeaderboard() {
let rankIndices = [];
for(let i = 0; i < botData.length; i++)
if(botData[i].alive)
rankIndices.push(i);
rankIndices.sort((a, b) => botData[b].coins - botData[a].coins);
for (var i = 0; i < botData.length; i++)
{
var colorDisplay = document.getElementById(botData[i].name + 'Color')
var nameDisplay = document.getElementById(botData[i].name + 'Name')
var rankDisplay = document.getElementById(botData[i].name + 'Place')
var ateDisplay = document.getElementById(botData[i].name + 'Ate')
var eatenDisplay = document.getElementById(botData[i].name + 'Eaten')
var mutualDisplay = document.getElementById(botData[i].name + 'Mutual')
var flightsDisplay = document.getElementById(botData[i].name + 'Flights')
var scoreDisplay = document.getElementById(botData[i].name + 'Score')
var winsDisplay = document.getElementById(botData[i].name + 'Wins');
if (!botData[i]['alive'])
{
rankDisplay.innerHTML = ''
nameDisplay.style.color = 'grey'
scoreDisplay.style.color = 'grey'
ateDisplay.style.color = 'grey';
eatenDisplay.style.color = 'grey';
mutualDisplay.style.color = 'grey';
flightsDisplay.style.color = 'grey';
winsDisplay.style.color = 'grey';
scoreDisplay.innerHTML = botData[i]['coins']
ateDisplay.innerHTML = botData[i].ate ? botData[i].ate + "(" + botData[i].ateCoins + ")" : "";
eatenDisplay.innerHTML = botData[i].eaten ? botData[i].eaten : "";
mutualDisplay.innerHTML = botData[i].mutual ? botData[i].mutual : "";
flightsDisplay.innerHTML = botData[i].flights ? botData[i].flights : "";
colorDisplay.style.backgroundColor = '#f8f8ff';
}
else
{
colorDisplay.style.backgroundColor = botData[i].color ? botData[i].color : '#ff0000';
ateDisplay.innerHTML = botData[i].ate ? botData[i].ate + "(" + botData[i].ateCoins + ")" : "";
eatenDisplay.innerHTML = botData[i].eaten ? botData[i].eaten : "";
mutualDisplay.innerHTML = botData[i].mutual ? botData[i].mutual : "";
flightsDisplay.innerHTML = botData[i].flights ? botData[i].flights : "";
scoreDisplay.innerHTML = botData[i]['coins'];
winsDisplay.innerHTML = botData[i].wins ? botData[i].wins : "";
let rank = rankIndices.findIndex(a => a == i);
rankDisplay.innerHTML = rank != -1 ? "<b>" +(rank+1)+"</b>" : "—";
if(rank == 0)
rankDisplay.style.color = 'red';
else if(rank == 1)
rankDisplay.style.color = 'green';
else if(rank == 2)
rankDisplay.style.color = 'blue';
else
rankDisplay.style.color = 'black';
}
}
document.getElementById("Title").innerHTML = "Display (round " + turnCanvas.inc + ")";
}
function postEvent(what, type)
{
let permitGold = document.getElementById("AddGold").checked;
let permitSilver = document.getElementById("AddSilver").checked;
let permitEating = document.getElementById("AddEating").checked;
let permitFlights = document.getElementById("AddFlights").checked;
if(
((type == "Gold") && permitGold) ||
((type == "Silver") && permitSilver) ||
((type == "Eating") && permitEating) ||
((type == "Flight") && permitFlights) ||
(type == "MainGame")
)
{
let textarea = document.getElementById('EventLog');
textarea.value += "[" + turnCanvas.inc + "] " + what + "\n";
textarea.scrollTop = textarea.scrollHeight;
}
}
resetEverything();
var tournament = false;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment