14
\$\begingroup\$

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)

\$\endgroup\$
  • \$\begingroup\$ Yes, as it will travel a distance of 100 (and receive a 100 energy bonus), as well as the default 100 energy per race \$\endgroup\$ – Redwolf Programs Aug 25 at 14:34
  • \$\begingroup\$ I can put all my lo quality botz in a single ansr IF U SAY SO. Shall I? \$\endgroup\$ – null Aug 27 at 11:47
  • \$\begingroup\$ @HighlyRadioactive Sure, I'm fine with that. Reduces clutter, but you might not get as much rep from it. \$\endgroup\$ – Redwolf Programs Aug 27 at 13:46

24 Answers 24

3
\$\begingroup\$

Compensator

A derivative from "SubOptimal", Compensator does not explicitly cater for the "Horde/Burst" strategy, rather it naturally compensates via the knowledge that if it was not first, then it may not have used all its energy in the previous turn and so it may have more energy than expected. To capitalise on this over supply of energy, this bot will use half of the excess energy to try and force a quicker race, but keeping the other half in reserve to try and stretch out the effect of the energy surplus over multiple races.

It does seem to perform slightly better than its sibling (SubOptimal), and as the time of this submission, ekes slightly ahead of all other bots.

{
    "Compensator": function(dist, energy, bots, storage) {
        if ( dist == 0)
        {
          if (storage.targetStartingEnergy == undefined)
          {
            storage.targetStartingEnergy = energy;
            storage.nominalStartingEnergy = energy + 100;
          }
          else
          {
            if (energy <= storage.nominalStartingEnergy)
            {
              storage.targetStartingEnergy = energy;
            }
            else
            {
              storage.targetStartingEnergy = ((energy - storage.nominalStartingEnergy) * 0.5) +  storage.nominalStartingEnergy;
            }
          }

          if (storage.raceNumber == undefined)
          {
            storage.raceNumber = 1;
          }
          else
          {
            storage.raceNumber++;
          }

          storage.energyConsumptionRate = storage.targetStartingEnergy / 100;
        }

        let step = 0;

        if (storage.raceNumber == 1)
        {
          step = 1;
        }
        else
        {
          step = storage.energyConsumptionRate;
        }

        return step;
    }
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ First place! Nice bot. \$\endgroup\$ – Redwolf Programs Sep 4 at 13:34
  • 1
    \$\begingroup\$ Thanks :) The more i participate in KoTH challenges the more i come to realise that normally the simpler bots (beyond the trivial) seem to perform best \$\endgroup\$ – Moogie Sep 5 at 22:20
6
\$\begingroup\$

Rate control

{
    "Rate control": function(distanceTravelled, energyLeft, _, s) {
        if (distanceTravelled === 0) {
            for (let i = 100; i > 0; --i) {
                if (10000 / i > energyLeft) {
                    s.travelSpeed = 100 / (i + 1);
                    break;
                }
            }
        }

        return s.travelSpeed;
    }
}

Each round uses up all of its energy to reach the finish line. Strictly better than "Slow and steady" as this entry will only ever use 1 or more energy per turn while also making sure to always make it to the end. Unoptimized, but still fairly fast.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You can do this just by returning energy / dist \$\endgroup\$ – Redwolf Programs Aug 25 at 14:12
  • 1
    \$\begingroup\$ @RedwolfPrograms Nope! Consider the case for 101 energy: you won't be able to make it to the finish line in 99 turns with that, yet you'll waste energy trying to do that anyway. \$\endgroup\$ – Alion Aug 25 at 14:16
  • \$\begingroup\$ Ah, I didn't think about that! \$\endgroup\$ – Redwolf Programs Aug 25 at 14:17
  • \$\begingroup\$ Fifth place. Did surprisingly well until the last few days! \$\endgroup\$ – Redwolf Programs Sep 4 at 13:39
4
\$\begingroup\$

Slow and steady

{
    "Slow and steady": function() {
        return 1;
    }
}

Baseline bot while I try to figure out what to do with this challenge. Doesn't adapt at all so it might start consistently losing if some sort of meta develops.

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

precomputed

{
    "precomputed": function(dist, energy, bots, storage) {
        if (dist === 0) {
            let movements = Array.from(new Array(100), _=>1)

            const totalEnergyConsumed = () => movements.reduce((a,c)=>a+c**2,0)
            let currentIndex = 0

            while(totalEnergyConsumed() < energy) {
                movements[currentIndex] += movements[currentIndex + 1]
                movements.splice(currentIndex + 1, 1)
                if (++currentIndex >= movements.length - 1) {
                    currentIndex = 0
                }
            }

            currentIndex = movements.length
            while(totalEnergyConsumed() > energy) {
                if(movements[currentIndex] > 1) {
                    movements[currentIndex]--
                    movements.push(1)
                } else {
                    currentIndex--
                }
            }

            storage.movements = {}
            movements.reduce((a,c)=>{storage.movements[a]=c;return a+c}, 0)
        }
        return storage.movements[dist]
    }
}

starts the race by calculating the full path to the end in order to move almost the same speed for the whole race, while still using all the energy available

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

Ninety

{
    "Ninety": function(dist, energy, bots, storage) {
        if (dist === 0) {
            for (let i = 90; i > 0; --i) {
                if (8100 / i > (energy - 10)) {
                    storage.travelSpeed = 90 / (i + 1);
                    break;
                }
            }
        }
        if (dist >= 89) {
            return 1;
        }

        return storage.travelSpeed;
    }
}

Aims to get 9 points per round. I'm not sure how well it does, but it's less likely to lose points to bots finishing faster than it (as compared to Rate Control, which this is forked from).

| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ Averages to about 9.638 points per race \$\endgroup\$ – Redwolf Programs Aug 26 at 21:35
3
\$\begingroup\$

Pulse

"Pulse": function(dist, energy, bots, storage) {
    storage.round = storage.round ? storage.round+1 : 1;
    if(storage.round%500==0) return Math.max([...bots])+50
    return Math.floor(Math.sqrt(energy/100))
}

Each step using only 1% of the energy. Every 500 turns, takes the distance of the first place in this moment, and adds 50 pass it.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I think the +50 is supposed to be outside of the Math.max, right now it just results in an NaN \$\endgroup\$ – Redwolf Programs Aug 28 at 20:38
  • \$\begingroup\$ Yes. thanks :) edited. \$\endgroup\$ – SomoKRoceS Aug 28 at 21:34
  • 1
    \$\begingroup\$ Gets third place in any games with more than 500 races, putting it in fifth place overall \$\endgroup\$ – Redwolf Programs Aug 28 at 22:19
3
\$\begingroup\$

Jack in the Box

Saves up its energy until it can beat the game in 40 moves, bringing the number of average moves per game down.

{
    "Jack in the Box": function(dist, energy, bots, storage) {
        if (!dist) {
            if (energy >= 250) {
                storage.speed = energy / 100
            } else {
                storage.speed = .5
            }
        }
        return storage.speed
    }
}

Simpleton

Simpleton just wants to win :(

{
    "Simpleton": function(dist, energy, bots, storage) {
        return energy / (100 - dist)
    }
}

Steady

Steady tries to go the same amount every turn, but doesn't like having extra energy.

{
    "Steady": function(dist, energy, bots, storage) {
        storage.turns = storage.turns || 0
        storage.totalTurns = storage.totalTurns || 0
        storage.games = storage.games || 0
        storage.totalEnergyGained = storage.totalEnergyGained || 0
        storage.previousEnergy = storage.previousEnergy || 0
        if (!dist) {
            if (storage.games == 0) {
                storage.speed = 1
            } else {
                storage.totalTurns += storage.turns
                storage.turns = 0
                storage.speed = Math.sqrt(storage.totalEnergyGained / storage.totalTurns) + storage.previousEnergy / storage.totalTurns
            }
            storage.totalEnergyGained += energy - storage.previousEnergy
            storage.games++
        }
        storage.turns++;
        storage.previousEnergy = Math.max(energy - Math.max(Math.min(storage.speed, 100 - dist, Math.sqrt(energy)), 0) ** 2, 0)
        return storage.speed;
    }
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Steady got second place! \$\endgroup\$ – Redwolf Programs Sep 4 at 13:37
3
\$\begingroup\$

Suboptimal

The optimal solution where there is no means to affect other racers is to use all your energy to ensure that you finish first to both gain the most energy next round and to deny energy for your opponents. To this can be achieved by, spending 1.0 energy per turn on the first race and then 2.0 per turn for subsequent races (due to the extra 100 energy given for winning at 100 distance)

This can be achieved by calculating the bot's energy / the distance to travel at the start of a race, storing this value and then returning this value each turn of the race.

Now, that we know the optimal solution sans opponent effects, we need to consider the actions that opponents can perform that can affect others. In this game, the only real effect is the ability to force the end of the current race by being the winner. Since bots are allowed to hoard and accumulate energy, they can decide to minimise energy consumption and maximise energy production, sacrificing the chance to gain many points for any particular race and instead spending the accumulated points in one race to dominate the other bots and win that race. While this strategy does not yield high points overall, it does impact bots that are expecting the races to finish after 100 turns. The average turn count of a race is thus reduced. So to compensate for this effect, a sub-optimal solution is derived from the optimal solution by adding a factor that emulates the effect of bots that use this "hoard-burst" strategy.

This factor is not able to computed unless the bot incorporates all other bot strategies and then performing an analysis to determine the factor. This is not really in the spirit of KoTH challenges and may be not allowed. So for this bot, a simple empirical analysis was performed to determine the factor at time of submission and adding in a scalar based on number of submissions that will increase the factor as more submissions on the assumption that later bots may be more interfering.

Ultimately the formula is:

distance_per_turn = starting_energy / (( race_distance + hoard_burst_factor) * (1.0 + (number_of_bots - number_of_bots_at_submission) * 0.1))

{
    "Suboptimal": function(dist, energy, bots, storage) {
        if ( dist == 0)
        {
          storage.energyConsumptionRate = energy / ((100 + 10) * ( 1.0 + (bots.length - 26) * 0.1 ));
        }
        
        return storage.energyConsumptionRate;
    },
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ AFAIK optimal_turns should be 50 except for the first race. 100/2 = 50. \$\endgroup\$ – alex berne Aug 30 at 9:23
  • \$\begingroup\$ @alexberne Good point, i will change to be race_distance to be more explicit \$\endgroup\$ – Moogie Aug 30 at 10:03
  • \$\begingroup\$ Fourth place. Maybe it's a little more optimal than we give it credit for! \$\endgroup\$ – Redwolf Programs Sep 4 at 13:38
  • \$\begingroup\$ @RedwolfPrograms hehe, touché. Thanks for the fun KoTH \$\endgroup\$ – Moogie Sep 5 at 22:25
3
\$\begingroup\$

Robin Hood

{
    "Robin Hood": function(dist, energy, bots, storage) {
        if (!dist)
            storage.move = [
                [100, 1],
                [200, Math.sqrt(192 / 49) - 0.00695],
                [10000 / 49, (100 / 49)]
            ].sort((a, b) => Math.abs(a[0] - energy) - Math.abs(b[0] - energy))[0][1];

        return storage.move;
    }
}

This bot will do one of three things in a race:

  • Move one unit per turn: This is done in the first race of each game to ensure it has the full 200 energy it needs
  • Move slightly slower than two units per turn: This is done every other turn and saves just enough energy to allow it to...
  • Move slightly faster than two units per turn: This lets it finish one turn faster than the current competitors, and just barely undercuts a few of the previous winners (though Rate control is ahead by a hundredth of a point as of posting)
| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Collector

{
    "Collector": function(dist, energy, bots, storage) {
        if (!dist) {
            if ("turns" in storage) {
                storage.avg = ((storage.avg * Math.max(storage.races++, 0)) + storage.turns) / Math.max(storage.races, 1);
            } else {
                storage.avg = 100;
                storage.races = -1;
            }
            
            storage.turns = 0;
            
            storage.move = (energy >= 10000 / (storage.avg | 0)) ? (100 / (storage.avg | 0)) : 0.5;
        }
        
        storage.turns++;
        
        return storage.move;
    }
}

Collector will default to moving at a rate of 0.5 units/turn. This is optimal for gathering energy. If it predicts at the beginning of a race that it can tie or beat the average with the energy it has, then it will try to do so.

Currently loses to Rate control, but might be able to better adapt to new strategies.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Greedy/Greedier

{
    "Greedy": function(dist, energy, bots, storage) {
        return energy > 100 ? 2 : 1;
    },
    "Greedier": function(dist, energy, bots, storage) {
        return dist + energy > 100 ? 2 : 1;
    },
}

Greedy will move 2 units/turn if it has at more than 100 energy otherwise 1 unit/turn. Greedier will move 2 units/turn if it thinks it will probably have enough energy to each the end otherwise 1 unit/turn. These were the simplest ways I could think of of using any bonus energy the bot might have.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Calculated Sprinter

Calculated Sprinter tries to run the full lap as quickly as he can with his current fuel left. Not smart enough to plan for future races, is just happy to be here for the run.

{
    "Calculated Sprinter": function(dist, energy, bots, storage){
        var remaining = 100-dist;
        var energyLeftPerUnit = energy/remaining;
        return Math.sqrt(energyLeftPerUnit)
    }
}
| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

(New) Accelerate

{
    "Accelerate": function(dist, energy, bots, storage) {
        return dist * 0.21 + 0.001;
    },
}

Calm down, I'm just experimenting with extremely simple bots.

This bot is very easy to understand. It initially runs at speed 0.001 and accelerates quadratically.

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ This can never score any points, because on the first turn, dist is 0, so it always returns 0. \$\endgroup\$ – ATaco Aug 27 at 8:16
  • \$\begingroup\$ @ATaco Sorry. Typo... \$\endgroup\$ – null Aug 27 at 8:23
2
\$\begingroup\$

I love Randomness

{
    "I love Randomness": function(dist, energy, bots, storage) {
        storage.rand = Math.abs(dist ^ energy ^ storage.rand) + 1;
        return Math.abs(dist ^ energy ^ storage.rand) + 1;
    }
}
| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ Why did people randomly upvote my low-quality bots? \$\endgroup\$ – null Aug 27 at 11:30
2
\$\begingroup\$

Surprise/Timing

"Timing": function(dist, energy, bots, storage) {
  storage.turns = storage.turns || 0;
  storage.games = storage.games || 0;
  storage.turns++;
  if(dist == 0) {
      storage.games++;
      estimated_game_length = Math.ceil( storage.turns / storage.games)+2;
      desired_speed = 100 / estimated_game_length;
      max_speed = Math.sqrt( energy / estimated_game_length);
      storage.speed = Math.min(desired_speed, max_speed);       
  }
  if(storage.games < 3)
      return storage.games;
  return storage.speed;
},
"Surprise": function(dist, energy, bots, storage) {
  storage.turns = storage.turns || 0;
  storage.games = storage.games || 0;
  storage.turns++;
  if(dist == 0) {
      storage.games++;
      estimated_game_length = Math.ceil( storage.turns / storage.games);
      desired_speed = 100 / (estimated_game_length - 3);
      max_speed = Math.sqrt( energy / estimated_game_length);
    if(desired_speed <= max_speed) {
      storage.speed = desired_speed;
    }
    else {
      storage.speed = Math.min(2, max_speed);
    }       
  }
  if(storage.games < 3)
       return storage.games;
  return storage.speed;
}

Calculate a fixed speed based on how long games are generally lasting. Timing then tries to hit the mark, while Surprise tries to beat it.

While running tests with both of these, it became apparent that we probably need rules about collusion in this KotH, minimal though the interactions are. Surprise could make Timing do much better by sacrificing its own score to make the race length shorter, and could help it even more by only doing so at fixed intervals that Timing knows about.

I'm not pulling these shenanigans now because I assume they're not in the spirit.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I'm tempted to create "Pinkie", the second generation of Surprise. \$\endgroup\$ – null Aug 29 at 2:02
  • \$\begingroup\$ I'd be interested to see how well collaborating bots would do, maybe you could post them here as non-competing and I'd just leave them out of the official games. \$\endgroup\$ – Redwolf Programs Aug 29 at 14:25
1
\$\begingroup\$

Mimic

{
    "Mimic": function(dist, energy, bots, storage) {
        if (!dist) {
            storage.last = bots;
            storage.rand = energy ** 3;
            
            return energy / (100 - dist);
        }
        
        storage.rand = Math.abs(dist ^ dist ** 2 ^ energy ^ energy ** 3 ^ energy ** 5 ^ bots.reduce((s, b) => s + b, 0) ^ storage.rand * (2 ** 31)) / (2 ** 31);
        
        var result = bots.map((b, i) => b - storage.last[i])[storage.rand * bots.length | 0]; // Fix RNG
        
        storage.last = bots;
        
        return Math.max(Math.min(result, Math.sqrt(energy / ((100 - dist) / 4))), Math.sqrt(energy / ((100 - dist))));
    }
}

Creates a list of every other bot's (effective) move in the last turn, and picks a pseudorandom one using a better version of HighlyRadioactive's PRNG. It ensures these values are within a certain range (which happens about half the time), so it doesn't do anything stupid.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Fast and not steady

{
    "Fast and not steady": function() {
        return 99999999;
    }
}
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Faster than Slow

{
    "Faster than Slow": function() {
        return 2;
    }
}

If you think this is a bad bot, then no.

Faster than Slow 48.951

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ This scored surprisingly well... \$\endgroup\$ – null Aug 28 at 14:00
  • \$\begingroup\$ Well, not that surprising: 200/2**2 = 50 = 100/2, so 2 is pretty optimal. \$\endgroup\$ – alex berne Aug 29 at 10:03
  • \$\begingroup\$ @alexberne Aha! That explains it. Thanks! \$\endgroup\$ – null Aug 29 at 13:24
1
\$\begingroup\$

Whole

Whole doesn't like fractional distances and will always move a distance that is an integer.

    "Whole": function(dist, energy, bots, storage) {
        if (dist == 0) {
            if (energy < 110) {
                storage.lambda = function(distance) {return 100 - distance - 1;}
                storage.step = 1
            }
            else {
                storage.lambda = function(distance) {return 200 - distance - 2;}
                storage.step = 2
            }
        }
        let expEnergyPast = storage.lambda(dist);
        if (expEnergyPast + (storage.step + 1) ** 2 <= energy) {
            return storage.step + 1;
        }
        return storage.step;
    }

```
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Fourty-Nine

Fourty-Nine did take a look at Winner & Winner2 and recognized that 49 turns to win is better than 48 turns to win. But Fourty-Nine wants to win according to your rules. So Fourty-Nine doesn't sacrifice its averange distance to win many races. But it will never go faster than 49 turns to win.

    "fourty-nine": function(dist, energy, bots, storage) {
        if (dist == 0) {
            if (energy < 110) {
                storage.step = 1
            }
            else if(energy < 10000.0/49){
                storage.step = 2
            }
            else {
                storage.step = 100.0/49
            }
        }
        return storage.step;
    },
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Predictor

{
    "Predictor": function(dist, energy, bots, storage) {
        if (!dist)
            if (energy == 100)
                storage.move = 1;
            else
                storage.move = (energy >= 10000 / (50 - bots.length * 0.25 | 0)) ? (100 / (50 - bots.length * 0.25 | 0)) : 1.3;

        return storage.move;
    }
}

Predictor assumes that the more bots are added, the faster it needs to go to win. It collects energy over time, then sprints toward the finish line in a manner similar to Collector or Jack in the box.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

DECISION3M8

Improvement on UWUTM8 that works kind of differently

Tries to predict when someone is speeding and tries to use more energy to gain more points

"DECISION3M8": function(dist, energy, bots, storage) {
    const checkpointPer = 5;
    if (storage.turn == undefined) {
        storage.turn = 0;
    } else {
        storage.turn = storage.turn + 1;
    }
    
    if (dist === 0) {
        if (storage.round == undefined) {
            storage.round = 0;
        }
        storage.round = storage.round + 1;
        storage.turn = 0;
        storage.maxAtPreviouscheckpoint = 0;
        storage.predictedTurnsLeft = 100;
        storage.travelSpeed = Math.sqrt(energy / 50);
        
        if (energy == 100) {
            return 1;
        }
    } else if (storage.turn % checkpointPer == 0) {
        let maxAtCurrentTurn = Math.max( ...bots );
        let speederCheck = maxAtCurrentTurn / (storage.turn / checkpointPer) - storage.maxAtPreviouscheckpoint / ((storage.turn / checkpointPer) - 1);
        let speedOfSpeederPerTurn = maxAtCurrentTurn / storage.turn;
        if ((Math.abs(speederCheck) < 0.01) && (maxAtCurrentTurn > dist)) {
            //console.log(speederCheck);
            storage.predictedTurnsLeft = Math.ceil(100 / speedOfSpeederPerTurn) - (100 - storage.turn);
            storage.travelSpeed = Math.sqrt(energy / (storage.turn - speedOfSpeederPerTurn));
            //console.log(storage.predictedTurnsLeft);
        }
    }
    
    return storage.travelSpeed;
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Third place. Interesting idea! \$\endgroup\$ – Redwolf Programs Sep 4 at 13:37
0
\$\begingroup\$

Winner

Winner doesn't care about your rules. Winner plays by its own rules.

Winner tries to win ( = end at distance 100) in as many races as possible.

"Winner": function(dist, energy, bots, storage) {
        if (dist == 0) {
            if (energy < 10000.0/49) {
                storage.step= 0.5;
            }
            else {
                storage.step = 100.0/49;
            }
        }
        return storage.step;
    },
"Winner2": function(dist, energy, bots, storage) {
        if (dist == 0) {
            if (energy < 10000.0/48) {
                storage.step= 0.5;
            }
            else {
                storage.step = 100.0/48;
            }
        }
        return storage.step;
    },

```
| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

UWUTM8

Just like many bots i tries to finish as fast as it can by using as mutch energy as it can. It also doesn't try to finish at all at specific marks but tries to reach 9 point mark

"UWUTM8": function(dist, energy, bots, storage) {
    if (dist === 0) {
        if (storage.round == undefined) {
            storage.round = 0;
        }
        storage.round = storage.round + 1;
        if (storage.round % 2500 == 0 || storage.round == 250 || storage.round == 750) {
            storage.travelSpeed = Math.sqrt(energy / 90)
        } else {
            storage.travelSpeed = Math.sqrt(energy / 100)
        }
    }

    return storage.travelSpeed;
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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