46
\$\begingroup\$

This challenge has ended. To see the final scores of the competitors, click here

In this challenge, each submission is one bot. Each bot should be a Javascript function. Bots will fight to obtain the highest total worth in gold. Gold can be farmed, or earned from killing other bots, and is used to upgrade healing, attacking, shielding, and farming.

Objective:

Over a number of rounds containing up to 1000 turns (ends when only one bot is left), the bot with the highest total worth (the sum of all gold obtained) is the winner.

Turns:

In each turn, every bot which is alive (>0 HP) will be run once. It can return a move, which can be one of the following:

  • Heal: Regains HP
  • Attack: Removes HP from another bot
  • Shield: Defends against later attacks
  • Stun: Skips another bot's next turn
  • Farm: Earns gold at the cost of HP
  • Upgrade: Make certain moves better

All bots will return their move before any are executed, so a stun, heal, attack, shield, etc. will not affect any bots moving later in that turn. For example, if Bot A stuns Bot B, and Bot B is after Bot A in the turn order, Bot B will still move later in that same turn and the stun will occur on the next turn.

Combat, Farming, and Upgrading:

Each bot has a maximum HP of 100, and an assigned UID between 0 and 99. This UID changes after every round, and is how bots keep track of each other.

Healing is one of the simplest moves, adding an amount of HP determined by its level (starts at 5 HP). A bot cannot heal past 100 HP.

Attacking a bot by its UID is another possible move, with a base damage of 5 HP at level 0. Bots can also be stunned, skipping their next turn, which also uses UIDs.

Bots have additional shield HP, which has no limit. This shield HP will absorb damage from direct attacks from other bots, and is added by shielding. At level 0, shielding adds 5 shield HP.

Farming will earn 5 gold at level 0, at the cost of 2 HP. This 2 HP cannot be shielded. The only use for gold (beyond winning) is to upgrade moves. Healing, attacking, and shielding have a base value of 5 HP, and farming starts at 5 gold. Each of those moves have individual levels, which start at 0. These formulas will determine the value in HP or gold of a move, where L is the level:

  • Healing: L + 5
  • Attacking: 1.25L + 5
  • Shielding: 1.5L + 5
  • Farming: 2L + 5

The cost of upgrading any move is the same for a certain level, and is determined by 2.5L² + 2.5L + 10, where L is the current level. A bot can use the function cost(currentLevel) as a shortcut to determine this.

Bots start with 25 gold, allowing them to quickly upgrade either two moves to level 1, or one move to level 2. This beginning gold does not count towards a bots total worth. Killing a bot gives you half of its total worth in gold, rounded up, and if two bots kill another in the same turn, they both get the reward.

Input/Output:

In order to communicate with the controller, the return value of the function is used to send move information. One of these should be returned:

  • Heal: heal()
  • Attack: attack(uid)
  • Shield: shield()
  • Stun: stun(uid)
  • Farm: farm()
  • Upgrade: upgrade("heal" / "attack" / "shield" / "farm")

To skip a turn (do nothing), return nothing, or return a falsy value.

To get the current turn number (starts at 1), use turn().

The arguments of your function will include information about your bot, UIDs of other bots, and between-turn storage. The first argument is an object with the following properties: uid, hp, gold, and shield. These are copies of your bot's current information. There is also an nested object levels, with the level numbers of heal, attack, shield, and farm.

The second argument is a shuffled array of all alive bots other than yours, formatted as an object containing properties uid, hp (plus shield), worth, and attack (attack level). The third argument is an empty object which can be used for between-turn storage.

Example Bots:

This bot will farm until it can upgrade its attack to level 5, then attack a random bot each turn until it dies (or wins). Not very effective due to lack of healing/shielding.

function freeTestBotA(me, others, storage) {
    if (me.levels.attack < 5) {
        if (me.gold < cost(me.levels.attack))
            return farm();
        return upgrade("attack");
    }
    return attack(others[0].uid);
}

This bot has two modes: offensive and defensive. It will either stun a random bot or heal when in defensive mode, and it will either attack or shield when in offensive mode. It will attempt to upgrade its attacks whenever possible.

function freeTestBotB(me, others, storage) {
    if (me.gold >= cost(me.levels.attack))
        return upgrade("attack");
    if (me.hp < 50)
        if (Math.random() < 0.5)
            return stun(others[0].uid);
        else
            return heal();
    else
        if (Math.random() < 0.5)
            return attack(others[0].uid);
        else
            return shield();
}

Rules:

  • Standard Loopholes forbidden
  • Bots may not read, modify, or add any variables outside of their scope, may not attempt to cheat, and may not call any controller-defined or DOM functions
  • Return value must be falsy, or one of the above function outputs
  • Bots should not be designed to target a specific bot, but can be designed to take advantage of common strategies
  • Bots may not attack themselves (discovered due to a comment by @Ness)
  • Bots must be sufficiently different from any other bots that they can be reasonably considered separate entries
  • Teaming is now not allowed
  • Controller can be found here
  • Chatroom

New Controller Debugging:

Using the file gold-battle-log.js, you can set the value of the debug property of a bot in botData to 0 (no logging), 1 (log moves), or 2 (log moves, hp, gold, levels, etc.)

Challenge ends at 1700 UTC on Friday, August 9th

\$\endgroup\$
8
  • 4
    \$\begingroup\$ Created a gist with all bots. gist.github.com/Draco18s/2efbf95edcf98d6b1f264e26bbb669d1 I will endeavor to keep it updated (but if not it's a decent start). \$\endgroup\$ Commented Aug 4, 2019 at 14:52
  • 4
    \$\begingroup\$ Auto-updating controller with bots included: redwolfprograms.com/koth \$\endgroup\$
    – rydwolf
    Commented Aug 7, 2019 at 15:36
  • 4
    \$\begingroup\$ I'm voting to close this question because it's already de-facto closed to new answers ("This challenge has ended. To see the final scores ...") \$\endgroup\$ Commented Sep 3, 2019 at 20:29
  • 3
    \$\begingroup\$ @pppery Could you not? I'd be fine with non-competitive answers, and the [closed] at the end is likely to make casual viewers skip over reading my challenge since they;d assume it's low quality or off-topic. \$\endgroup\$
    – rydwolf
    Commented Sep 4, 2019 at 3:11
  • 5
    \$\begingroup\$ @pppery I've never heard of a challenge being closed for being finished until today, and I'd argue the social restriction you want to enforce doesn't even exist. There's no need to close it, and I don't want it closed. To me, that seems like closing for the sake of closing, rather than for the good of the site. If someone wants to post an answer to an old question, they should be able to. There's no note after the serious contender rule saying it has to be a serious contender when it's posted; an answer can still be a serious contender for the challenge even if it's not a contender for winning \$\endgroup\$
    – rydwolf
    Commented Sep 4, 2019 at 3:35

43 Answers 43

1
2
1
\$\begingroup\$

CampBot

function campBot(self,others,storage){
    if(!storage.victimBlacklist){
        storage.victimBlacklist=[]
    }
    let turnsLeft=999-turn()
    function findVictim(){
        let potentialVictims=others.filter(bot=>bot.worth>70&&((bot.attack>1&&bot.hp<200)||bot.hp<80)&&!storage.victimBlacklist.includes(bot.uid))
        if(potentialVictims.length>0){
            let victim=potentialVictims[Math.floor(Math.random()*potentialVictims.length)]
            storage.victimUid=victim.uid
            storage.victimPrevHp=victim.hp
            storage.prevMove="attack"
            return attack(victim.uid)
        }else{
            storage.prevMove="farm"
            return farm()
        }   
    }
    if(self.hp<=(95-self.levels.heal)){
        storage.prevMove="heal"
        return heal()
    } else if(self.shield<=50){
        return shield()
    } else if(self.gold>=cost(self.levels.attack)&&turnsLeft>100&&self.levels.attack<=3){
        storage.prevMove="upgrade"
        return upgrade("attack")
    } else if(self.gold>=cost(self.levels.farm)&&cost(self.levels.farm)<turnsLeft*(2*(self.levels.farm+1)+5)){
        storage.prevMove="upgrade"
        return upgrade("farm")
    }else if(self.gold>=cost(self.levels.heal)&&cost(self.levels.heal)<turnsLeft/(self.levels.heal+6)*(2*self.levels.farm+5)&&self.levels.heal<=2){
        storage.prevMove="upgrade"
        return upgrade("heal")
    }else if(self.gold>=cost(self.levels.shield)&&self.levels.shield<=3){
        return upgrade("shield")
    }else if(!storage.victimUid){
        return findVictim()
    }else if(Object.values(others).map(bot=>bot.uid).includes(storage.victimUid)){
        let victimCurrHp=Object.values(others).filter(bot=>bot.uid==storage.victimUid)[0].hp
        if(storage.victimPrevHp<victimCurrHp&&storage.prevMove==="attack"){
            storage.victimBlacklist.push(storage.victimUid)
            storage.victimUid=undefined
            return findVictim()
        }else{  
            storage.victimPrevHp=victimCurrHp
            storage.prevMove="attack"
            return attack(storage.victimUid)
        }
    }else{
        storage.victimUid=undefined
        return findVictim()
    }
}

If there is an easy target with a decent amount of gold, then it attacks it. If it discovers that the other bot heals faster than it can attack, then it blacklists the other bot from being attacked and finds another victim. If there are no good targets, it just camps by farming while waiting for a good target(hence the name).

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

PacifistBot

He keeps track of every single bot in the game via storage.others. It permits him to know of the dead guys' worth to know if he can win, even when they're dead.

Decision tree:

  • If he's 1v1 and winning, stuns the opponent until turn 1000.
  • If he's really low life, heal. Has to be rethinked because it calcs on max atk over all the others, and now others are more aggressive.
  • If he's not safe, shield. It's the main source of its defense since it will upgrade shield the most. Has to be rethinked because I forgot to take shield attribute in account, I was using me like I use others (hp being hp + shield).
  • Upgrades heal once, so to level 1, never more. it permits 3 farms per heal for a ridiculously low cost, rather than 5 farms per 2 heals.
  • Upgrades shield if someone becomes aggressive. Could be rethinked to look out for aggressiveness towards him.
  • Upgrades farm if possible, keeping a solid active gold basis to be able to upgrade shield in case of imminent danger.
  • Farms.
function pacifistWinnerBot(me, others, storage) {
	if(!storage.start){
		storage.start=true;
		storage.others = others;
	}
	others.map(bot => 
		storage.others.map(
			bot2 => {if(bot2.uid==bot.uid)bot=bot2}//update worth, hp, attack level
		)
	);
	const maxAtk = 5+1.25*Math.max(storage.others.map(bot => bot.attack));
	if(storage.win || ((me.gold > Math.max(storage.others.map(bot => bot.worth))+25) && storage.others.reduce((bot,x) => x+(bot.hp<=0),0)==1)){
		storage.win=true;
		return stun(storage.others.filter(bot => bot.hp>0)[0].uid);
	}
	if(me.hp < 2+maxAtk) return heal();
	if(me.hp < 2+maxAtk*2) return shield();
	if(me.levels.healing<1 && me.gold>cost(me.levels.healing)) return upgrade("heal");
	if((me.gold > cost(me.levels.shield)) && (1.5*me.levels.shield+5 < 2*maxAtk)) return upgrade("shield");
	if(me.gold > 50+cost(me.levels.farm)) return upgrade("farm");
	return farm();
}

Try it online! with for timestamp 4.57pm Aug 2nd UTC+2.

BullyDozer is making it hard, but still happy with what I've done.

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

Captain Farmer

It's hard being everyone's favorite superhero, so he just kills the villains that are easy to get. When he's not busy letting other people do his job, he farms gold and heals once he reaches a certain threshold. When his health gets too low, he'll put up some shields to keep himself safe from the villains (other bots), so he can get back to farming. If he knows he's going to die, he'll farm for the rest of his short life to maximize his gold output.

function captFarmer(me, others, storage) {
    if (!storage.mode)
        storage.mode = "prep";
    var weak = others.find(el => el.hp < me.levels.attack * 1.25 + Math.max(...others.map(el => el.attack)) * 1.75 + 10 && Math.ceil(el.worth / 2) > me.levels.farm * 2 + 5);
    if (weak && storage.weak >= 0) {
        storage.weak += 1;
        return attack(weak.uid);
    }
    if (storage.weak < 0)
        storage.weak += 1;
    else if (storage.weak > 3)
        storage.weak = -3;
    else if (!storage.weak)
        storage.weak = 0;
    if (me.hp + me.shield < 90)
        storage.mode = "lowhp";
    if (storage.mode == "prep") {
        if (turn() < 5)
            return shield();
        if (turn() == 5)
            return upgrade("attack");
        if (turn() == 6)
            return upgrade("attack");
        storage.modeTimer = 0;
        storage.mode = "farm";
    } else if (storage.mode == "lowhp") {
        let last = storage.lastHp;
        let hp = me.hp + me.shield;
        storage.lastHp = hp;
        if (!last)
            return shield();
        if (me.hp == 100) {
            if (me.shield >= 25) {
                storage.mode = "farm";
                storage.modeTimer = 0;
            }
            return shield();
        }
        if (last > hp) {
            if (last - hp + me.levels.heal + 5 <= me.levels.shield * 1.5 + 5)
                return shield();
            else
                return farm();
        } else {
            return shield();
        }
    } else if (storage.mode == "farm") {
        if (me.gold >= cost(me.levels.shield))
            return upgrade("shield");
        if (me.gold >= cost(me.levels.farm))
            return upgrade("farm");
        if (me.gold >= cost(me.levels.attack))
            return upgrade("attack");
        if (me.hp > 80 && !storage.healMode)
            return farm();
        storage.healMode = true;
        if (me.hp < 97)
            return heal();
        storage.healMode = false;
        return shield();
    }
    return shield();
}
\$\endgroup\$
1
\$\begingroup\$

Persistent Blue Shell

function BlueShell(me, others, storage) {
    var victimUid = 0;
    var maxWorth = 0;
    var needNewVictim = true;
    var victimHp = 100;
    for (var i = 0; i < others.length; i++) {
        var hp = others[i].hp;
        var worth = others[i].worth;
        var uid = others[i].uid;
        if (worth > maxWorth) {
            victimUid = uid;
            maxWorth = worth;
            victimHp = hp;
        }
        if (uid == storage.victim) {
            needNewVictim = false;
        }
    }
    var attacked = false;
    if (turn() > 1) {
        attacked = (me.hp + me.shield) < (storage.hp + storage.shield);
    }
    storage.hp = me.hp;
    storage.shield = me.shield;
    if (needNewVictim || !storage.victim || attacked) {
        storage.victim = victimUid;
        storage.victimHp = victimHp;
        if (cost(me.levels.shield) <= me.gold) {
            return upgrade("shield");
        } else {
            return shield();
        }
    }
    if (storage.victimHp <= 5) {
        return attack(storage.victim)
    }
    return stun(storage.victim);
}
  • Select the bot with highest worth
  • Repeatedly attempt to stun (attack if victim has < 5 health)
  • If the chosen bot dies or BlueShell gets deflected (i.e. attacked) re-evaluate who is in the lead and target them instead.
    • But first shield once to avoid dying (hopefully) mitigate damage from attacks

Will never win, and almost never ends with a non-zero worth, but when testing it looks like it will affect the order of the bots in the top few places

\$\endgroup\$
3
  • \$\begingroup\$ Shielding once just adds the equivalent of 5 health to your bot. How does that prevent dying? \$\endgroup\$
    – rydwolf
    Commented Aug 7, 2019 at 19:42
  • \$\begingroup\$ It doesn't prevent it entirely, but it helps to mitigate the damage from the bots that attack "randomly", and shield is both more efficient to upgrade than healing, and doesn't have a cap. Updated the wording slightly to make the intent a little clearer \$\endgroup\$
    – reffu
    Commented Aug 7, 2019 at 19:45
  • 1
    \$\begingroup\$ Another use is to defend against serial killer. If you bot has the lowest HP, serial killer will attack you, then others will "jump on the bandwagon" and kill you \$\endgroup\$
    – rydwolf
    Commented Aug 7, 2019 at 19:56
1
\$\begingroup\$

AntiWar

function antiWar(self,others,storage){
    let turnsLeft=999-turn()
    let lowestHpBots=others.sort((a,b)=>a.hp-b.hp)
    if(self.shield<=50||self.shield<=5500/others.length&&self.shield<=1200&&turn()>=20||lowestHpBots[1]&&lowestHpBots[1].hp>self.hp+self.shield){
        return shield()
    } else if(self.gold>=cost(self.levels.attack)&&turnsLeft>20){
        return upgrade("attack")
    }else if(self.gold>=cost(self.levels.shield)&&self.levels.shield<=4&&turnsLeft>20){
        return upgrade("shield")
    }else{
        return stun(others.sort((a,b)=>b.attack-a.attack)[0].uid)
    }
}

This bot hates all others that fight and tries to prevent war. Its method of doing this is stunning the bot with the most attack, as that bot is most likely to attack others.

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

Reformed TeamBot

function reformedAttackBot(self,others,storage){
    let turnsLeft=999-turn()
    let lowestHpBots=others.sort((a,b)=>a.hp-b.hp)
    let maxDamage=others.map(c=>c.attack).reduce((a, b) => Math.max(a, b))*1.25+5
    if(storage.attackFailures===undefined){
        storage.attackFailures=0
    }
    if(!storage.worth){
        storage.worth=5 
        storage.prevGold=self.gold
    }else if(self.gold>storage.prevGold){
        storage.worth+=self.gold-storage.prevGold
    }
    if(others.length===1&&storage.worth>others[0].worth){
        //stun lock the other bot if there are only 2 left and I can win
        return stun(others[0].uid)
    } else if(self.hp<=(95-self.levels.heal)){
        return heal()
    } else if(lowestHpBots[0]&&lowestHpBots[0].hp<=25&&lowestHpBots[0].worth/2>2*self.levels.farm+5&&self.hp+self.shield>=110){
        //kill assist
        return attack(lowestHpBots[0].uid)
    } else if(self.shield<=200||self.shield<=6000/others.length&&self.shield<=1200&&turn()>=20||lowestHpBots[1]&&lowestHpBots[1].hp>self.hp+self.shield){
        return shield()
    }if(storage.attackFailures>3){
        if(self.gold>=cost(self.levels.shield)&&self.levels.shield<=6){
            return upgrade("shield")
        } else if(self.gold>=cost(self.levels.farm)&&(turnsLeft+1)*(2*(self.levels.farm)+5)<turnsLeft*(2*(self.levels.farm+1)+5)){
            return upgrade("farm")
        } else if(self.gold>=cost(self.levels.heal)&&(turnsLeft+1)/(self.levels.heal+5)*(2*self.levels.farm+5)<turnsLeft/(self.levels.heal+6)*(2*self.levels.farm+5)&&self.levels.heal<=2){
            return upgrade("heal")
        }else{
            return farm()
        }
    } else if(self.gold>=cost(self.levels.attack)&&turnsLeft>20&&self.levels.attack+1<Math.max(others.map(bot=>bot.attack))){
        return upgrade("attack")
    } else if(self.gold>=cost(self.levels.shield)&&self.levels.shield<=4&&turnsLeft>20){
        return upgrade("shield")
    } else if(others.length>0){
        if(others.find(bot=>bot.uid===storage.target)===undefined){
            let target=others.sort((a,b)=>b.worth-a.worth)[0]
            storage.target=target.uid
            if(storage.targetPrevHP!==undefined&&storage.targetPrevHp<target.hp){
                storage.attackFailures++
                return farm()
            }else{
                storage.targetPrevHp=target.hp
                return attack(target.uid)
            }
        }
    } else if(self.gold>=cost(self.levels.farm)&&turnsLeft>100){
        return upgrade("farm")
    }else{
        return farm()   
    }
}

After the ban on teaming, TeamBot - Attack reformed and stopped teaming. This hasn't prevented it from exploiting PersistentBlueShell's stunning to attack, though. If PersistentBlueShell dies, it switches to farming.

Old Teambots

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

LittleAnt

Didn't really have time. Wrote it in last moment. Had only time to test if it works. No idea how it will do.

function LittleAnt(me, others) {
    if (turn() == 1) {
        return upgrade('farm');
    }
    if (turn() ==2) {
        return upgrade('shield');
    }
    let avgHp = 0;
    let minHp = 100;
    let maxHp = 0;
    let avgGold = 0;
    let maxGold = 0;
    let avgAttack = 0;
    let maxAttack = 0;
    let i = 0, calculatedValue;
    let countOthers = others.length;
    let currEnemy = null;
    let estimatedAttackers = 0;
    let maxAttackEnemyId = null;
    let maxUpgradeName = null;
    let maxAttackValue = 0;
    let maxHealValue = 0;
    let maxUpgradeValue = 0;
    let maxFarmValue = 0;
    let bestTacticValue = 0;
    let maxShieldValue = 0;
    let levels = [
        {
            name: "attack",
            level: me.levels.attack,
            averageValue: 2,
            estimatedValue: 0
        },
        {
            name: "farm",
            level: me.levels.farm,
            averageValue: 6,
            estimatedValue: 0
        },
        {
            name: "heal",
            level: me.levels.heal,
            averageValue: 3,
            estimatedValue: 0
        },
        {
            name: "shield",
            level: me.levels.shield,
            averageValue: 8,
            estimatedValue: 0
        }
    ];

    for (i = 0; i < countOthers; i++) {
        currEnemy = others[i];
        avgHp += currEnemy.hp / countOthers;
        avgGold += currEnemy.worth / countOthers;
        avgAttack += currEnemy.attack / countOthers;
        maxHp = ((maxHp > currEnemy.hp) ? maxHp : currEnemy.hp);
        minHp = ((minHp < currEnemy.hp) ? minHp : currEnemy.hp);
        maxGold = ((maxGold > currEnemy.worth) ? maxGold : currEnemy.worth);
        maxAttack = ((maxAttack > currEnemy.hp) ? maxAttack : currEnemy.hp);
        estimatedAttackers = Math.floor(Math.sqrt(countOthers - 1));
        calculatedValue = (-currEnemy.hp + estimatedAttackers * (me.levels.attack + 5)) * (currEnemy.worth / 2);
        maxAttackValue = maxAttackValue > calculatedValue ? maxAttackValue : calculatedValue;
        maxAttackEnemyId = maxAttackValue > calculatedValue ? maxAttackEnemyId : currEnemy.uid;
    }

    maxHealValue = 100 - me.hp + Math.sqrt(turn()) + 5 + Math.sqrt(countOthers);

    for (i = 0; i < 4; i++) {
        if (me.gold - cost(levels[i].level) >= 0) {

        } else {
            levels[i].estimatedValue = -100;
        }
        levels[i].estimatedValue = Math.sqrt(me.hp) * (me.gold / cost(levels[i].level) * levels[i].averageValue) - Math.sqrt(turn());
        maxUpgradeValue = maxUpgradeValue > levels[i].estimatedValue ? maxUpgradeValue : levels[i].estimatedValue;
        maxUpgradeName = maxUpgradeValue > levels[i].estimatedValue ? maxUpgradeName : levels[i].name;
    }

    maxFarmValue = 5 + Math.sqrt(turn()) + me.levels.farm - 2 - Math.sqrt(maxHealValue);
    maxShieldValue = Math.sqrt(turn()) * me.shield + 50;
    maxShieldValue = maxShieldValue > 80 ? maxShieldValue : 80;

    bestTacticValue = Math.max(maxHealValue, maxAttackValue, maxFarmValue, maxUpgradeValue, maxShieldValue);

    if (me.hp < 3) {
        return heal();
    } else {
        if (Math.abs(maxAttackValue - bestTacticValue) < 1) {
            return attack(maxAttackEnemyId);
        }
        if (Math.abs(maxFarmValue - bestTacticValue) < 1) {
            return farm();
        }
        if (Math.abs(maxUpgradeValue - bestTacticValue) < 1) {
            return upgrade(maxUpgradeName);
        }
        if (Math.abs(maxHealValue - bestTacticValue) < 1) {
            return heal();
        }
    }
    return farm();
 }
\$\endgroup\$
3
  • \$\begingroup\$ you should probably add a heading \$\endgroup\$ Commented Aug 9, 2019 at 14:08
  • \$\begingroup\$ and fix the indentation. The trailing } not being inside the code block will mess up the automated code scraper's attempts to load your bot. \$\endgroup\$ Commented Aug 9, 2019 at 14:09
  • \$\begingroup\$ Average gold/round of 412.755, 29th place out of 39 \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2019 at 14:26
1
\$\begingroup\$

LettuceBot

Nobody really likes, nobody really hates it. Bots eat it, but usually not on its own.

function lettuceBot(me, others, storage) {
    var weak = others.find(el => el.hp < me.levels.attack * 1.25 + Math.max(...others.map(el => el.attack)) * 2.5 + 10 && Math.ceil(el.worth / 2) >= 5);
    if (weak)
        return attack(weak.uid);
    if (me.levels.heal < 1)
        return upgrade("heal");
    if (me.gold >= cost(me.levels.farm))
        return upgrade("farm");
    if (me.shield < 100)
        return shield();
    if (me.hp <= 94)
        return heal();
    return farm();
}
\$\endgroup\$
1
\$\begingroup\$

Serial Killer

function serialKiller(self,others,storage){
    let turnsLeft=999-turn()
    let lowestHpBots=others.sort((a,b)=>a.hp-b.hp)
    if(self.hp<=(95-self.levels.heal)){
        return heal()
    } else if(self.shield<=150||self.shield<=6000/others.length&&self.shield<=1200&&turn()>=20||lowestHpBots[1]&&lowestHpBots[1].hp>self.hp+self.shield){
        return shield()
    } else if(self.gold>=cost(self.levels.attack)&&turnsLeft>20){
        return upgrade("attack")
    }else if(self.gold>=cost(self.levels.shield)&&self.levels.shield<=4&&turnsLeft>20){
        return upgrade("shield")
    }else{
        return attack(others.sort((a,b)=>a.hp-b.hp)[0].uid)
    }
}

This bot's one goal is to kill everything, starting from the bot with the lowest health.

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

Bernard Szumborski

function BernardSzumborski(me, others) {
    this.getLevel = function (type) {
        return (typeof me.levels[type] === 'undefined' ? 0 : me.levels[type]);
    };

    this.getAttackPower = function (level) {
        if (typeof level === 'undefined') level = this.getLevel('attack');
        return (level * 1.25) + 5;
    };

    this.shieldOrUpgrade = function () {
        if (myGold >= cost(this.getLevel('shield'))) return upgrade('shield');
        return shield();
    };

    let currentTurn = turn(),
        myGold = me.gold,
        myTotalHp = me.hp + me.shield,
        myAttackPower = this.getAttackPower(),
        topAttackPower = 0,
        attackOptions = [];

    others.sort((a, b) => b.attack - a.attack);
    for (let i = 0; i < others.length; i++) {
        let other = others[i];
        if (i < 5) topAttackPower += this.getAttackPower(other.attack);
        if (other.hp <= myAttackPower + 10) attackOptions.push(other);
    }

    if (attackOptions.length > 0) {
        attackOptions.sort((a, b) => b.worth - a.worth);
        return attack(attackOptions[0].uid);
    }

    if (myTotalHp < (topAttackPower * 3) + 5) return shield();

    if (currentTurn > 50) {
        let topWorth = others.filter(other => (other.hp > (currentTurn / 2) + 100)).sort((a, b) => b.worth - a.worth).slice(0, 3);
        let topHp = others.sort((a, b) => b.hp - a.hp).slice(0, 2);

        for (let topWorthBot of topWorth) {
            for (let topHpBot of topHp) {
                if (topWorthBot.uid === topHpBot.uid) {
                    return stun(topWorthBot.uid);
                }
            }
        }

        if (topWorth.length > 0 && Math.random() * 20 < 19) {
            let target = topWorth[Math.floor(Math.random() * topWorth.length)];
            return stun(target.uid);
        }
    }

    return this.shieldOrUpgrade();
}

Bernard Szumborski is a mercenary, he has been sent by a group of people to stop the powerful wealthy class.

He makes sure that he is strong enough that no one can kill him. Then looks for wealthiest with lots of defense (HP) and targets them because they are the wealthiest of wealthiest! If the ones with top wealth don't have lots of defense, then just targets one of them randomly each turn.

Although it is not in his contract to kill, he gets tempted when he sees someone very weak and kills them.

\$\endgroup\$
0
\$\begingroup\$

TheHalfHalf

Looks every second move for heal or farm. Every other move he shields or randomly attacks other bots. Decides everything on ~50% of its own HP.

function TheHalfHalf(me, others, storage){

    if (turn() == 0) {
        return upgrade("heal");
    }

    if(turn() % 2 == 0) {
        if(me.hp <= 50 - (me.levels.heal + 5)){
            return heal()
        } else {
            if(me.gold >= cost(me.levels.farm)){
                return upgrade("farm")
            } else {
                return farm();
            }
        }
    } else {
        if(me.hp <= 50){            
            return shield();
        } else {
            return attack(others[Math.floor(Math.random() * Math.floor(others.length))].uid);       
        }
    }
}
\$\endgroup\$
4
  • 2
    \$\begingroup\$ I recommend upgrading heal once at the beginning of the game, so you can farm three times before healing rather than two \$\endgroup\$
    – rydwolf
    Commented Aug 7, 2019 at 12:57
  • 1
    \$\begingroup\$ @RedwolfPrograms - Tested it out and increased the average score by +200 - thanks! I did not took a look on many of the other bots here, so I hope this was no trick on improving your or other bots in any way :p blind trust here now :D \$\endgroup\$
    – pixma140
    Commented Aug 7, 2019 at 13:20
  • 1
    \$\begingroup\$ Don't worry, we're all just here to have a good time. I've never heard of anyone trying to cheat or sabotage a bot in these challenges \$\endgroup\$
    – rydwolf
    Commented Aug 7, 2019 at 13:28
  • 1
    \$\begingroup\$ No worries here. Just joking around a little ;) \$\endgroup\$
    – pixma140
    Commented Aug 7, 2019 at 13:40
0
\$\begingroup\$

Barbarian Man

As everyone's least favorite superhero, he's a bit desperate. Rather than farm in his free time, he likes to use complex math to choose which villain to repeatedly attack. Once the world is empty of easy-to-kill villains, he retires to a peaceful life of farming. Like his cousin Captain Farmer, he has a special emergency low-health mode.

function barbarianMan(me, others, storage) {
    if (!storage.mode) {
        storage.mode = "prep";
        storage.prep = 0;
    }
    if (me.hp + me.shield < 100)
        storage.mode = "lowhp";
    var weak = others.find(el => el.hp < me.levels.attack * 1.25 + Math.max(...others.map(el => el.attack)) * 2.5 + 10 && Math.ceil(el.worth / 2) >= 5);
    if (weak && storage.weak >= 0) {
        storage.weak += 1;
        return attack(weak.uid);
    }
    if (storage.weak < 0)
        storage.weak += 1;
    else if (storage.weak > 3)
        storage.weak = -3;
    else if (!storage.weak)
        storage.weak = 0;
    if (storage.mode == "prep") {
        storage.prep += 1;
        if (storage.prep < 3)
            return upgrade("shield");
        if (storage.prep < 7)
            return shield();
        if (storage.prep < 10)
            return farm();
        if (storage.prep < 11)
            return heal();
        if (storage.prep < 13)
            return farm();
        if (storage.prep < 14)
            return heal();
        if (storage.prep < 16)
            return upgrade("attack");
        storage.mode = "kill";
    } else if (storage.mode == "lowhp") {
        if ((me.hp + me.shield) > 150) {
            if (me.hp >= 95)
                storage.mode = "kill";
            return heal();
        }
        return shield();
    } else if (storage.mode == "kill") {
        if (!storage.badTargs) {
            storage.badTargs = [];
            storage.testHp = [];
        }
        storage.lastHp = null;
        if (me.gold >= cost(me.levels.attack) && me.levels.attack < 5)
            return upgrade("attack");
        if (me.gold >= cost(me.levels.shield))
            return upgrade("shield");
        if (me.gold >= cost(me.levels.farm))
            return upgrade("farm");
        if (storage.gTarg && attack(storage.gTarg)[0])
            return attack(storage.gTarg);
        if (storage.test == "attack1" && attack(storage.tTarg)[0]) {
            storage.test = "attack2";
            storage.testHp[0] = others.find(el => el.uid == storage.tTarg).hp;
            return attack(storage.tTarg);
        }
        if (storage.test == "attack2" && attack(storage.tTarg)[0]) {
            storage.test = "exam";
            storage.testHp[1] = others.find(el => el.uid == storage.tTarg).hp;
            return attack(storage.tTarg);
        }
        if (storage.test == "exam" && attack(storage.tTarg)[0]) {
            if (storage.testHp[1] - storage.testHp[0] > 3) {
                storage.gTarg = storage.tTarg;
                storage.test = null;
            } else {
                storage.badTargs.push(storage.tTarg);
                storage.test = null;
            }
            return heal();
        }
        let targs = [];
        let scores = [];
        for (let o, i = 0; i < others.length; i++) {
            o = others[i];
            if (storage.badTargs.includes(o.uid))
                continue;
            targs.push(o.uid);
            scores.push((-(o.worth ** 2) / 5000 + 4 * (o.worth) / 15) / o.hp);
        }
        if (!targs.length)
            storage.mode = "farm";
        storage.tTarg = targs[scores.indexOf(Math.max(...scores))];
        storage.test = "attack1";
        return heal();
    } else if (storage.mode == "farm") {
        if (me.gold >= cost(me.levels.shield))
            return upgrade("shield");
        if (me.gold >= cost(me.levels.farm))
            return upgrade("farm");
        if (me.hp > 80 && !storage.healMode)
            return farm();
        storage.healMode = true;
        if (me.hp < 97)
            return heal();
        storage.healMode = false;
        return shield();
    }
    return shield();
}
\$\endgroup\$
2
  • \$\begingroup\$ He will happily farm himself to death with over 800 shields left. \$\endgroup\$ Commented Aug 7, 2019 at 2:29
  • \$\begingroup\$ @Draco18s Maybe my new update will fix that \$\endgroup\$
    – rydwolf
    Commented Aug 7, 2019 at 3:00
0
\$\begingroup\$

TeacherBot

function TeacherBot(self, others, storage){
    var maxAtk = others.map(c=>c.attack).reduce((a, b) => Math.max(a, b));
    var maxDmg = maxAtk * 1.25 + 5;
    var myDmg = self.levels.attack * 1.25 + 5;
    var potential_victim = others.find( bot => bot.hp <= Math.max(maxDmg, myDmg) );
    if (potential_victim) {
        return attack(potential_victim.uid);
    }
    let turnsLeft=999-turn()
    if(self.shield<50 && turn() > 5){
        return shield()
    }else if(self.gold>=cost(self.levels.attack)&&turnsLeft>20){
        return upgrade("attack")
    }else if(self.gold>=cost(self.levels.shield)&&self.levels.shield<=4&&turnsLeft>50){
        return upgrade("shield")
    }else{
        return attack(others.sort((a,b)=>b.attack-a.attack)[0].uid)
    }
}

The teacher is not above getting in on a kill (who doesn't like money? BullyBot, that's who), but its primary focus for attacks is the bot with the highest attack value, aiming to punish them for attacking other bots.

Performs at about rank #8 (~12k average gold).

\$\endgroup\$
1
2

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.