11
\$\begingroup\$

In this challenge, all bots are wizards. At the beginning of each round, they create a wand which has a certain chance of successfully stunning another wizard. They then fight, by choosing one bot to attack in each turn. The last bot remaining wins.

Results

500 games, 1000 rounds each

Revenge!: 132101.68886125216
JustEnough: 125373.84608578209
Copycat: 118350.98257686458
Climber: 100280
UpperMiddleClassHunter: 99618.4944296837
Golden: 92541.83731532418
Bully: 84120
TallPoppyLoper: 81359.05734216684
A Wizard Arrives Precisely When He Means To: 81218.40354787472
The Wild Card: 77687.08356861532
My First Bot: 74766.78481191777
Roulette Wheel: 67559.90399160102
Don't Kill Me: 58361.64359801064
Marginally Lower Threat: 40538.27420692073
Simple Threat Neutralizer: 13957
ThereCanOnlyBeOneStrongest: 13000
The Terminator: 3701
SimpleSimon: 3295
Peace Lover: 0
Pacifist: 0

Game details

  • A game consists of a number of rounds
  • At the beginning of each round, all bots choose the success chance of their wand (0 to 1, inclusive, defaults to 0 if invalid)
  • A round will last until one bot remains, or all remaining bots have a 0% chance of winning
    • In each turn, every bot must choose a bot to attack
    • If an attack succeeds (based on the wand's success chance), the bot who is attacked is eliminated for the rest of the round
  • The last bot alive wins 1/sqrt(chance) points (up to a maximum of 20), where chance is the wand's success chance
  • If all remaining bots are eliminated in the same turn, no points are awarded
  • The bot with the most points after a certain number of games wins

Bot format

Each bot should consist of two JavaScript functions, the first of which is run once per turn. This function will be provided three arguments:

  • Itself as an object (see below for format)
  • An array containing all other non-eliminated bots
    • Each bot is formatted as an object
    • Each bot contains a uid property, which is an integer ID randomly chosen for each game
    • Each bot contains a wand property, which contains their wand's success chance
    • Each bot contains a attacked property, which contains the UID of the last bot they attempted to attack (or null if first turn)
  • A storage object
    • Can be used for storage between turns or rounds (resets for each game)

The return value of this function should be the UID of the bot to attack.

A second function will choose its wand's success chance before the first turn of each round, and will be provided one argument, its storage object.

Example bot

Example bot will attack a random (valid) bot that another bot attacked last turn, even itself. Always chooses a 75% wand success chance.

//Turn

function (self, others, storage) {
    var attacked = others.map(bot => bot.attacked).filter(uid => others.find(bot => bot.uid == uid));
    return attacked[Math.random() * attacked.length | 0] || others[0].uid;
}

//Wand

function (storage) {
    return 0.75;
}

Controller

//Bots go in this object:

var bots = {
    "Example bot": [
        function (self, others, storage) {
            var attacked = others.map(bot => bot.attacked).filter(uid => others.find(bot => bot.uid == uid));
            return attacked[Math.random() * attacked.length | 0] || others[0].uid;
        },
        function (storage) {
            return 0.75;
        }
    ]
};

//games: Number of games to run
//rounds: Number of rounds per game
//turns: Max turns per game
//log: 0 (no logging), 1 (errors only), 2 (detailed logging)

function runGames(games = 100, rounds = 100, turns = 1000, log = 0) {
    let p = performance.now();
    let botData = [];
    for (let bot in bots)
        botData.push({
            name: bot,
            turn: bots[bot][0],
            round: bots[bot][1],
            uid: 0,
            alive: !0,
            wand: 0,
            attacked: 0,
            points: 0,
            storage: {}
        });
    for (let g = 0; g < games; g++) {
        let uids = new Array(botData.length).fill(0).map((a, b) => b);
        let round = 0;
        for (let j, i = uids.length - 1; i > 0; i--) {
            j = Math.floor(Math.random() * (i + 1));
            [uids[i], uids[j]] = [uids[j], uids[i]];
        }
        for (let i = 0; i < botData.length; i++) {
            botData[i].uid = uids[i];
            botData[i].storage = {};
        }
        for (let r = 0; r < rounds; r++) {
            let turn = 0;
            if (log >= 2)
                console.log("[0] Starting round " + (g + 1) + "-" + (r + 1));
            for (let b, i = 0; i < botData.length; i++) {
                b = botData[i];
                try {
                    b.wand = Math.max(0, Math.min(1, Number(b.round(
                        b.storage
                    ))));
                } catch(e) {
                    b.wand = 0;
                    if (log)
                        console.warn("[" + turn + "] " + b.name + "[1]:\n" + (e.stack || e.message));
                }
                if (Number.isNaN(b.wand))
                    b.wand = 0;
                if (log >= 2)
                    console.log("[" + turn + "] " + b.name + " (" + b.uid + "): " + (b.wand * 100) + "%");
                b.alive = !0;
                b.attacked = null;
            }
            do {
                let attacks = [];
                let max, alive = [];
                turn++;
                for (let b, i = 0; i < botData.length; i++) {
                    b = botData[i];
                    if (!b.alive)
                        continue;
                    try {
                        attacks[i] = b.turn(
                            {
                                uid: b.uid,
                                wand: b.wand,
                                attacked: b.attacked
                            },
                            botData.filter(a => a.alive && a.uid != b.uid).map(a => ({
                                uid: a.uid,
                                wand: a.wand,
                                attacked: a.attacked
                            })).sort((a, b) => a.uid - b.uid),
                            b.storage
                        );
                    } catch(e) {
                        attacks[i] = !1;
                        if (log)
                            console.warn("[" + turn + "] " + b.name + "[0]:\n" + (e.stack || e.message));
                    }
                }
                for (let b, i = 0; i < botData.length; i++) {
                    b = botData[i];
                    if (!b.alive)
                        continue;
                    if (!botData.find(a => a.alive && a.uid === attacks[i])) {
                        b.alive = !1;
                        if (log >= 2)
                            console.log("[" + turn + "] " + b.name + " (" + b.uid + "): Invalid bot (" + attacks[i] + ")");
                        attacks[i] = undefined;
                    }
                    b.attacked = attacks[i];
                }
                for (let b, i = 0; i < botData.length; i++) {
                    b = botData[i];
                    if (attacks[i] === undefined)
                        continue;
                    if (Math.random() < b.wand) {
                        if (log >= 2)
                            console.log("[" + turn + "] " + b.name + " (" + b.uid + "): Attacked " + botData.find(a => a.uid == attacks[i]).name + " (" + attacks[i] + ")");
                        botData.find(a => a.uid == attacks[i]).alive = !1;
                    } else if (log >= 2) {
                        console.log("[" + turn + "] " + b.name + " (" + b.uid + "): Failed to attack " + botData.find(a => a.uid == attacks[i]).name + " (" + attacks[i] + ")");
                    }
                }
                alive = botData.filter(a => a.alive);
                if (alive.length == 1)
                    alive[0].points += Math.min(20, 1 / Math.sqrt(alive[0].wand));
                if (alive.length <= 1) {
                    if (log >= 2)
                        console.log("[" + turn + "] Winner of round " + (g + 1) + "-" + (r + 1) + ": " + (alive.length ? alive[0].name : "No winner"));
                    break;
                }
            } while (turn < turns);
        }
    }
    console.log(games + " game(s) completed (" + ((performance.now() - p) / 1000).toFixed(3) + "s):\n" + botData.map(a => [a.name, a.points]).sort((a, b) => b[1] - a[1]).map(a => a[0] + ": " + a[1]).join("\n"));
}

Rules

  • Accessing the controller or other bots is not allowed
  • There's no time limit, but keep within reason
  • Any bots that error or choose an invalid bot to attack are eliminated
  • Deadline: Challenge finished Friday, April 3, 12:00 UTC (8:00 EDT)

Clarification: Wand success chance is only chosen at the beginning of each round, not once per turn.

\$\endgroup\$
21
  • 1
    \$\begingroup\$ It looks like the deadline (March 27, 12:00 UTC) is 5½ hours before the challenge was posted (March 27, 17:28 UTC), making it impossible to compete \$\endgroup\$
    – Endenite
    Mar 27, 2020 at 17:54
  • 1
    \$\begingroup\$ I don’t really understand why you’d give yourself a chance of less than 100% \$\endgroup\$
    – AJFaraday
    Mar 27, 2020 at 21:00
  • 3
    \$\begingroup\$ @AJFaraday To increase the points you get from winning, and make yourself less of a target (as bots will likely attack the biggest threat) \$\endgroup\$ Mar 27, 2020 at 21:37
  • 1
    \$\begingroup\$ So... what happens when a player with 0 wand wins? Infinity? NaN? What's the score??? \$\endgroup\$
    – Alion
    Mar 27, 2020 at 23:32
  • 3
    \$\begingroup\$ @RedwolfPrograms I appreciate the bug fix, but at least mention this arbitrary limit that you've just imposed in the spec, please. It'd be nice if you also put some answers to my other questions there while you're at it... \$\endgroup\$
    – Alion
    Mar 28, 2020 at 10:57

20 Answers 20

6
\$\begingroup\$

Golden

Uses the golden ratio, which, if my calculations are correct, is a Nash equilibrium solution to the two-player game. Attacks the most deadly if there is still more than one opponent.

"Golden": [
    function(self, others, storage) {
        var targ;
        for (var i in others) {
            if (targ == null || others[targ].wand < others[i].wand)
                targ = i
        }
        return others[targ].uid;
    },
    function(storage) {
        return 0.381966;
    }
]

Try it online!

\$\endgroup\$
5
\$\begingroup\$

Revenge!

A bot that always takes revenge on the bot that attacked it the most thus far, with just a 0.5% wand success rate:

'Revenge!': [
  function(self, others, storage){
    // Initialize attackLog the first round:
    if(storage.attackLog == null)
      storage.attackLog = {};

    // Update the attackLog:
    others.forEach(other => {
      if(storage.attackLog[other.uid] == null)
        storage.attackLog[other.uid] = 0;
      if(other.attacked == self.uid)
        storage.attackLog[other.uid]++;
    });

    // Filter from the remaining bots the one that attacked you the most thus far:
    var attackedMostBy = null;
    var maxAttackedBy = 0;
    others.forEach(other => {
      if(storage.attackLog[other.uid] > maxAttackedBy){
        attackedMostBy = other;
        maxAttackedBy = storage.attackLog[other.uid];
      }
    });

    // If the bots that are left never attacked you: just attack one at random:
    return attackedMostBy == null ? others[Math.random()*others.length|0].uid
    // Else: take revenge on the bot that attacked you the most thus far:
                                  : attackedMostBy.uid;
  },
  function(storage){
    // 0.25% is the lowest wand success rate that is still within the max of 20 boundary,
    // so we'll use twice that wand success rate of 0.5% for a score of roughly 14.1 each
    return 0.005;
  }
]

Try it with all current bots. It seems to be doing quite well, since it's usually in second place.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ First place, nice bot! \$\endgroup\$ Apr 3, 2020 at 23:01
  • \$\begingroup\$ good work! It has been a consistent top place bot. \$\endgroup\$
    – Moogie
    Apr 3, 2020 at 23:20
  • \$\begingroup\$ @RedwolfPrograms Oh, really? Nice! :) In my TIO tests it was always second place after Golden, although new bots might have affected the scores. Also, how many rounds did you do, out of curiosity? \$\endgroup\$ Apr 4, 2020 at 9:02
  • \$\begingroup\$ @KevinCruijssen 500 games, 1000 rounds each (the additional number of rounds might have affected it as well) \$\endgroup\$ Apr 4, 2020 at 14:52
5
\$\begingroup\$

Climber

Always target the bot who, by our calculation, looks to have the highest score. Keeps a 1% wand for the 10 point payout if he wins.

"Climber": [
    function(me, them, storage) {
        storage.current = them.slice(0);
        fromEntries = function(arr){
            var obj = {};
            for(var pair of arr) obj[pair[0]] = pair[1];
            return obj
        }
        if(!storage.scores)
            storage.scores = fromEntries(them.map(x => [x.uid, 0]));

        var targ;
        for (var i in them) {
            if (targ == null || storage.scores[targ] < storage.scores[them[i].uid])
                targ = them[i].uid;
        }
        return targ;
    },
    function(storage) {
        if(storage.scores){
            for(var bot of storage.current) 
                // divide the score by the number of bots that could have won
                storage.scores[bot.uid] += 
                           Math.max(Math.pow(bot.wand, -1/2), 20) /
                             (storage.current.length + 1);
        }
        return 0.01;
    }
]
\$\endgroup\$
5
\$\begingroup\$

Bully

"Bully": [
    function attack(self, others, storage) {
        minWand = Math.min(...others.map(bot => bot.wand));
        targets = others.filter(bot => bot.wand === minWand);
        return targets[Math.random() * targets.length | 0].uid;
    },

    function wandChance(storage) {
        return 0.0025;
    }
]

Only targets the "weakest" bots, aka bots with the lowest wand success chance.

Test all current bots!

\$\endgroup\$
4
\$\begingroup\$

Upper Middle Class Hunter

"UpperMiddleClassHunter": [
    function(self, others, storage) {
        var myOthers = others;
        myOthers.sort((a, b) => a.wand - b.wand);
    return myOthers[myOthers.length * 0.8 | 0].uid;
    },
    function(storage) {
        return 0.333;
    }
],

It hunts the upper middle class bots knowing that the supreme bots will kill each other and be targets from other bots and so it doesn't need to target them. Nor will it hunt the lower bots as they also are targeted by others. Instead hunt the bots that are not the strongest but could be a threat.

It attacks with a 1/3 chance of winning, seeking a balance to kill of the threatening bots, but still gain a reasonable score if winning a round.

Its simplicity hides its effectiveness... in my tests it is either winner or runner up after 500 games.

\$\endgroup\$
3
\$\begingroup\$

Peace Lover

Not a troll entry! ...probably.

function attack(me, alive) {
    const them = alive.filter(bot => bot.uid !== me.uid);
    const attackers = them.filter(bot => bot.attacked === me.uid);

    function getMaxWand(bots) {
        return Math.max(...bots.map(bot => bot.wand));
    }
    function hasWand(wand) {
        return bot => bot.wand === wand;
    }

    function getTargets(bots) {
        return bots.filter(hasWand(getMaxWand(bots)));
    }
    const primaryTargets = getTargets(attackers);
    const secondaryTargets = getTargets(them);

    function getRandomValue(array) {
        return array[Math.random() * array.length | 0];
    }

    return (getRandomValue(primaryTargets) || getRandomValue(secondaryTargets)).uid;
}
function chooseChance() {
    return 0;
}

Points its useless wand according to an improved version of the heuristic of this other entry of mine.

Note: This entry didn't work properly because of a bug in the controller, but along with the bug fix came a limit of a measly 20 instead of the Infinity that can be inferred from the spec imposed on the score per win...

\$\endgroup\$
2
  • 2
    \$\begingroup\$ If the maximum score you can get is 20 anyway, you might as well set the wand's success chance to 0.05 \$\endgroup\$ Mar 30, 2020 at 15:52
  • 2
    \$\begingroup\$ @mathjunkie First, I just want to keep this entry fully peaceful. It's beneficial vs entries that don't like players with high wand. Second, it would have to be 0.0025. I expected more from a math junkie :P \$\endgroup\$
    – Alion
    Mar 30, 2020 at 17:53
3
\$\begingroup\$

JustEnough

Targets the strongest of the lowest third of opponents using a wand that is slightly stronger.

Otherwise, targets the first opponent with almost full power unless less than three opponents exist, then use almost no power.

"JustEnough": [
    function(self, others, storage) {

        var targ;
        storage.wand = 0.002;

        var multiplier = 1.01;
        if (others.length > 2){
           multiplier = 1.25;
           storage.wand = 0.99;
        }

        for (var i in others) {
            if ( others[i].wand < 0.3)
            {
                if (targ == null || others[i].wand > others[targ].wand) {
                   targ = i;
                   storage.wand = others[targ].wand * multiplier ;
                   if (storage.wand > .99) storage.wand = 0.99;
                }
            }
        }

        if (targ == null)
        {
           targ = 0;
        }

        return others[targ].uid;
    },
    function(storage) {
        return storage.wand;
    }
]

Bot derived from MegaTom's entry: Golden

\$\endgroup\$
3
\$\begingroup\$

My First Bot

This is my first attempt at entering a KotH competition and I don't know what I'm doing.

It literally just hits everyone in turn for a damage value which seemed to be returning good results.

    'My First Bot': [
      function(self, others,storage) {
        if(typeof storage.n == 'undefined'){
          storage.n = 0;
        }
        if(others[storage.n]){
          return others[storage.n].uid;
        } else {
          storage.n = 0;
          return others[0].uid;
        }
      },
      function(storage) {
        return 0.009;
      }
    ]
\$\endgroup\$
3
\$\begingroup\$

A Wizard Arrives Precisely When He Means To

'A Wizard Arrives Precisely When He Means To': [
  function(self, others, storage){
    // Store the amount of bots left:
    storage.numBots = others.length;

    // Get the bots within the highest 10% wand success rate:
    var strongOnes = [];
    for(var wandTreshold = 0.9; strongOnes.length == 0; wandTreshold -= 0.1)
      var strongOnes = others.filter(o => o.wand >= wandTreshold);
    // And pick a random one of those strong bots to attack:
    return strongOnes[Math.random()*strongOnes.length|0].uid;
  },
  function(storage){
    // Increase the round number (or initialize at 1 the first round):
    storage.round = storage.round == null ? 1 : storage.round+1;

    // If just one or two bots are left: increase wand success rate to 100%
    return storage.numBots <= 2 ? 1
    // If there are more than two bots are left: use wand success rate dependent on the round
                                : Math.max(1 / storage.round - 0.01, 0.001);
  }
]

This bot does a couple of things:

  1. It lowers its own wand success rate \$SR\$ based on the round number \$r\$, using the formula: \$SR_r\text{ (in %)}=\frac{1}{round}\times100-1\$, which will progress like this: \$SR_r=[99, 49, 32\frac{1}{3}, 24, 19, 15\frac{2}{3}, 13\frac{2}{7}, 11\frac{1}{2}, 10\frac{1}{9},9, ...]\$, unless this is below 0.1%, in which case it'll use that instead. Also, it will use a 100% wand success rate, if there are just two or three bots (including itself) left in play.
  2. It will attack a random bot within the top 10% ranged wand success rate (i.e. \$[90\%, 100\%]\$ until all those are eliminated, then \$[80\%, 90\%)\$ until all those are eliminated, etc. So let's say the bots left have a wand success rate of [83%, 75%, 50%, 1%, 25%, 82.5%, 50%, 50%, 72.5%, 85%] it will pick a random both in the range \$[80\%, 90\%)\$, thus one of the bots [83%, 82.5%, 85%].

From the current bots, this bot usually ends up somewhere in the middle, so not too great, but also not last: Try it with all current bots.

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

The Terminator

Turn

function (self, others, storage) {
    var attacked = others.map(bot => bot.attacked).filter(uid => others.find(bot => bot.uid == uid));
    return attacked[Math.random() * attacked.length | 0] || others[0].uid;
}

Wand

function (storage) {
    return 1.00;
}

Listen, and understand! That The Terminator is out there! It can't be bargained with. It can't be reasoned with. It doesn't feel pity, or remorse, or fear. And it absolutely will not stop... ever, until you are dead!

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

Simple Threat Neutralizer

Just to get this challenge going a bit, perhaps...

function attack(me, them) {
    them = them.filter(bot => bot.uid !== me.uid);
    const maxWand = Math.max(...them.map(bot => bot.wand));
    const attackers = them.filter(bot => bot.attacked === me.uid);
    return (attackers.filter(bot => bot.wand === maxWand)[0] || them.filter(bot => bot.wand === maxWand)[0]).uid;
}
function chooseChance() {
    return 1;
}

Chooses bots with a simple danger heuristic - first bots that have attacked it in the previous turn, then all the rest. Sort by most powerful wand and target the first in the array.

\$\endgroup\$
2
  • \$\begingroup\$ Note: No need to filter yourself out of the them array, that's done by the controller by default. \$\endgroup\$ Mar 28, 2020 at 1:14
  • 1
    \$\begingroup\$ @RedwolfPrograms Keeping it in in case the controller starts following the spec in this regard... \$\endgroup\$
    – Alion
    Mar 28, 2020 at 10:25
2
\$\begingroup\$

Roulette Wheel

Every action taken is uniformly random.

"Roulette Wheel": [
  function (self, others, storage) {
    return others[Math.random() * others.length | 0].uid //Random target out of all opponents
  },
  function (storage) {
    return 0.0025 + Math.random() * 0.9975 //Random strength in range [1/400, 1]
  }
]
\$\endgroup\$
2
\$\begingroup\$

Tall Poppy Loper

Has a major case of Tall Poppy Syndrome and will elminate that bot.

It also has an irrational fear of others' potential so will elminate bots that threaten its supremancy.

"TallPoppyLoper": [
    function(self, others, storage) {

        if (storage.history == null )
        {
            storage.history = {};
            storage.selfuid = self.uid;
            storage.roundNo = 0;

            for (var i in others) {
                var historyBot = {};
                historyBot.alive = true;
                historyBot.killCount = 0;
                storage.history[others[i].uid] = historyBot;
            } 
        }
        else if (self.attacked === null)
        {
            storage.roundNo = storage.roundNo + 1;
        }

        for (var i in others) {
            storage.history[others[i].uid].alive = true;
        }

        var targ;
        for (var i in others) {

            if (others[i].attacked != self.uid && others[i].attacked !=null && !storage.history[others[i].attacked].alive) {
                storage.history[others[i].uid].killCount = storage.history[others[i].uid].killCount + 1;
            }

            if (targ == null || storage.history[others[targ].uid].killCount < storage.history[others[i].uid].killCount)
            {
                targ = i
            }
        }

        for (var i in others) {
            storage.history[others[i].uid].alive = false;
        }

        return others[targ].uid;
    },
    function(storage) {
        return 0.95;
    }
]

Bot derived from MegaTom's entry: Golden

\$\endgroup\$
3
  • \$\begingroup\$ This bot can throws error around this part: storage.history[others[i].attacked].alive, since storage.history[...] is undefined. \$\endgroup\$ Mar 31, 2020 at 6:51
  • \$\begingroup\$ yeah, just tried it out... in the process of fixing it up \$\endgroup\$
    – Moogie
    Mar 31, 2020 at 10:47
  • \$\begingroup\$ Now fixed... i think \$\endgroup\$
    – Moogie
    Mar 31, 2020 at 10:56
2
\$\begingroup\$

Don't Kill Me

Survives by staying the second most dangerous wizard throughout the game, so there's always a bigger fish to fry. Scores pretty high.

"Don't Kill Me": [
    function attack(self, others, storage) {
        storage.left = others.length
        maxWand = Math.max(...others.map(bot => bot.wand))
        storage.safeWand = Math.max(...others.filter(bot => bot.wand != maxWand).map(bot => bot.wand))
        return others.filter(bot => bot.wand === maxWand)[0].uid
    },

    function wandChance(storage) {
        return storage.safeWand ? ((storage.left > 1) ? storage.safeWand : 0.01) : 0.1
    }
]

Try it Online!

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

The Wild Card

Chooses a wand success chance at random from the set {0.1, 0.2, ..., 0.9}, favouring values closer to 0.5. Each turn, it makes a decision to either attack a bot who previously attacked it or attack at random.

'The Wild Card': [
  function(self, others, storage){
    // Keep a list of the bots who we hold grudges against
    if (!storage.enemies) storage.enemies = [];

    // Update our list of enemies
    others.forEach(o =>{
        if (o.attacked === self.uid && !storage.enemies.includes(o.uid)){
           storage.enemies.push(o.uid);
        }
    });

    // Filter out bots that have been eliminated
    storage.enemies = storage.enemies.filter(x => others.map(o => o.uid).includes(x));

    if (Math.random() > storage.wand || storage.enemies.length === 0){
        // Our wand couldn't beat a random number
        return others[Math.random() * others.length | 0].uid
    }

    // Our wand was stronger than a random number - attack one of our "enemies"
    return storage.enemies[Math.random() * storage.enemies.length | 0];
  },
  function(storage){
    // Reset the enemies list at the start of each round
    storage.enemies = [];

    // Randomly assign a wand success rate (favours values closer to 0.5)
    var u = Math.random();
    var randNum = 0.9
    if (u < 0.3) randNum = 0.5
    else if (u < 0.5) randNum = 0.4
    else if (u < 0.7) randNum = 0.6
    else if (u < 0.8) randNum = 0.3
    else if (u < 0.9) randNum = 0.7
    else if (u < 0.925) randNum = 0.2
    else if (u < 0.95) randNum = 0.8
    else if (u < 0.975) randNum = 0.1

    // Store our wand success rate to use when choosing a target
    storage.wand = randNum;

    return randNum;
  }
],

Try it against current bots

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

Copycat

Attempts to make it to the late stages of a round, then copy the average wand strength of the remaining bots when there are 1 or 2 other bots left.

Targets bots with wand strength between 0.1 and 1 (exclusive).

"Copycat": [
    function(self, others, storage) {
        // Copy the strategy of the bots that make it to the end
        if (others.length == 2){
            storage.strength = (others[0].wand + others[1].wand) / 2;
        } else if (others.length == 1) {
            storage.strength = others[0].wand;
        }

        // Filter out the highest and lowest strength bots
        var filteredOthers = others.filter(a => a.wand < 1 && a.wand > 0.1);

        if (filteredOthers.length > 0)
            return filteredOthers[Math.random() * filteredOthers.length | 0].uid;
        return others[Math.max(0, others.length - 2)].uid;
    },
    function(storage) {
        // Prevent overfitting: discard the copied strategy with 1/10 probability
        if (!storage.strength || Math.random() <= 0.1){
            storage.strength = 0.025;
        }

        return storage.strength;
    }
],

Try it against the other bots

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

There Can Only Be One Strongest

Simply chooses the strongest remaining bot and attacks at full power!

"ThereCanOnlyBeOneStrongest": [
    function(self, others) {
        var targ;
        for (var i in others) {
            if (targ == null || others[targ].wand < others[i].wand)
                targ = i
        }
        return others[targ].uid;
    },
    function(storage) {
        return 1;
    }
]

Bot derived from MegaTom's entry: Golden

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

Simple Simon

Simply chooses the first bot in the collection of other opponents and attacks at full power

"SimpleSimon": [
    function(self, others) {
        return others[0].uid;
    },
    function(storage) {
        return 1;
    }
]

Bot derived from MegaTom's entry: Golden

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

Marginally Lower Threat

"Marginally Lower Threat": [
  function (self, others, storage) {
    let maxwand = others.reduce((a,b) => Math.max(a,b.wand), 0)
    let maxbots = others.filter(b => b.wand == maxwand)
    return maxbots[Math.random() * maxbots.length | 0].uid
  },
  function (storage) {
    return 0.999
  }
]

If bots are going to target the strongest remaining, then why not be just a little less than the strongest? Targets a random strongest bot each turn.

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

Pacifist

Thinks wizard battles are not cool and will not target anyone.

"Pacifist": [
    function(self, others) {
        return others[0].uid;
    },
    function(storage) {
        return 0;
    }
]

Bot derived from MegaTom's entry: Golden

\$\endgroup\$
3
  • 2
    \$\begingroup\$ null return value is currently invalid. Maybe returning self.uid would be better? Although this makes the bots less pacifist and more like suicidal :) \$\endgroup\$ Mar 31, 2020 at 6:39
  • \$\begingroup\$ -1 "In each turn, every bot must choose a bot to attack" \$\endgroup\$
    – Alion
    Mar 31, 2020 at 7:43
  • \$\begingroup\$ ok now targetting the "first" bot \$\endgroup\$
    – Moogie
    Mar 31, 2020 at 11:00

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.