This challenge has officially ended. Further submissions will not be competitive (but are still welcome!). View the scores here
In this challenge, submissions ("bots") should be Javascript functions which try to win as many races as possible. In each race, bots will have a limited amount of energy which should be used to travel as quickly as possible over a distance of 100 units.
Mechanics
Each game will consist of a number of races, which are made up of a number of turns. In each turn, bots will choose a nonnegative distance to travel forward. The amount of energy consumed is equal to the square of the distance traveled (and bots will only travel as far as their energy level allows if they return a longer distance).
At the beginning of each race, bots' positions are reset to 0. Once one or more bots reach a distance of 100 units, the race will end. Note that if a bot returns a distance which would place it further than 100 units from the start, it will only move as far as it needs in order to win. At the beginning of all races, bots will receive 100 energy in addition to any remaining from the last race. They will also receive a bonus of 1 energy for every unit they traveled in the previous race.
Points
At the end of each race, all bots will receive one point for every ten units they have traveled when the game ends (including fractions of a point). At the end of a game, the bot with the most points wins.
Because it's likely bots will be optimized for a certain number of races, there will be 5 categories in which all bots will compete: 250 races, 750 races, 2500 races, 7500 races, and 25000 races. A bot's overall score will be the sum of their average score per race in each of these categories, making the highest possible score 50.
Input/output
Bots will receive the following arguments: dist
(the distance they have traveled in the current race), energy
(the amount of energy they have), bots
(an array of the distances of all other bots as of the end of the last turn, shuffled at the end of each race), and storage
, which is by default an empty object and can be used to store information between races.
Example bot
Follower will try to keep ahead of the average bot, by the average amount moved per turn.
{
"Follower": function(dist, energy, bots, storage) {
storage.turns = storage.turns || 0;
if (Math.max(...bots))
storage.avg = ((storage.avg || 0) * storage.turns++ + bots.reduce((a, b, i) => a + (b - storage.last[i]), 0) / bots.length) / storage.turns;
storage.last = bots;
return (bots.reduce((a, b) => a + b, 0) / bots.length + (storage.avg || 1)) - dist;
}
}
Controller
// Each bot should be placed in this object
var bot_data = {
"Follower": function(dist, energy, bots, storage) {
storage.turns = storage.turns || 0;
if (Math.max(...bots))
storage.avg = ((storage.avg || 0) * storage.turns++ + bots.reduce((a, b, i) => a + (b - storage.last[i]), 0) / bots.length) / storage.turns;
storage.last = bots;
return (bots.reduce((a, b) => a + b, 0) / bots.length + (storage.avg || 1)) - dist;
}
};
var games = 0;
var records = {};
// races: Number of races
// log: Array of bot names to log information about, or null for no logging
// Per-turn logging will only happen in games with less than 10 races
// Per-race logging will only happen in games with less than 100 races
// bold: Whether to use bold text when logging information
var run_game = function(races, log = [], bold = true) {
var perf_now = performance.now();
var bots = [];
games++;
for (let bot in bot_data)
bots.push({
name: bot,
run: bot_data[bot]
});
var uids = new Array(bots.length);
for (let i = 0; i < uids.length; i++)
uids[i] = i;
var race = 0;
var turn = 0;
for (let r = 0; r < races; r++) {
race++;
for (let j, i = 0; i < uids.length; i++) {
j = Math.random() * (i + 1) | 0;
[uids[i], uids[j]][uids[j], uids[i]];
}
for (let b, i = 0; i < bots.length; i++) {
b = bots[i];
bots[i] = {
name: b.name,
run: b.run,
uid: uids[i],
dist: 0,
energy: (b.energy || 0) + 100,
points: b.points || 0,
storage: b.storage || {},
next: 0,
inactive: 0
};
}
turn = 0;
while ((bots.every(b => b.dist < 100) && bots.some(b => b.energy > 0 && b.inactive < 3))) {
turn++;
for (let b, i = 0; i < bots.length; i++) {
b = bots[i];
try {
b.next = b.run(
b.dist, b.energy,
bots.filter(o => o.uid != b.uid).map(o => o.dist),
b.storage
);
if (log && log.includes(b.name) && races < 10)
console.log("[" + race + ":" + turn + "] " + b.name + "(" + (Math.round(b.dist * 1000) / 1000) + "," + (Math.round(b.energy * 1000) / 1000) + "):", b.next);
} catch(e) {
if (log && races < 10)
console.warn("[" + race + ":" + turn + "] " + b.name + ":\n" + (e.stack || e.message));
b.next = 0;
}
b.next = Number(b.next);
if (Number.isNaN(b.next))
b.next = 0;
b.next = Math.max(Math.min(b.next, 100 - b.dist, Math.sqrt(b.energy)), 0);
if (!b.next)
b.inactive++;
}
for (let b, i = 0; i < bots.length; i++) {
b = bots[i];
b.dist += b.next;
b.energy = Math.max(b.energy - b.next ** 2, 0);
}
}
for (let b, i = 0; i < bots.length; i++) {
b = bots[i];
b.energy = b.energy + b.dist;
b.points += b.dist / 10;
}
if (log && races < 100)
console.log(
(bold ? "%c" : "") + "Race " + race + ":\n" +
(bold ? "%c" : "") + bots.map(b => b).sort((a, b) => b.dist - a.dist).map(
b => b.name.slice(0, 16) + " ".repeat(20 - Math.min(b.name.length, 16)) + (Math.round(b.dist * 1000) / 10000)
).join("\n"), ...(bold ? ["font-weight: bold;", ""] : [])
);
}
for (let i = 0; i < bots.length; i++)
records[bots[i].name] = (records[bots[i].name] || 0) + bots[i].points / races;
if (log)
console.log(
(bold ? "%c" : "") + "Average Points/Race (" + races + " races, " + (Math.ceil((performance.now() - perf_now) * 1000) / 1000) + "ms):\n" +
(bold ? "%c" : "") + bots.sort((a, b) => b.points - a.points).map(
b => b.name.slice(0, 16) + " ".repeat(20 - Math.min(b.name.length, 16)) + (Math.round((b.points / races) * 10000) / 10000)
).join("\n"), ...(bold ? ["font-weight: bold;", ""] : [])
);
};
// Print and clear records for average scores
var print_records = function(bold = true) {
console.log(
(bold ? "%c" : "") + "Sum of Average Points/Game:\n" +
(bold ? "%c" : "") + Object.entries(records).sort((a, b) => b[1] - a[1]).map(
b => b[0].slice(0, 16) + " ".repeat(20 - Math.min(b[0].length, 16)) + (Math.round(b[1] * 10000) / 10000)
).join("\n"), ...(bold ? ["font-weight: bold;", ""] : [])
);
};
var clear_records = function() {
records = {};
};
// Default race categories
run_game(250);
run_game(750);
run_game(2500);
run_game(7500);
run_game(25000);
print_records();
Rules
If no bots have moved for three turns in a row, a race will end (scores will still be counted)
If a bot errors, it will lose a turn (i.e., not move)
Bots may not tamper with the controller or other bots, or be otherwise malicious
Bots should run in a reasonable amount of time
Bots must be deterministic; no randomness unless it's seeded by the bot's arguments
Chat: https://chat.stackexchange.com/rooms/112222/simple-race-koth
Bots due by: Friday, September 4th, 12:00 UTC (08:00 EDT)