A game of dice, but avoid number 6

NeoBot

Instead, only try to realize the truth - there is no spoon

NeoBot peeks into the matrix (aka random) and predicts if the next roll will be a 6 or not - it can't do anything about being handed a 6 to start with but is more than happy to dodge a streak ender.

NeoBot doesn't actually modify the controller or runtime, just politely asks the library for more information.

class NeoBot(Bot):
    def __init__(self, index, end_score):
        self.random = None
        self.last_scores = None
        self.last_state = None
        super().__init__(index,end_score)

    def make_throw(self, scores, last_round):
        while True:
            if self.random is None:
                self.random = inspect.stack()[1][0].f_globals['random']
            tscores = scores[:self.index] + scores[self.index+1:]
            if self.last_scores != tscores:
                self.last_state = None
                self.last_scores = tscores
            future = self.predictnext_randint(self.random)
            if future == 6:
                yield False
            else:
                yield True

    def genrand_int32(self,base):
        base ^= (base >> 11)
        base ^= (base << 7) & 0x9d2c5680
        base ^= (base << 15) & 0xefc60000
        return base ^ (base >> 18)

    def predictnext_randint(self,cls):
        if self.last_state is None:
            self.last_state = list(cls.getstate()[1])
        ind = self.last_state[-1]
        width = 6
        res = width + 1
        while res >= width:
            y = self.last_state[ind]
            r = self.genrand_int32(y)
            res = r >> 29
            ind += 1
            self.last_state[-1] = (self.last_state[-1] + 1) % (len(self.last_state))
        return 1 + res

Cooperative Swarm

Strategy

I don't think anyone else has yet noticed the significance of this rule:

If the game goes to 200 rounds, the bot (or bots) with the highest score is the winner, even if they do not have 40 points or more.

If every bot always rolled until they busted, then everyone would have a score of zero at the end of round 200 and everybody would win! Thus, the Cooperative Swarm's strategy is to cooperate as long as all players have a score of zero, but to play normally if anybody scores any points.

In this post, I am submitting two bots: the first is CooperativeSwarmBot, and the second is CooperativeThrowTwice. CooperativeSwarmBot serves as a base class for all bots that are formally part of the cooperative swarm, and has placeholder behavior of simply accepting its first successful roll when cooperation fails. CooperativeSwarmBot has CooperativeSwarmBot as its parent and is identical to it in every way except that its non-cooperative behavior is to make two rolls instead of one. In the next few days I will be revising this post to add new bots that use much more intelligent behavior playing against non-cooperative bots.

Code

class CooperativeSwarmBot(Bot):
    def defection_strategy(self, scores, last_round):
        yield False

    def make_throw(self, scores, last_round):
        cooperate = max(scores) == 0
        if (cooperate):
            while True:
                yield True
        else:
            yield from self.defection_strategy(scores, last_round)

class CooperativeThrowTwice(CooperativeSwarmBot):
    def defection_strategy(self, scores, last_round):
        yield True
        yield False

Analysis

Viability

It is very hard to cooperate in this game because we need the support of all eight players for it to work. Since each bot class is limited to one instance per game, this is a hard goal to achieve. For example, the odds of choosing eight cooperative bots from a pool of 100 cooperative bots and 30 non-cooperative bots is:

$$\frac{100}{130} * \frac{99}{129} * \frac{98}{128} * \frac{97}{127} * \frac{96}{126} * \frac{95}{125} * \frac{94}{124} * \frac{93}{123} \approx 0.115$$

More generally, the odds of choosing \$i\$ cooperative bots from a pool of \$c\$ cooperative bots and \$n\$ noncooperative bots is:

$$\frac{c! \div (c - i)!}{(c+n)! \div (c + n - i)!}$$

From this equation we can easily show that we would need about 430 cooperative bots in order for 50% of games to end cooperatively, or about 2900 bots for 90% (using \$i = 8\$ as per the rules, and \$n = 38\$).

Case Study

For a number of reasons (see footnotes 1 and 2), a proper cooperative swarm will never compete in the official games. As such, I'll be summarizing the results of one of my own simulations in this section.

This simulation ran 10000 games using the 38 other bots that had been posted here the last time I checked and 2900 bots that had CooperativeSwarmBot as their parent class. The controller reported that 9051 of the 10000 games (90.51%) ended at 200 rounds, which is quite close to the prediction that 90% of games would be cooperative. The implementation of these bots was trivial; other than CooperativeSwarmBot they all took this form:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Less that 3% of the bots had a win percentage that was below 80%, and just over 11% of the bots won every single game they played. The median win percentage of the 2900 bots in the swarm is about 86%, which is outrageously good. For comparison, the top performers on the current official leaderboard win less than 22% of their games. I can't fit the full listing of the cooperative swarm within the maximum allowed length for an answer, so if you want to view that you'll have to go here instead: https://pastebin.com/3Zc8m1Ex

Since each bot played in an average of about 27 games, luck plays a relatively large roll when you look at the results for individual bots. As I have not yet implemented an advanced strategy for non-cooperative games, most other bots benefited drastically from playing against the cooperative swarm, performing even the cooperative swarm's median win rate of 86%.

The full results for bots that aren't in the swarm are listed below; there are two bots whose results I think deserve particular attention. First, StopBot failed to win any games at all. This is particularly tragic because the cooperative swarm was actually using the exact same strategy as StopBot was; you would have expected StopBot to win an eight of its games by chance, and a little bit more because the cooperative swarm is forced to give its opponents the first move. The second interesting result, however, is that PointsAreForNerdsBot's hard work finally paid off: it cooperated with the swarm and managed to win every single game it played!

+---------------------+----+--------+--------+------+------+-------+------+--------+
|Bot                  |Win%|    Wins|  Played|   Max|   Avg|Avg win|Throws|Success%|
+---------------------+----+--------+--------+------+------+-------+------+--------+
|AggressiveStalker    |100.0|      21|      21|    42| 40.71|  40.71|  3.48|   46.32|
|PointsAreForNerdsBot |100.0|      31|      31|     0|  0.00|   0.00|  6.02|    0.00|
|TakeFive             |100.0|      18|      18|    44| 41.94|  41.94|  2.61|   50.93|
|Hesitate             |100.0|      26|      26|    44| 41.27|  41.27|  3.32|   41.89|
|Crush                |100.0|      34|      34|    44| 41.15|  41.15|  5.38|    6.73|
|StepBot              |97.0|      32|      33|    46| 41.15|  42.44|  4.51|   24.54|
|LastRound            |96.8|      30|      31|    44| 40.32|  41.17|  3.54|   45.05|
|Chaser               |96.8|      30|      31|    47| 42.90|  44.33|  3.04|   52.16|
|GoHomeBot            |96.8|      30|      31|    44| 40.32|  41.67|  5.60|    9.71|
|Stalker              |96.4|      27|      28|    44| 41.18|  41.44|  2.88|   57.53|
|ClunkyChicken        |96.2|      25|      26|    44| 40.96|  41.88|  2.32|   61.23|
|AdaptiveRoller       |96.0|      24|      25|    44| 39.32|  40.96|  4.49|   27.43|
|GoTo20Bot            |95.5|      21|      22|    44| 40.36|  41.33|  4.60|   30.50|
|FortyTeen            |95.0|      19|      20|    48| 44.15|  45.68|  3.71|   43.97|
|BinaryBot            |94.3|      33|      35|    44| 41.29|  41.42|  2.87|   53.07|
|EnsureLead           |93.8|      15|      16|    55| 42.56|  42.60|  4.04|   26.61|
|Roll6Timesv2         |92.9|      26|      28|    45| 40.71|  42.27|  4.07|   29.63|
|BringMyOwn_dice      |92.1|      35|      38|    44| 40.32|  41.17|  4.09|   28.40|
|LizduadacBot         |92.0|      23|      25|    54| 47.32|  51.43|  5.70|    5.18|
|FooBot               |91.7|      22|      24|    44| 39.67|  41.45|  3.68|   51.80|
|Alpha                |91.7|      33|      36|    48| 38.89|  42.42|  2.16|   65.34|
|QuotaBot             |90.5|      19|      21|    53| 38.38|  42.42|  3.88|   24.65|
|GoBigEarly           |88.5|      23|      26|    47| 41.35|  42.87|  3.33|   46.38|
|ExpectationsBot      |88.0|      22|      25|    44| 39.08|  41.55|  3.57|   45.34|
|LeadBy5Bot           |87.5|      21|      24|    50| 37.46|  42.81|  2.20|   63.88|
|GamblersFallacy      |86.4|      19|      22|    44| 41.32|  41.58|  2.05|   63.11|
|BePrepared           |86.4|      19|      22|    59| 39.59|  44.79|  3.81|   35.96|
|RollForLuckBot       |85.7|      18|      21|    54| 41.95|  47.67|  4.68|   25.29|
|OneStepAheadBot      |84.6|      22|      26|    50| 41.35|  46.00|  3.34|   42.97|
|FlipCoinRollDice     |78.3|      18|      23|    51| 37.61|  44.72|  1.67|   75.42|
|BlessRNG             |77.8|      28|      36|    47| 40.69|  41.89|  1.43|   83.66|
|FutureBot            |77.4|      24|      31|    49| 40.16|  44.38|  2.41|   63.99|
|SlowStart            |68.4|      26|      38|    57| 38.53|  45.31|  1.99|   66.15|
|NotTooFarBehindBot   |66.7|      20|      30|    50| 37.27|  42.00|  1.29|   77.61|
|ThrowThriceBot       |63.0|      17|      27|    51| 39.63|  44.76|  2.50|   55.67|
|OneInFiveBot         |58.3|      14|      24|    54| 33.54|  44.86|  2.91|   50.19|
|MatchLeaderBot       |48.1|      13|      27|    49| 40.15|  44.15|  1.22|   82.26|
|StopBot              | 0.0|       0|      27|    43| 30.26|   0.00|  1.00|   82.77|
+---------------------+----+--------+--------+------+------+-------+------+--------+

Flaws

There are a couple of drawbacks to this cooperative approach. First, when playing against non-cooperative bots cooperative bots never get the first-turn advantage because when they do play first, they don't yet know whether or not their opponents are willing to cooperate, and thus have no choice but to get a score of zero. Similarly, this cooperative strategy is extremely vulnerable to exploitation by malicious bots; for instance, during cooperative play the bot who plays last in the last round can choose to stop rolling immediately to make everybody else lose (assuming, of course, that their first roll wasn't a six).

By cooperating, all bots can achieve the optimal solution of a 100% win rate. As such, if the win rate was the only thing that mattered then cooperation would be a stable equilibrium and there would be nothing to worry about. However, some bots might prioritize other goals, such as reaching the top of the leaderboard. This means that there is a risk that another bot might defect after your last turn, which creates an incentive for you to defect first. Because the setup of this competition doesn't allow us to see what our opponents did in their prior games, we can't penalize individuals that defected. Thus, cooperation is ultimately an unstable equilibrium doomed for failure.

Footnotes

[1]: The primary reasons why I don't want to submit thousands of bots instead of just two are that doing so would slow the simulation by a factor on the order of 1000 [2], and that doing so would significantly mess with win percentages as other bots would almost exclusively be playing against the swarm rather than each other. More important, however, is the fact that even if I wanted to I wouldn't be able to make that many bots in a reasonable time frame without breaking the spirit of the rule that "A bot must not implement the exact same strategy as an existing one, intentionally or accidentally".

[2]: I think there are two main reasons that the simulation slows down when running a cooperative swarm. First, more bots means more games if you want each bot to play in the same number of games (in the case study, the number of games would differ by a factor of about 77). Second, cooperative games just take longer because they last for a full 200 rounds, and within a round players have to keep rolling indefinitely. For my setup, games took about 40 times longer to simulate: the case study took a little over three minutes to run 10000 games, but after removing the cooperative swarm it would finish 10000 games in just 4.5 seconds. Between these two reasons, I estimate it would take about 3100 times longer to accurately measure the performance of bots when there is a swarm competing compared to when there isn't.


Adaptive Roller

Starts out more aggressive and calms down towards the end of the round.
If it believes it's winning, roll an extra time for safety.

class AdaptiveRoller(Bot):

    def make_throw(self, scores, last_round):
        lim = min(self.end_score - scores[self.index], 22)
        while sum(self.current_throws) < lim:
            yield True
        if max(scores) == scores[self.index] and max(scores) >= self.end_score:
            yield True
        while last_round and scores[self.index] + sum(self.current_throws) <= max(scores):
            yield True
        yield False