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), wherechance
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.
wand
wins?Infinity
?NaN
? What's the score??? \$\endgroup\$