50
\$\begingroup\$

Note: The survey for community favorite will be released soon

In this KoTH, the aim is to be the last bot alive. Coins will be placed in random areas, and your bot must get the coins first. If a bot runs into another bot, the bot with more coins wins, and the other bot dies. More details below.

Coin types

There will be 2 types of coins: gold and silver. Gold adds 5 coins to the bot's strength, and silver adds 2. Once a coin is collected, another coin is placed at another spot on the board. At any given time, there is one gold coin and four silver coins in the arena.

Bot collisions

In the event of two bots trying to occupy the same space, the one with more coins will stay, and the one with less will...not. The winning bot will gain 85% of opponents coins (Rounded up). If they are tied, both die. If three or more try to occupy the same space, the most powerful wins, and gets 85% of all the other bot's coins. In the event that the most powerful bot is a tie, all of the bots die who tried to enter the space.

Arena

The arena's side length is calculated with 4 + botCount. When placing bots in the beginning of the game, random places are chosen. The system ensures that no bots start in the same space, or next to each other. Coins generate randomly, excluding a 3 by 3 square centered on each bot. If a bot is found outside of the arena, it dies instantly. The arena starts at (0,0), or Northwest, in the upper left corner, and the location of a bot is always an integer.

Your bot

Your bot should be a function, in any object oriented language that has arrays, integers, strings, and functions. Note that all submissions will be converted to Javascript, to make things simple. To store information between moves, use botNotes.storeData(key, value) and botNotes.getData(key, value). You may not store or access data any way, other than that which is provided through the parameters and botNotes. You should create a function that, when called, returns a string north, east, south, west, or none. There will be 3 arguments for the function:

  • An object with four integers (locationX, locationY, coins, arenaLength), your current location, your coins, and the length of the arena

  • A multidimensional array with the X and Y coordinates of other bots, and their coin count, ex-[[0,5,4],[4,7,1],[7,4,12]]

  • An array with the coin locations listed (Gold is always first)

This is a king of the hill challenge, Standard Loopholes prohibited. Your function will be run several thousand times, each time allowed one "Move". Note that if the game exceeds 20,000 moves, the bot with the most coins wins. This will be done 8,000 times, to remove randomness.

Chatroom: https://chat.stackexchange.com/rooms/81347/gold-collectors-koth

Prizes:

First Place: 100-point bounty
Community Favorite: 15-point accepted answer

Winners:

First Place: TBTPTGCBCBA
Second Place: Big King Little Hill
Third Place: Potentially Victorious
Fourth Place: Polite Near-Sighted Drunk Bot
Fifth Place: Safety Coin

\$\endgroup\$
17
  • 6
    \$\begingroup\$ "Note that all submissions will be converted to Javascript, to make things simple." How is this supposed to work? Do you make the conversion? \$\endgroup\$
    – Laikoni
    Commented Aug 8, 2018 at 14:20
  • 23
    \$\begingroup\$ There's nothing wrong with a koth only allowing a single language, especially one as widely spread as JavaScript. Rather than ambiguously "converting" answers to JavaScript (presumably yourself and by hand) you should simply limit the challenge to JS only. We've had plenty of Java-only and Python-only koths in the past, after all. \$\endgroup\$
    – Mayube
    Commented Aug 8, 2018 at 14:23
  • 2
    \$\begingroup\$ A version of this where you control acceleration instead of position would be pretty cool. \$\endgroup\$
    – akozi
    Commented Aug 8, 2018 at 19:21
  • 13
    \$\begingroup\$ To everyone: There are waaaay too many comments already. Do not leave useless comments such as "+1, nice bot", and please delete your comments if it's redundant. We are not a typical Q&A site, but nobody likes reading hundreds of commands. \$\endgroup\$
    – user202729
    Commented Aug 9, 2018 at 5:16
  • 5
    \$\begingroup\$ (To my own question): According to N.P. in chat, it is the latter: all the bots move, then all the conflicts get resolved, then all the coin pickups occur, then all the new coins are placed. \$\endgroup\$
    – BradC
    Commented Aug 9, 2018 at 20:59

34 Answers 34

29
\$\begingroup\$

BaitBot - JavaScript Node.JS

Why bother chasing or running if you can never catch? Instead, BaitBot finds the nearest coin and waits for a weaker bot to also approach it. When they're both adjacent, baitBot goes for the coin assuming the weaker bot will as well. If baitBot is waiting and a stronger bot approaches, he just grabs the coin and skedaddles. Try Me!

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) => distanceBetween(a, closestCoin) - distanceBetween(b, closestCoin))[0]
  if(nearestThreat && distanceBetween(nearestThreat, closestCoin) <= 2) {
    return directions[adjacentDir(closestCoin)]
  }

  

  //Otherwise, wait until there's a target also next to the coin. If none are close, just take it
  let targets = others.filter(a => edibility(a) > 0 && distanceBetween(closestCoin, a) <= 3)
  targets.sort((a,b) => distanceBetween(a, closestCoin) - distanceBetween(b, closestCoin))
  if(targets.length > 0 && distanceBetween(targets[0], closestCoin) > 1){
    return directions[0]
  }
  return directions[adjacentDir(closestCoin)]

}
\$\endgroup\$
13
  • 1
    \$\begingroup\$ Ha, that's a neat idea, I like it. \$\endgroup\$
    – Sundar R
    Commented Aug 8, 2018 at 21:37
  • \$\begingroup\$ This approach is pretty cool...no, very cool \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 0:45
  • 1
    \$\begingroup\$ BaitBot needs nearestThreat && distanceTo(nearestThreat) rather than just distanceTo(nearestThreat). It fails when there is no threat. \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 13:55
  • 1
    \$\begingroup\$ Yes, nearestThreat is undefined if all other bots have more points than yours. \$\endgroup\$
    – Night2
    Commented Aug 10, 2018 at 17:35
  • 1
    \$\begingroup\$ Well, I get notices like [10] Bot Bait Bot tired of this world, and jumped off its edge in my event log \$\endgroup\$
    – rydwolf
    Commented Aug 13, 2018 at 15:17
18
\$\begingroup\$

Big King Little Hill | JavaScript

function BigKingLittleHill(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.filter(enemy => enemy[2] >= me.coins/50).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]);
		}
	}
	//no moves are safe (uh oh!), return the highest priority
	return coordToString(moves[0][0])
}

Try it online!

Big King Little Hill makes decisions based on “zones of control”. It will only pursue coins that are in its zone of control meaning it can reach the coin before any other bot can. When there are no coins in its zone of control it instead moves to maximize the size of its zone of control. Big King Little Hill calculates the zone of control of each of its 5 possible moves and favors the moves that maximize the size of its zone of control. In this way, Big King Little Hill eventually reaches a local maximum (little hill) of control and waits for a coin to be generated within its zone. Additionally, Big King Little Hill rejects any move that could result in its death unless there are no alternatives.

Big King Little Hill is a pessimist (it prefers the term realist) because it does not bother to contest any coin it isn't assured to get. It is also a pacifist in that it does not pursue weaker bots in any sense (although it might step on one if they get in the way). Lastly, Big King Little Hill is a coward that will not jeopardize its own life for any reward unless it absolutely has to.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Welcome to PPCG! That's a very good bot =D \$\endgroup\$ Commented Aug 10, 2018 at 20:55
  • \$\begingroup\$ This is what I was thinking for a bot. Good job. \$\endgroup\$
    – Jo.
    Commented Aug 11, 2018 at 18:48
16
\$\begingroup\$

First Gen Learning Algorithm | JavaScript (Node.js)

function run() {
	return ['north','east','south','west'][(Math.random()*4)|0];
}

Try it online!

Have you ever seen those timelapses of learning algorithms learning to play a game? They often move almost randomly in the first few generations...

\$\endgroup\$
3
  • \$\begingroup\$ LOL...It might still work! \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:10
  • 2
    \$\begingroup\$ Most learning algorithms work literally random for the first few iterations, not just almost randomly. \$\endgroup\$ Commented Aug 8, 2018 at 16:56
  • \$\begingroup\$ Guess what? This bot got almost 0.22 coins per round on average! \$\endgroup\$
    – rydwolf
    Commented Aug 16, 2018 at 22:44
16
\$\begingroup\$

Potentially Victorious | JavaScript

Preferred color for this bot is #1600a6.

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 = Math.ceil(20*(1+Math.log2(me.coins/25)));
    else
    huntingTimer++;

    function distanceFromMe(X, Y) { return Math.abs(me.locationX - X) + Math.abs(me.locationY - Y); }

    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 reachability = [1, 1, 1, 1, 1];
    let distances = coins.map(c => distanceFromMe(c[0], c[1]));
    for(let i = 0; i < others.length; i++)
    {
        for(let coin = 0; coin < 5; coin++)
            reachability[coin] += (Math.abs(others[i][0] - coins[coin][0]) + Math.abs(others[i][1] - coins[coin][1])) < distances[coin];
    }

    let potential = gravitation(40, coins[0][0], coins[0][1]) / (reachability[0]); // Gold

    // Silver
    for(let i = 1; i < 5; i++)
    {
        potential += gravitation(10, coins[i][0], coins[i][1]) / (reachability[i]);
    }

    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])) < 3) &&
            (huntingTimer == 0) &&
            (me.coins > 25) && 
            (me.coins < (others[0][2]*.9)) &&
            (others[i][2] < me.coins-5) && (others[i][2] >= 10)
        )
        {
            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]);
        }
    }

    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];
}

(Apologies for sloppy formatting, the 4 spaces indentation for this site doesn't go well with my custom of using tabs.)

Rough explanation

I hereby resign at the attempt to update the explanation of the formulae. The coefficients are constantly changing and it's kinda hard to keep the explanation up to date. So I will just explain the general principle.

Each coin and each bot generates a force field with some potential. I just add the potentials from everything together and the bot then goes wherever the potential is the lowest. (Obviously this idea is stolen from physics.)

I use two kinds of potentials. First one is a pseudo-gravitational one (which acts at any range), with $$U = - \frac{k}{r + \frac 1 5} \cdot \frac{1}{1 + n}.$$ The k is a "strength" of the field, and, with this choice of sign, the potential is attractive. The r here (and everywhere else) is the distance in the taxicab metric, r = |x₁ - x₂| + |y₁ - y₂|.

I use k = 40 for gold coins and k = 10 for silver coins. n is the number of bots who are nearer to the particular coin than we are. Otherwise, we absolutely ignore the other bots (if we get in the way of a stronger bot, we run away, but that's it). I value gold coins for more than they are worth because otherwise the bots that go primarily after the gold all the time beat me.

The second potential is an exponentially decaying one (which effectively acts only at very small distances). This is generated by the other, mainly the more powerful, bots.

These produce a field with $$ U = -5 \times 1400 e^{-3r}. $$ This force is prohibitively strong at range 0-1, but decays to almost nothing at the larger distances. (Distance + 1 means cutting the force in 1/20.)

We generally don't attack the other bots intentionally (of course if they get in our way and we step on them, it's their fault), but there is a possibility to do that. If some harsh conditions are fulfilled, we may enter the hunting mode, being focused on a single bot. To enter the hunting mode:

  1. We must have at least 25 coins. (We need to get some coins first.)
  2. They must have at most (our coins - 5) coins, and at least 10 coins. (We don't want to be hunting someone who grabs one coin and is suddenly more powerful, and we don't want to be pursuing zero-coin bots either.)
  3. We must lag behind the currently leading bot by at least 1/10 of his coins. (You need to be lucky to hunt something, so there is no need to give away a good position just for trying our luck.)
  4. We must not be on a hunting cooldown (see below).

If all these are satisfied, the hunting mode is activated. For the next 10 rounds, the hunted bot only emits the potential $$ U = -150 e^{-r}. $$ After these 10 rounds elapse, we enter the hunting cooldown, during which we may not enter hunting mode again. (That is to prevent us from endlessly and fruitlessly chasing one bot while all other happily grab coins.) The hunting cooldown is 20 rounds when we have 25 coins, and increases by 20 coins per each doubling of that. (In other words, the cooldown is 20(1 + log2(c / 25)).) (We use that because in the endgame, all the huntable bots are most likely dead, and so any hunt will be most likely in vain. Because of that, we want to limit the wasted time. But sometimes, a lucky late-game eating can change everything, so we keep the possibility.)

Finally, the whole arena is placed into an infinite potential well which prevents the bot from escaping.

\$\endgroup\$
16
  • \$\begingroup\$ I think this bot probably beats out my weighted movement too. \$\endgroup\$ Commented Aug 8, 2018 at 18:18
  • \$\begingroup\$ @fəˈnɛtɪk — So do I :—). I think that this one can handle the other bots quite a bit better. (Also it's not "blinded" by a coin at an adjacent square.) But I definitely gave you +1 because I'm a huge fan of this idea. \$\endgroup\$
    – Ramillies
    Commented Aug 8, 2018 at 18:25
  • \$\begingroup\$ Could you apply the principle of least action to this? \$\endgroup\$
    – Beta Decay
    Commented Aug 8, 2018 at 19:05
  • \$\begingroup\$ @BetaDecay: Not quite I'm afraid, calculus doesn't respond well to discrete problems like this. Even in the continuous case it wouldn't be trivial (because of fixed magnitude of velocity), but it could be doable after some polar coordinates magic I guess. \$\endgroup\$
    – Ramillies
    Commented Aug 8, 2018 at 19:10
  • 4
    \$\begingroup\$ This is cool. I'm not sure risk is monotonically related to distance though, it seems like a threat 2 spaces away is more likely to kill you than one adjacent, since in the latter case one of you needs to stay stationary for a collision to occur. \$\endgroup\$
    – Cain
    Commented Aug 8, 2018 at 21:45
12
+100
\$\begingroup\$

The Bot That Plays The Game Cautiously But Can Be Aggressive | JavaScript

Preferred color: #F24100

Note: Although this bot has taken 1st place, it is due to teaming up with "Feudal Noble" at the end and eating him for more coins. Otherwise this bot would have been 3rd. If you are interested in bots that are more powerful individually, checkout Potentially Victorious and Big King Little Hill.

function (me, monsters, coins) {
    var i, monstersCount = monsters.length, phaseSize = Math.round((me.arenaLength - 4) / 4),
        center = (me.arenaLength - 1) / 2, centerSize = me.arenaLength / 4,
        centerMin = center - centerSize, centerMax = center + centerSize, centerMonsters = 0, centerMonstersAvg = null,
        end = 2e4, apocalypse = end - ((me.arenaLength * 2) + 20), mode = null;

    var getDistance = function (x1, y1, x2, y2) {
        return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
    };

    var isAtCenter = function (x, y) {
        return (x > centerMin && x < centerMax && y > centerMin && y < centerMax);
    };

    var round = botNotes.getData('round');
    if (round === null || !round) round = 0;
    round++;
    botNotes.storeData('round', round);

    var isApocalypse = (round >= apocalypse && round <= end);
    if (isApocalypse) {
        mode = botNotes.getData('mode');
        if (mode === null || !mode) mode = 1;
    }

    for (i = 0; i < monstersCount; i++) if (isAtCenter(monsters[i][0], monsters[i][1])) centerMonsters++;

    var lc = botNotes.getData('lc');
    if (lc === null || !lc) lc = [];
    if (lc.length >= 20) lc.shift();
    lc.push(centerMonsters);
    botNotes.storeData('lc', lc);

    if (lc.length >= 20) {
        centerMonstersAvg = 0;
        for (i = 0; i < lc.length; i++) centerMonstersAvg += lc[i];
        centerMonstersAvg = centerMonstersAvg / lc.length;
    }

    var getScore = function (x, y) {
        var score = 0, i, chaseFactor = 0.75, coinFactor = 1;

        if (monstersCount < phaseSize) {
            chaseFactor = 0;
            coinFactor = 0.25;
        } else if (monstersCount < phaseSize * 2) {
            chaseFactor = 0;
            coinFactor = 0.5;
        } else if (monstersCount < phaseSize * 3) {
            chaseFactor = 0.5;
            coinFactor = 0.75;
        }

        if (isApocalypse) {
            if (mode === 1) {
                var centerDistance = getDistance(x, y, center, center);
                if (centerDistance <= 3) {
                    mode = 2;
                } else {
                    score += 5000 / (centerDistance / 10);
                }
            }
            if (mode === 2) chaseFactor = 1000;
        }

        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 : 10000);
            }
        }

        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 ? 250 : 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;
            if (coinMonsterFactor >= 1) coinMonsterFactor *= 15;
            score += ((coinValue * coinMonsterFactor) / coinDistance) + (centerMonstersAvg === null || centerMonstersAvg > 1.75 ? -1 * (50 / coinDistanceCenter) : 200 / coinDistanceCenter);
        }

        return score + Math.random();
    };

    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 (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;
        }
    }

    if (isApocalypse) botNotes.storeData('mode', mode);

    return topCommand;
}

This bot (aka "TBTPTGCBCBA") tries to make the best decision possible by generating a score for each possible move and selects the move with higher score for each turn.

The scoring system has many details which are evolved since the start of the challenge. They can be described generally like this:

  • The closer the coins are to a possible move, the more score that move gets. If a coin has no other possible contestants, the score goes even higher. If a coin has other possible contestants, the score goes lower.
  • If another bot is close to a possible move and has less coins, depending on the phase of the game, it could mean more score for that move. So it is casual for "TBTPTGCBCBA" to eat a few of other bots in each game.
  • If another bot is close to a possible move with equal or more points, that move gets enough negative score to make sure death is avoided. Of course there could be some cases that all possible moves are bad and death can't be avoided, but that is very rare.
  • There is a mechanism to keep track of number of bots in middle of the board for last 20 turns. If average is low enough, all moves towards coins in the middle get more score and if the average is high, then all moves towards coins in the middle get lower score. This mechanism allows to avoid conflicts with "Feudal Noble". Since the "Feudal Noble" is always in the middle (unless it is being chased), the average number of bots in the middle goes up and "TBTPTGCBCBA" understands to avoid middle if there is a better option outside of middle area. If "Feudal Noble" dies, the average goes down and "TBTPTGCBCBA" understands that it can use the middle.
  • There are some factors that dynamically change based on the phase of the game (detected from number of bots alive), these factors affect the scoring in each of above items.
  • This bot has a special ability. Over time it grows tired of the selfishness of "Feudal Noble" and the oppression of peasants. At the right moment, it will rise to end the unpleasant Feudalism system. A successful attempt not only helps poor peasants, but also provides a higher win chance due to the coins taken from the "Feudal Noble".
\$\endgroup\$
2
  • \$\begingroup\$ It seems a lot more...intelligent than the others \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:15
  • 5
    \$\begingroup\$ I like the monsters part of the params \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 22:25
10
\$\begingroup\$

Safety coin | JavaScript

SafetyCoin=(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";}
  }
}

This bot heads straight towards a weighted valued coin (value/distance) which it can not die from reaching at the same time or after another bot. If there is no valid coin with this property it sits where it is the bot now moves in a random safe direction(safety means that if a bot moves towards it, safety coin cannot collide. This allows the bot to swap places with another bot if immediately next to it), weighted towards the center of the arena.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Huh, this may actually win. Although, there are a lot of bots \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 18:00
  • \$\begingroup\$ This method will have more problems the more unevenly the enemy bots with higher or equal value are placed. \$\endgroup\$ Commented Aug 8, 2018 at 18:01
  • 1
    \$\begingroup\$ Well, they'll probably all be making a beeline to the coin nearest to them/gold coin. \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 18:03
  • \$\begingroup\$ It will only do well if it manages to get some coins right at the start. \$\endgroup\$ Commented Aug 8, 2018 at 18:06
  • \$\begingroup\$ And that is mostly luck, since the place where coins generate is randomly decided \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 18:13
10
\$\begingroup\$

The GUT, JavaScript

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";
    }
}

With Potentially Victorious we've got two fields: the bot field and the coin field. However, nature isn't that complicated. It's time to unify the two fields to produce the Grand Unified Theory.

Firstly, we need to work out what the potential of the field is. Assuming our own bot doesn't influence the field in any way, we can write this as:

$$V = \sum_\limits{n} k_n\left(e^{k_n^2-100(x-x_n)^2}+e^{k_n^2-100(y-y_n)^2}\right)$$

Where \$k_n\$ is the "relative property" of the object and \$(x_n, y_n)\$ are the coordinates of each object.

The relative property of the object is calculated like so:

$$k = \frac{c_{\text{object}}}{c_{\text{me}}}$$

Where \$c\$ is the coin value. (The coin value is positive for bots and negative for coins). And \$c_{\text{me}} = c_{\text{self}} + 1\$ where \$c_{\text{self}}\$ is my own coin value (with 1 correction to prevent a division by zero).

Let's just call this correction part of Modified Betanian Dynamics (MOBD).

We can also find the kinetic energy as:

$$T = \frac{1}{2}c_{\text{me}}(\dot{x}^2 + \dot{y}^2)$$

We can now calculate the action:

$$\begin{align}\text{Action} &= \int^b_a (T-V)dt\\&=\int^b_a \left(\frac{1}{2}c_{\text{me}}(\dot{x}^2 + \dot{y}^2) - \sum_\limits{n} k_n\left(e^{k_n^2-100(x-x_n)^2}+e^{k_n^2-100(y-y_n)^2}\right)\right) dt\end{align}$$

And so the Lagrangian of our bot in the coin-bot field is:

$$\mathcal{L} = \frac{1}{2}c_{\text{me}}(\dot{x}^2 + \dot{y}^2) - \sum_\limits{n} k_n\left(e^{k_n^2-100(x-x_n)^2}+e^{k_n^2-100(y-y_n)^2}\right)$$

We now need to solve the Euler-Lagrange equations:

$$\frac{d}{dt} \frac{\partial \mathcal{L}}{\partial \dot{x}} = \frac{\partial \mathcal{L}}{\partial x}$$

and:

$$\frac{d}{dt} \frac{\partial \mathcal{L}}{\partial \dot{y}} = \frac{\partial \mathcal{L}}{\partial y}$$

So:

$$\frac{d}{dt} \frac{\partial \mathcal{L}}{\partial \dot{x}} = \frac{d}{dt} \left[c_{\text{me}}\dot{x}\right] = c_{\text{me}}\ddot{x}$$

$$\frac{\partial \mathcal{L}}{\partial x} = \sum\limits_n 200 k_n \left(x - x_n\right) e^{k_n^2 - 100(x-x_n)^2}$$

$$\Rightarrow \ddot{x} = \sum\limits_n \frac{200k_n}{c_{\text{me}}} \left(x - x_n\right) e^{k_n^2 - 100(x-x_n)^2}$$

And also:

$$\frac{d}{dt} \frac{\partial \mathcal{L}}{\partial \dot{y}} = \frac{d}{dt} \left[c_{\text{me}}\dot{y}\right] = c_{\text{me}}\ddot{y}$$

$$\frac{\partial \mathcal{L}}{\partial y} = \sum\limits_n 200 k_n \left(y - y_n\right) e^{k_n^2 - 100(y-y_n)^2}$$

$$\Rightarrow \ddot{y} = \sum\limits_n \frac{200k_n}{c_{\text{me}}} \left(y - y_n\right) e^{k_n^2 - 100(y-y_n)^2}$$

Now we don't need to go any further: we just look at the direction of the overall acceleration:

$$\text{output} = \begin{cases}\text{north} &\text{if }\ddot{y}<0 \text{ and } |\ddot{y}|>|\ddot{x}| \\ & \\ \text{south} &\text{if }\ddot{y}>0 \text{ and } |\ddot{y}|>|\ddot{x}| \\ & \\ \text{west} &\text{if }\ddot{x}<0 \text{ and } |\ddot{x}|>|\ddot{y}| \\ & \\ \text{east} &\text{if }\ddot{x}>0 \text{ and } |\ddot{x}|>|\ddot{y}| \\ & \\ \text{none} &\text{if }|\ddot{y}|=|\ddot{x}|\end{cases}$$

And just like that, we've unified the coins and the bots. Where's my Nobel Prize?

\$\endgroup\$
9
  • 6
    \$\begingroup\$ Your Nobel Prize was lost in the mail, but we could give you an Emmy Award instead \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 12:47
  • 1
    \$\begingroup\$ Looks like physics starts getting popular in this challenge. :—D. And of course I'm very curious about how well it will do. \$\endgroup\$
    – Ramillies
    Commented Aug 9, 2018 at 13:27
  • 1
    \$\begingroup\$ (By the way, you could have saved the hassle with Euler-Lagrange equations, because they reduce to the well-known fact that F = c_me a = - grad U :—).) \$\endgroup\$
    – Ramillies
    Commented Aug 9, 2018 at 13:34
  • \$\begingroup\$ @Ramillies Meh, it was more fun doing it this way :D \$\endgroup\$
    – Beta Decay
    Commented Aug 9, 2018 at 13:48
  • 1
    \$\begingroup\$ Are you sure that you want to be using the k = coins of something else / your coins? You start with no coins... and with NaN everywhere, you're not too likely to get any. \$\endgroup\$
    – Ramillies
    Commented Aug 10, 2018 at 14:38
9
\$\begingroup\$

Goldilocks, JavaScript (Node.js)

function goldilocks(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
}

Try it online!

Just locks on to the gold coin's location and moves towards it every time. (Thanks to @Mayube's 'B33-L1N3' bot for the original code this used, though barely any of it remains.)

\$\endgroup\$
2
  • \$\begingroup\$ This is a pretty nice, simple bot. I like it. \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:09
  • 2
    \$\begingroup\$ By the way, I'm using this bot as a test for my controller (: \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 19:44
9
\$\begingroup\$

The AntiCapitalist | Javascript

Has no incentive to go after coins, but tries to place himself exactly between the two richest bots with the same amount of money, in the hope they will hunt him and eventually catch him at the same time, taking two capitalists with him when he dies. Does not actively resist getting coins, so he might become a more juicy target.

function antiCapitalist(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();
}
\$\endgroup\$
8
\$\begingroup\$

Third Gen Learning Algorithm | JavaScript (Node.js)

function run(me) {
	options = [];
	if (me.locationX > 0) options.push('west');
	if (me.locationY > 0) options.push('north');
	if (me.locationX < me.arenaLength) options.push('east');
	if (me.locationY < me.arenaLength) options.push('south');

	return options[Math.floor(Math.random() * options.length)];
}

Try it online!

After a few generations of learning, this bot has learned that leaving the arena = bad

\$\endgroup\$
5
  • \$\begingroup\$ Oh, good. I've heard this is called "Natural Selection" \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:16
  • 5
    \$\begingroup\$ Where is the second gen \$\endgroup\$ Commented Aug 8, 2018 at 17:15
  • 11
    \$\begingroup\$ @LuisfelipeDejesusMunoz He left the arena. \$\endgroup\$
    – Jo.
    Commented Aug 9, 2018 at 0:13
  • \$\begingroup\$ This is a good bot for debugging the controller \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 13:50
  • 3
    \$\begingroup\$ Oh, by the way, the arena starts at 0, so it should be arenaLength - 1. This killed your bot a fair few times to many \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 13:52
6
\$\begingroup\$

B33-L1N3 | JavaScript (Node.js)

function(me, others, coins) {
	// Do nothing if there aren't any coins
	if (coins.length == 0) return 'none';
	// Sort by distance using Pythagoras' Theorem
	coins = coins.sort((a, b) => (a[0] ** 2 + a[1] ** 2) - (b[0] ** 2 + b[1] ** 2));
	// Closest coin
	let target = coins[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');
}

Try it online!

Makes a beeline for the closest coin

\$\endgroup\$
3
  • \$\begingroup\$ Oh, I thought B33-L1N3 was some sort of model number \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:16
  • \$\begingroup\$ let coins = ... Uncaught SyntaxError: Identifier 'coins' has already been declared \$\endgroup\$
    – Night2
    Commented Aug 10, 2018 at 17:20
  • \$\begingroup\$ Delete the let \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 18:24
5
\$\begingroup\$

Livin' on the Edge, JavaScript

function LivinOnTheEdge (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';
  }
}

This one heard the edge of the arena is a dangerous place to be. Knowing no fear, it tirelessly circles the board clockwise, only inches away from the certain death that awaits behind the border, hoping no other bot will dare to move so closely to the edge's vicinity.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ This wouldn't end well if another bot were created that had 1 more coin, and patrolled the border in the opposite direction (: \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 23:07
  • 8
    \$\begingroup\$ I would make a border control joke, but I'm leaving that one up to @BetaDecay \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 0:53
5
\$\begingroup\$

Damacy, JavaScript (Node.js)

function damacy(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
}

Try it online!

One last katamari-based bot for today, this time with a little bit of memory. Thanks to @BetaDecay for the name suggestion - definitely a more fun name than my simplePredictorKatamari.

Tries to figure out how bots have moved in the last turn, and based on that, predicts where they'll try to move at the end of this turn (assuming they continue to move in the same direction).

(Thanks to @fəˈnɛtɪk, for noticing that I was calling the wrong function name in botNotes, and to @OMᗺ for noticing a bug in the base code.)

\$\endgroup\$
4
  • \$\begingroup\$ This is probably the only one right now that can catch others outside of just being lucky. \$\endgroup\$
    – Cain
    Commented Aug 8, 2018 at 22:56
  • \$\begingroup\$ Isn't botnotes supposed to be "storedata" not setdata? \$\endgroup\$ Commented Aug 9, 2018 at 1:26
  • \$\begingroup\$ @fəˈnɛtɪk See, already needs a bugfix! :) Thanks, corrected now. \$\endgroup\$
    – Sundar R
    Commented Aug 9, 2018 at 7:59
  • \$\begingroup\$ You should replace aWt = 1 in the params with aWt, and put aWt = aWt || 1 underneath (Same with bWt). This prevents errors. \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 14:57
4
\$\begingroup\$

Not so Blindly | JavaScript (Node.js)

Important Note: This approach is not entirely mine and has been answered in a similar question. Make sure to vote that answer as well.

Haev you ever heard about A* pathfinding algorithm? here it is. It creates the best path from one point to the less valuable coin (as everyone is going for the most valuable, no one is going for the less) and tries to not collide with any other user.

Expects parameters as follow:

AI({locationX: 3, locationY: 1, arenaLength: [5,5]}, [[2,1],[2,2], ...],[[1,2],[3,1], ...])

Maybe I do one that goes hunting other bots


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[0] ||
                next[1] < 0 || next[1] >= me.arenaLength[1]) {
                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";
    }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Wow...a pathfinding algorithm already? It's only been 3 hours! \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 17:55
  • \$\begingroup\$ My algorithms go for whichever coins are safest to go to. \$\endgroup\$ Commented Aug 8, 2018 at 17:59
4
\$\begingroup\$

Coward | Python 2

import random

def move(me, others, coins):
    target = (me.locationX, me.locationY)

    # Identify the dangerous opponents.
    threats = [i for i, value in enumerate(others[2]) if value >= me.coins]

    # If no one scary is nearby, find a nearby coin.
    safe = True
    for x, y in self.coins:
        distance = abs(me.locationX - x) + abs(me.locationY - y)
        safe = True
        for i in threats:
            if abs(others[0][i] - x) + abs(others[1][i] - y) <= distance:
                safe = False
                break

        if safe:
            target = (x, y)
            break

    # Otherwise, just try not to die.
    if not safe:
        certain = []
        possible = []
        for x, y in [
            (me.locationX, me.locationY),
            (me.locationX + 1, me.locationY),
            (me.locationX - 1, me.locationY),
            (me.locationX, me.locationY + 1),
            (me.locationX, me.locationY - 1),
        ]:
            # Don't jump off the board.
            if x < 0 or y < 0 or x == me.arenaLength or y == me.arenaLength:
                continue

            # Check if we can get away safely.
            for i in threats:
                if abs(others[0][i] - x) + abs(others[1][i] - y) <= 1:
                    break
            else:
                certain.append((x, y))

            # Check if we can take a spot someone is leaving.
            for i in threats:
                if others[0][i] = x and others[1][i] == y:
                    for i in threats:
                        if abs(others[0][i] - x) + abs(others[1][i] - y) == 1:
                            break
                    else:
                        possible.append((x, y))

        if certain:
            target = random.choice(certain)
        elif possible:
            target = random.choice(possible)
        # Otherwise, we're doomed, so stay still and pray.

    directions = []
    x, y = target
    if x < me.locationX:
        directions.append('west')
    if x > me.locationX:
        directions.append('east')
    if y < me.locationY:
        directions.append('north')
    if y > me.locationY:
        directions.append('south')
    if not directions:
        directions.append('none')

    return random.choice(directions)

Avoid bots with more money if at all possible. Otherwise, grab money that's lying around.

\$\endgroup\$
1
  • \$\begingroup\$ This is the most basic bot that has a chance at winning \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 0:51
4
\$\begingroup\$

Wild Goose Chase Bot, Javascript

A bot that's really good at dodging other bots, but very bad at getting coins.


Algorithm:

  1. If there are no adjacent bots, return none
  2. Otherwise:
    1. Return none with a random chance 1/500 chance (this is intended to prevent stalemates).
    2. Determine which spaces are safe to move to (ie inside the arena and not occupied by another bot)
    3. Return one at random

Code:

function wildGooseChase(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)];
}

Try it Online!

Note to Redwolf Programs:

This bot has the potential to cause very long rounds. I have taken some liberties to prevent stalemates, but have not tested if they are actually effective. If this bot becomes a problem during testing, please feel free to disqualify it.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the note. An expert evader has an okay chance if it happens to collect enough coins while being chased \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 0:50
  • \$\begingroup\$ I like this. It's almost like bait for the hunting robots \$\endgroup\$
    – Beta Decay
    Commented Aug 12, 2018 at 10:58
4
\$\begingroup\$

KatamariWithValues, JavaScript (Node.js),

function katamariWithValues(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
}

Try it online!

(Thanks to @OMᗺ for pointing out a bug in the original code this was based on.)

Tries to grow by "eating" bots with less coins than itself. If that's not possible (no such bot exists), then seeks out the nearest coin.

This version has small tweaks to (a) give higher preference to gold coins than to silver coins - with the risk that seeking a more distant gold coin may end up costing the bot's life or lead to chasing after fool's gold (b) skip bots with 0 coins - no need to waste time chasing after those.

\$\endgroup\$
4
  • \$\begingroup\$ A smart hunter...well, that's even better! \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 19:35
  • \$\begingroup\$ @RedwolfPrograms Let's hope so! :) \$\endgroup\$
    – Sundar R
    Commented Aug 8, 2018 at 19:41
  • \$\begingroup\$ Should've called this Damacy ;) \$\endgroup\$
    – Beta Decay
    Commented Aug 8, 2018 at 21:31
  • 1
    \$\begingroup\$ @BetaDecay Wish granted. \$\endgroup\$
    – Sundar R
    Commented Aug 8, 2018 at 21:55
4
\$\begingroup\$

Polite Near-Sighted Drunk Bot | JavaScript

function politeNearSightedDrunkBot(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
}

Staggers around picking up nearby coins, but randomly switches directions every so often. Does what he can to avoid bumping into anyone, but he gets... belligerent... when aggravated. Tends to sober up as the competition goes on.

May need some debugging, when the controller is fully done I'll work on it.

\$\endgroup\$
1
  • 4
    \$\begingroup\$ Hmm, it raises the bar, then gets drunk at it \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 21:51
4
\$\begingroup\$

Weighted Motion | JavaScript

WeightedMotion=(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";
}

Moves in the direction it has assigned the highest value while avoiding running off the edge of the board.

Value is calculated as such:

  • Coin = power of coin / distance to coin
  • Bot = Difference in power of bots / 2 * distance to bot
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Well, this looks like a pretty awesome bot. Be sure to check the directions, since it would be a real loss if your bot was a master at running away from coins (: \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 17:17
  • \$\begingroup\$ Well, still. I've got to be nice, right? \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 17:32
  • \$\begingroup\$ Well, post it! It'll make up for the smaller, faster bots that also exist at the moment in large quantities. \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 17:37
  • \$\begingroup\$ for(i=0;i<6;i++){ there are only 5 coins in total, 1 gold and 4 silver. Your loop is running 6 times from 0 to 5. \$\endgroup\$
    – Night2
    Commented Aug 10, 2018 at 17:22
4
\$\begingroup\$

Proton | JavaScript

Proton=(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;
}

All coins (including those held by other bots) emit a repulsive force towards Protonbot. Based on this force, it builds up velocity and bounces off of walls (turns around immediately upon hitting a boundary). If it ends up next to a bot or coin it can consume, the Strong nuclear force takes over and it moves to consume it, dropping all velocity when it does so.

\$\endgroup\$
2
  • \$\begingroup\$ Hmm, nuclear physics applied to treasure hunting? This beats Science Channel any day! \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 at 2:41
  • \$\begingroup\$ You need to replace sin with Math.sin, cos with Math.cos, and so on \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 15:23
3
\$\begingroup\$

Blindy | JavaScript (Node.js)

This definitely wont win but at least participate. First try at KoH challenge. It sorts the coins and goes to the closest one. He doesn't look for players so he doesn't care if he collide with other.

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
}
\$\endgroup\$
1
  • \$\begingroup\$ Hmm, it might work since other bots will be seeking the gold primarily, potentially leaving you to get the silver without any fighting \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:13
3
\$\begingroup\$

Feudal Noble | JavaScript

Preferred color: #268299

function (noble, peasants, coins) {
    var center = (noble.arenaLength - 1) / 2, centerSize = noble.arenaLength / 4, peasantsCount = peasants.length,
        centerMin = center - centerSize, centerMax = center + centerSize, apocalypse = 2e4 - ((noble.arenaLength * 2) + 20), inDanger = false;

    var round = botNotes.getData('round');
    if (round === null || !round) round = 0;
    round++;
    botNotes.storeData('round', round);

    var getDistance = function (x1, y1, x2, y2) {
        return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
    };

    var isAtCenter = function (x, y) {
        return (x > centerMin && x < centerMax && y > centerMin && y < centerMax);
    };

    var getScore = function (x, y) {
        var score = 0, i, centerFactor = 10;

        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);
                inDanger = true;
            }
        }

        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);
            }
        }

        if (round >= apocalypse) centerFactor = 1000;
        score -= getDistance(x, y, center, center) * centerFactor;

        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;
        }
    }

    if (round >= apocalypse) {
        var dg = botNotes.getData('dg');
        if (dg === null || !dg) dg = [];
        if (dg.length >= 20) dg.shift();
        dg.push(inDanger);
        botNotes.storeData('dg', dg);
        if (dg.length >= 20) {
            var itsTime = true;
            for (i = 0; i < dg.length; i++) if (!dg[i]) itsTime = false;
            if (itsTime) return 'none';
        }
    }

    return topCommand;
}

This feudal noble stays at the center of the field and claims it as his own palace. Collects anything at the center for himself, but anything in farms far away should be brought to him by peasants. Of course if an angry powerful peasant appears at the palace, noble might run away to save his life, but he comes back as soon as it is safe!

As the time goes on, peasants get stronger and stronger. Professional fighters and powerful heros start to rise from the peasantry. Power of the noble keeps decaying. He tries to keep his wealth and his Feudalism system together as long as he can. But finally a time comes that he should accept his faith, he should accept that people don't want Feudalism anymore. That is the day that feudal noble gives up everything, doesn't run from powerful peasants anymore and gets killed by one of them.

\$\endgroup\$
2
\$\begingroup\$

Quantum Gnat Bot | JavaScript

function quantumGnatBot(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
}

This annoying bot tries to buzz around the strongest bot without getting swatted and has a slight chance of phasing through those who try to hunt it. It has a tendency to draw the two most powerful bots into close proximity... ;)

\$\endgroup\$
1
  • \$\begingroup\$ If he can't find a suitable target in getTargetDirections(), then interesting things start to happen. (Such as breaking down everything due to undefined has no property 0 error.) \$\endgroup\$
    – Ramillies
    Commented Aug 11, 2018 at 10:40
2
\$\begingroup\$

Retired ICE Agent, JavaScript

Preferred colour: indianred

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
        // Find the weakest bot
        var weakling = [];
        var weakling_money = 1000000;

        for (var k = 0; k < others.length; k++) {
            if (others[k][2] < weakling_money) {
                weakling_money = others[k][2];
                weakling = others[k];
            }
        }

        // Move to the weakest bot
        if (me.locationY != weakling[1]) {
            if (me.locationY - weakling[1] > 0) {
                return "north";
            } else {
                return "south";
            }
        } else {
            if (me.locationX - weakling[0] > 0) {
                return "west";
            } else {
                return "east";
            }
        }
    }
}

Now retired, this ICE agent is bitter about humanity. As a result, Retired ICE now targets the weakest bot, while keeping its coin value above the average (as per ICE policy).

\$\endgroup\$
2
\$\begingroup\$

Greedy-Pursuit | Haskell

Preferred colour: #62bda4

import Data.List

f x y c _ bs _
  | [bx,by,_]:_ <- sortByDist x y $ filter ((c>).last) bs = toDir (bx-x,by-y)
f x y _ _ _ cs
  | [cx,cy,_]:_ <- sortByDist x y cs = toDir (cx-x,cy-y)
f _ _ _ _ _ _ = "none"


sortByDist x y = sortOn (\[bx,by,_]-> abs (bx-x) + abs (by-y))

toDir (dx,dy)
  | dx > 0 = "east"
  | dx < 0 = "west"
  | dy > 0 = "south"
  | dy < 0 = "north"
  | otherwise = "none"

Try it online!*

Pretty simple strategy, picks the first decision from:

  • if there are bots with less coins: pick closest and move towards it
  • if there are coins: pick closest and move towards it
  • default: stay

The bot only tries to catch other bots or coins without caring about potentially stronger bots that might try to catch it.

* I don't really know JavaScript but I did the thing with google (might be inaccurate): Try it online!

\$\endgroup\$
5
  • 6
    \$\begingroup\$ I wonder how he is going to translate haskell into js \$\endgroup\$ Commented Aug 8, 2018 at 15:16
  • 3
    \$\begingroup\$ @LuisfelipeDejesusMunoz: Yeah me too. But luckily, it's not very sophisticated code. \$\endgroup\$ Commented Aug 8, 2018 at 15:19
  • \$\begingroup\$ @LuisfelipeDejesusMunoz Just use Node.JS and process.open (or child_process.spawn, or similar) with some parsing. \$\endgroup\$
    – user202729
    Commented Aug 8, 2018 at 15:20
  • \$\begingroup\$ @LuisfelipeDejesusMunoz: I tried to translate it and added a link, but I don't feel very confident writing JavaScript, so it might be buggy. \$\endgroup\$ Commented Aug 8, 2018 at 15:48
  • 4
    \$\begingroup\$ @LuisfelipeDejesusMunoz It would be one thing if this was a 10,000 line AI learning program, but I think I can manage this (: \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:14
1
\$\begingroup\$

Coin Magnet | JavaScript

CoinMagnet=(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";
}

This bot is rather dumb, it heads in the direction of the most acquirable coins. This includes coins that it can not get because other bots have higher power than itself.

\$\endgroup\$
1
\$\begingroup\$

ICE Agent | Javascript

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";
            }
        }
    }
}

What's the point of a border if isn't being patrolled? ICE moves anticlockwise around the border, picking up any bots which stray into its path.

Before it can do that, it needs to be able to eat other bots first. For that reason ICE, keeps its coins above the average of all of the bots.

Guaranteed to steal kids from their parents™

\$\endgroup\$
1
  • \$\begingroup\$ This would be more funny if it weren't so relevant \$\endgroup\$ Commented Aug 13, 2018 at 2:05
1
\$\begingroup\$

X Marks The Spot | JavaScript

function(me, others, coins){
    if (me.locationY != 0) {
        // If not on X axis
        if (others.every(other => other[1]==me.locationY-1)) {
            // If any in my way
            if (!others.every(other => other[0]==me.locationX-1)) {
                if (me.locationX != 0) {
                    // If no one to my left and not on edge of board
                    return "west"
                } else {
                    return "none"
                }
            } else if (!others.some(other => other[0]==me.locationX+1)) {
                if (me.locationX != me.arenaLength-1) {
                    // If no one to my right and not on edge of board
                    return "east"
                } else {
                    return "none"
                }
            } else {
                // I'm surrounded
                return "none"
            }
        } else {
            // No one in my way
            return "north"
        }
    } else {
        // If on the x axis
        if (!others.some(other => Math.abs(other[0]-me.locationX)==1 && other[1] == me.locationY)) {
            // If no one next to me
            move = ["east","west"][Math.floor(Math.random()*2)]

            // Prevent from falling off the board
            if (move == "east" && me.locationX == me.arenaLength-1) {
                return "west"
            } else if (move == "west" && me.locationX == 0) {
                return "east"
            } else {
                return move
            }
        } else {
            // I'm surrounded
            return "none"
        }
    }
}

X marks the spot, so all the gold must be on the x axis, right? My bot makes a beeline for the y=0 line then stays there, moving about randomly.

\$\endgroup\$
4
  • \$\begingroup\$ Huh, an interesting method indeed \$\endgroup\$
    – rydwolf
    Commented Aug 8, 2018 at 16:15
  • 1
    \$\begingroup\$ Python (2, unfortunately) interpreter in JS (...) \$\endgroup\$
    – user202729
    Commented Aug 8, 2018 at 16:21
  • 1
    \$\begingroup\$ Given that The arena starts at (0,0) in the upper left corner, are you sure you want to move south to get to y=0? \$\endgroup\$ Commented Aug 8, 2018 at 16:45
  • \$\begingroup\$ @AdmBorkBork Thanks, that could've been bad \$\endgroup\$
    – Beta Decay
    Commented Aug 8, 2018 at 16:50
1
\$\begingroup\$

Firebird

    function(me,others,coins) {
        var x = me.locationX;
        var y = me.locationY;
        var safe = [true, true, true, true];
        var threats = [];
        var targets = [];
        var opps = [];
        
        var meTo = (loc) => (Math.abs(x - loc[0]) + Math.abs(y - loc[1]));
        var inSquare = (loc, r) => (Math.abs(loc[0] - x) <= r && Math.abs(loc[1] - y) <= r);
        var distance = (from, loc) => (Math.abs(from[0] - loc[0]) + Math.abs(from[1] - loc[1]));
        var attackRange = (from, check, r) => {
            for (var i = 0; i < check.length; i++) {
                if (distance(check[i], from) == (r || 1)) {
                    return true;
                }
            }
            return false;
        };
        var dirStr = (dir) => (['north','east','south','west'][dir]);
        
        var i, n, o, p;
        for (i = 0; i < others.length; i++) {
            o = others[i];
            if (o[2] >= me.coins) {
                threats.push(o);
            } else {
                targets.push([o[0], o[1], Math.floor(o[2] * 0.55)]);
            }
        }
        for (i = 1; i < 5; i++) {
            targets.push([coins[i][0], coins[i][1], 2]);
        }
        targets.push([coins[0][0], coins[0][1], 5]);
        if (y === 0 || attackRange([x, y - 1], threats)) {
            safe[0] = false;
        }
        if (x == me.arenaLength - 1 || attackRange([x + 1, y], threats)) {
            safe[1] = false;
        }
        if (y == me.arenaLength - 1 || attackRange([x, y + 1], threats)) {
            safe[2] = false;
        }
        if (x === 0 || attackRange([x - 1, y], threats)) {
            safe[3] = false;
        }
        if (safe.includes(false)) {
            if (!(safe[0]) && safe[2]) {
               opps.push(2);
            }
            if (!(safe[1]) && safe[3]) {
                opps.push(3);
            }
            if (!(safe[2]) && safe[0]) {
                opps.push(0);
            }
            if (!(safe[3]) && safe[1]) {
                opps.push(1);
            }
        } else {
            targets.sort((a,b)=>(meTo(a) - meTo(b)));
            o = targets[0];
            if (o[0] == x) {
                if (o[1] < y) {
                    return 'north';
                } else {
                    return 'south';
                }
            } else if (o[1] == y) {
                if (o[0] < x) {
                    return 'west';
                } else {
                    return 'east';
                }
            } else if (Math.abs(o[0] - x) < Math.abs(o[1] - y)) {
                if (o[1] < y) {
                    return 'north';
                } else {
                    return 'south';
                }
            } else if (Math.abs(o[0] - x) > Math.abs(o[1] - y)) {
                if (o[0] < x) {
                    return 'west';
                } else {
                    return 'east';
                }
            }
        }
        console.log(safe[opps[0]]);
        var lx, ly;
        for (i = 0; i < opps.length; i++) {
            if (opps[i] === 0) {
                lx = x;
                ly = y - 1;
            }
            if (opps[i] == 1) {
                lx = x + 1;
                ly = y;
            }
            if (opps[i] == 2) {
                lx = x;
                ly = y + 1;
            }
            if (opps[i] == 3) {
                lx = x - 1;
                ly = y;
            }
            if (attackRange([lx, ly], targets, 0)) {
                return dirStr(opps[i]);
            }
        }
        return dirStr(opps[0]);
    }

Completely revamped to be more deadly than before (:

\$\endgroup\$
3
  • 2
    \$\begingroup\$ My absolute loser of a bot \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 22:09
  • \$\begingroup\$ It's not targeting them, it's avoiding them \$\endgroup\$
    – rydwolf
    Commented Aug 10, 2018 at 22:24
  • \$\begingroup\$ Oh sorry, I misunderstood \$\endgroup\$
    – Beta Decay
    Commented Aug 10, 2018 at 22:35
1
\$\begingroup\$

A-Path-y | JavaScript

Preferred color for this bot is #0077b3.

 run: function (me, others, coins)
{
    var X_INDEX = 0;
    var Y_INDEX = 1;
    var COIN_INDEX = 2;

    var GOLD_POINTS = 5;
    var SILVER_POINTS = 2;

    var NORTH = 0;
    var SOUTH = 1;
    var WEST = 2;
    var EAST = 3;
    var IDLE = 4;
    var MOVE_COMMANDS_COUNT = IDLE+1;

    var MAP_TYPE_BLANK = 0;
    var MAP_TYPE_BOT = 1;
    var MAP_TYPE_GOLD_COIN = 2;
    var MAP_TYPE_SILVER_COIN = 3;

    var MIDGAME_THRESHOLD = 25;

    var PATH_FINDING_MAX_STEPS = 10000;
    var offsets = [[0,-1],[1,0],[0,1],[-1,0]];

function randInt(min,max)
    {
        return  Math.floor(Math.random() * ((max - min) + 1)) + min;
    }


    /**
     * Find a path using a*, returns the direction to take from the starting position coupled with a metric describing the cost of the path
     */
function pathFind(startX,startY,targetX,targetY,map,mapSize)
    {
        var i;
        var j;

        // shuffleIndecies to make path selection slightly random
        var indecies = [0,1,2,3];
        var shuffleIndecies = new Array(4);
        for (j=0;j<4;j++)
        {
            var randomIndex = randInt(0,3-j);
            shuffleIndecies[j] = indecies[randomIndex];
            indecies[randomIndex] = indecies[0];
            var lastElementIndex = 4-j-1;
            indecies[0] = indecies[lastElementIndex];
        }

        // A*
        if (!(startX===targetX && startY===targetY))
        {

            var tileX = new Array(PATH_FINDING_MAX_STEPS);
            var tileY = new Array(PATH_FINDING_MAX_STEPS);
             var fscore = new Array(PATH_FINDING_MAX_STEPS);
             var gscore = new Array(PATH_FINDING_MAX_STEPS);
             var openList = new Array(PATH_FINDING_MAX_STEPS);
             var tileParent = new Array(PATH_FINDING_MAX_STEPS);
             var tileIsClosed = new Array(mapSize);

             for (i = 0;i<PATH_FINDING_MAX_STEPS;i++)
             {
                 tileX[i]=0;
                 tileY[i]=0;
                 fscore[i]=0;
                 gscore[i]=0;
                 openList[i]=0;
                 tileParent[i]=0;
             }


             for (i = 0;i<mapSize;i++)
             {
                 var newArray = new Array(mapSize);
                 tileIsClosed[i] = newArray;
                 for (j = 0;j<mapSize;j++)
                 {
                     tileIsClosed[i][j] = 0;
                 }
             }

             var currentIndex = -1;     

            var openListSize=1;
            var tileId=1;

            tileX[0]=targetX;
            tileY[0]=targetY;
            fscore[0]=1;
            gscore[0]=map[targetX][targetY].negativeWeight;



            do
            {
              var currentBestIndex=-1;
              var currentBestScore=2147483647;
              //  Look for the lowest F cost square on the open list
              for (var ii=0;ii<openListSize;ii++)
              {
                if (fscore[openList[ii]]<currentBestScore)
                {
                  currentBestScore=fscore[openList[ii]];
                  currentBestIndex=ii;
                }
              }
              if (currentBestIndex===-1)
              {
                break;
              }
              currentIndex=openList[currentBestIndex];
              var currentTileX=tileX[currentIndex];
              var currentTileY=tileY[currentIndex];

              // found path
              if (startX===currentTileX && startY===currentTileY)
              {
                break;
              }

              // if not in closed list
              if (tileIsClosed[currentTileX][currentTileY]===0)
              {
                    // Switch it to the closed list.
                    tileIsClosed[currentTileX][currentTileY]=1;
                    // remove from openlist
                    openList[currentBestIndex]=openList[--openListSize];   

                    // add neighbours to the open list if necessary
                    for (j=0;j<4;j++)
                    {
                        i = shuffleIndecies[j];

                        var surroundingCurrentTileX=currentTileX+offsets[i][0];
                        var surroundingCurrentTileY=currentTileY+offsets[i][1];
                        if (surroundingCurrentTileX>=0 && surroundingCurrentTileX<mapSize &&
                            surroundingCurrentTileY>=0 && surroundingCurrentTileY<mapSize )
                        {
                          tileX[tileId]=surroundingCurrentTileX;
                          tileY[tileId]=surroundingCurrentTileY;

                          var surroundingCurrentGscore=gscore[currentIndex] + map[surroundingCurrentTileX][surroundingCurrentTileY].negativeWeight;
                          gscore[tileId]=surroundingCurrentGscore;
                          fscore[tileId]=surroundingCurrentGscore+Math.abs( surroundingCurrentTileX-startX)+Math.abs( surroundingCurrentTileY-startY);
                          tileParent[tileId]=currentIndex;
                          openList[openListSize++]=tileId++;
                        }
                    }
              }
              else
              {
              // remove from openlist
              openList[currentBestIndex]=openList[--openListSize];    
              }
            } while(true);

            if (tileX[tileParent[currentIndex]]<startX) return {moveDirection:WEST, pathLength:currentIndex, pathScore:gscore[currentIndex]+currentIndex/4};
            else if (tileX[tileParent[currentIndex]]>startX) return {moveDirection:EAST, pathLength:currentIndex, pathScore:gscore[currentIndex]+currentIndex/4};
            else if (tileY[tileParent[currentIndex]]<startY) return {moveDirection:NORTH, pathLength:currentIndex, pathScore:gscore[currentIndex]+currentIndex/4};
            else if (tileY[tileParent[currentIndex]]>startY) return {moveDirection:SOUTH, pathLength:currentIndex, pathScore:gscore[currentIndex]+currentIndex/4};
        }
        console.log("Path finding failed");
        return {moveDirection:IDLE, pathLength:0, pathScore:2147483647};
     }

function process(info,bots,coins)
    {
        var i;
        var j;
        var k;
        var x;
        var y;

        // initialise map
        var mapSize = info.arenaLength;
        var map = new Array(mapSize);
        for (i = 0;i < info.arenaLength;i++)
        {
            var newArray = new Array(info.arenaLength);
            map[i] =  newArray;
            for (j = 0;j < mapSize;j++)
            {
                map[i][j] = {type:MAP_TYPE_BLANK, coins: 0 , negativeWeight:i===0||i===mapSize-1||j===0||j===mapSize-1?3:1};
            }
        }

        // populate map with bots
        for (i = 0 ; i<bots.length;i++)
        {
            map[bots[i][X_INDEX]][bots[i][Y_INDEX]].type = MAP_TYPE_BOT;
            map[bots[i][X_INDEX]][bots[i][Y_INDEX]].coins = bots[i][COIN_INDEX];

            for (j=-1;j<2;j++)
            {
                x = bots[i][X_INDEX] + j;
                if (x>=0 && x < mapSize)
                {
                    for(k=-1;k<2;k++)
                    {
                        if (Math.abs((k+j)%2) === 1)
                        {
                            y = bots[i][Y_INDEX] + k;
                            if (y>=0 && y< mapSize )
                            {
                                // are we adjacent the bot or potentially will be?
                                if (Math.abs(info.locationX-x)<=1 && Math.abs(info.locationY-y)<=1)
                                {
                                    // make the cell significantly less attractive when the bot is stronger than us, or
                                    // make the cell slightly more attactive when the bot is weaker than us, or
                                    // not change if the bot has no coins
                                    map[x][y].negativeWeight+= bots[i][COIN_INDEX] >= info.coins?100000:(bots[i][COIN_INDEX]===0?0:-1);
                                }
                                // another bot is not a direct threat/target
                                else
                                {
                                    // make the cell moderately less attractive when the bot is stronger than us, or
                                    // make the cell slightly more attactive when the bot is weaker than us, or
                                    // not change if the bot has no coins
                                    map[x][y].negativeWeight+= bots[i][COIN_INDEX] >= info.coins?3:(bots[i][COIN_INDEX]===0?0:-1);
                                }
                            }
                        }
                    }
                }
            }
        }

        // populate map with coins
        for (i = 0 ; i<coins.length;i++)
        {
            map[coins[i][X_INDEX]][coins[i][Y_INDEX]].type = i === 0?MAP_TYPE_GOLD_COIN:MAP_TYPE_SILVER_COIN;
            map[coins[i][X_INDEX]][coins[i][Y_INDEX]].coins = i === 0?GOLD_POINTS:SILVER_POINTS;

            // check to see whether bots are adjacent to the coin
            for (j=-1;j<2;j++)
            {
                x = coins[i][X_INDEX] + j;
                if (x>=0 && x < mapSize)
                {
                    for(k=-1;k<2;k++)
                    {
                        if ((k+j)%2 === 1)
                        {
                            y = coins[i][Y_INDEX] + k;
                            if (y>=0 && y< mapSize )
                            {
                                if (map[x][y].type === MAP_TYPE_BOT)
                                {
                                    // this coin looks like a trap as a stronger bot is adjacent to it
                                    if (map[x][y].coins >= info.coins)
                                    {
                                        map[coins[i][X_INDEX]][coins[i][Y_INDEX]].negativeWeight+=100000;
                                    }
                                    else
                                    {
                                        // are we adjacent the coin? we might be able to kill another bot if it trys to get the coin
                                        if (Math.abs(info.locationX-coins[i][X_INDEX])<=1 && Math.abs(info.locationY-coins[i][Y_INDEX])<=1)
                                        {
                                            map[coins[i][X_INDEX]][coins[i][Y_INDEX]].negativeWeight+=-20;
                                        }
                                        // another bot is likely to get this coin... make it less attractive
                                        else
                                        {
                                            map[coins[i][X_INDEX]][coins[i][Y_INDEX]].negativeWeight=+100;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // add the coin attractiveness, more for gold coins
            map[coins[i][X_INDEX]][coins[i][Y_INDEX]].negativeWeight += i === 0?-20:-10;
        }


        var pathBest = {moveDirection:IDLE, pathLength: 2147483647, pathScore: 2147483647};

        if (info.coins > MIDGAME_THRESHOLD)
        {
            var viableCoinCount =0;
            var viableCoins = new Array(5); 


            // find coins that are reachable before any other bot
            outer1:
            for (j = 0 ; j<coins.length;j++)
            {
                var contention = 0;

                var myDistanceToCoin = Math.abs(info.locationX-coins[j][X_INDEX]) + Math.abs(info.locationY-coins[j][Y_INDEX]);

                for (i = 0 ; i<bots.length;i++)
                {
                    var dist = Math.abs(bots[i][X_INDEX]-coins[j][X_INDEX]) + Math.abs(bots[i][Y_INDEX]-coins[j][Y_INDEX]);
                    if (dist < myDistanceToCoin)
                    {
                        continue outer1;
                    }
                }
                viableCoins[viableCoinCount++] = j;
            }

            // no coins are reachable before another bot so find the cell that is furthest away from any bot and head there
            if (viableCoinCount ===0)
            {
                var mostIsolatedCellX = mapSize/2;
                var mostIsolatedCellY = mapSize/2;
                var mostIsolatedCellMinBotDistance = 0;

                for (x=5;x<mapSize-5;x++)
                {
                    for (y=5;y<mapSize-5;y++)
                    {
                        if (x!= info.locationX && y!=info.locationY)
                        {

                            // ignore coin attractiveness
                            map[x][y].negativeWeight = map[x][y].negativeWeight<-4?map[x][y].negativeWeight:1;


                            var currentCellMinBotDistance = 2147483647;

                            for (i = 0 ; i<bots.length;i++)
                            {
                                var dist = Math.abs(bots[i][X_INDEX]-x) + Math.abs(bots[i][Y_INDEX]-y) + Math.abs(info.locationX-x) + Math.abs(info.locationY-y);
                                if (dist < currentCellMinBotDistance )
                                {
                                    {
                                        currentCellMinBotDistance = dist;                           
                                        if (currentCellMinBotDistance>mostIsolatedCellMinBotDistance)
                                        {
                                            mostIsolatedCellMinBotDistance = currentCellMinBotDistance;
                                            mostIsolatedCellX=x;
                                            mostIsolatedCellY=y;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                // attempt to find path to most isolated cell
                pathBest = pathFind(info.locationX, info.locationY, mostIsolatedCellX,mostIsolatedCellY, map, mapSize);
            }

            // attempt to find paths to each viable coin, keeping the best result
            for (i = 0 ; i<viableCoinCount;i++)
            {
                var path = pathFind(info.locationX, info.locationY, coins[viableCoins[i]][X_INDEX],coins[viableCoins[i]][Y_INDEX], map, mapSize);
                if (path.pathScore < pathBest.pathScore)
                {
                    pathBest = path;
                }
            }
        }
        else
        {
            // attempt to find paths to each coin, keeping the best result
            for (i = 0 ; i<coins.length;i++)
            {
                var path = pathFind(info.locationX, info.locationY, coins[i][X_INDEX],coins[i][Y_INDEX], map, mapSize);
                if (path.pathScore < pathBest.pathScore)
                {
                    pathBest = path;
                }
            }
        }


        var move = IDLE;
        if (pathBest.pathLength === 2147483647)
        {
            outer:
            for (i=0;i<MOVE_COMMANDS_COUNT;i++)
            {
                switch (i)
                {
                    case NORTH:
                        if (info.locationY-1 < 0)
                        {
                            continue;
                        }
                        move = i;
                        break outer;
                    case SOUTH:
                        if (info.locationY+1 === info.arenaLength)
                        {
                            continue;
                        }
                        move = i;
                        break outer;
                    case WEST:
                        if (info.locationX-1 < 0)
                        {
                            continue;
                        }
                        move = i;
                        break outer;
                    case EAST:
                        if (info.locationX+1 === info.arenaLength)
                        {
                            continue;
                        }
                        move = i;
                        break outer;
                    case IDLE:
                        move = i;
                        break;
                    default:
                }
            }
        }
        else
        {
            move = pathBest.moveDirection;
        }

        switch (move)
        {
        case NORTH:
            return "north";
        case SOUTH:
            return "south";
        case EAST:
            return "east";
        case WEST:
            return "west";
        default:
            return "none";
        }
    }
    return process(me, others, coins);
}

This bot uses path finding coupled with a map of cell desirabilities to avoid bots that could kill us, go for coins that are close, not traps and are least risky.

It does not seem to be a contender for the winning place, but it does hold its own and will be alive at the end of the match if it survives the initial melee.

Bot now has mid-to-late game strategy that ignores coins it cannot reach before other bots and if cannot go to any coin then moves to the closest cell that is furthest away from all other bots that are stronger than itself.

It now has a chance of winning.

Note sorry for the crappy code, I have automatically converted it from Java

\$\endgroup\$
3
  • \$\begingroup\$ Be sure to get any bugs and/or updates out of the way soon, 18 hours to due date! \$\endgroup\$
    – rydwolf
    Commented Aug 14, 2018 at 17:55
  • \$\begingroup\$ @RedwolfPrograms Have you observed a bug? if so please let me know so I can correct. Thanks \$\endgroup\$
    – Moogie
    Commented Aug 14, 2018 at 22:42
  • \$\begingroup\$ No, but you never know. Just be sure to double check, since I've seen many bots lose due to one wrong number, or wrong function, or mistype more times than I can count \$\endgroup\$
    – rydwolf
    Commented Aug 14, 2018 at 22:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.