Smallest unique number KoTH

BayesBot

Tries to make the optimal choice using a simple statistical model.

import random

def dirichlet(counts):
    counts = [random.gammavariate(n, 1) for n in counts]
    k = 1. / sum(counts)
    return [n * k for n in counts]

class BayesBot(object):
    def __init__(self, index):
        self.index = index
        self.counts = [[0.2 * (10 - i) for i in range(10)] for _ in range(10)]
    def select(self):
        player_distributions = []
        for i, counts in enumerate(self.counts):
            if i == self.index:
                continue
            player_distributions.append(dirichlet(counts))
        cumulative_unique = 0.
        scores = [0.] * 10
        for i in range(10):
            p_unpicked = 1.
            for d in player_distributions:
                p_unpicked *= (1. - d[i])
            p_unique = p_unpicked * sum(d[i] / (1. - d[i]) for d in player_distributions)
            scores[i] = p_unpicked * (1. - cumulative_unique)
            cumulative_unique += p_unique * (1. - cumulative_unique)
        return scores.index(max(scores)) + 1
    def update(self, choices):
        for i, n in enumerate(choices):
            self.counts[i][n - 1] += 1

Avoid Constant Bots

Keep track of which bots have always returned the same value, and skip those values. Of the remaining values, select them randomly, but biased significantly towards lower values.

import numpy as np

class AvoidConstantBots(object):
    all_values = range(1, 11)
    def __init__(self, index):
        self.index = index
        self.constant_choices = None

    def select(self):
        available = set(self.all_values)
        if self.constant_choices is not None:
            available -= set(self.constant_choices)
        if len(available) == 0:
            available = set(self.all_values)
        values = np.array(sorted(available))
        weights = 1. / (np.arange(1, len(values) + 1)) ** 1.5
        weights /= sum(weights)
        return np.random.choice(sorted(available), p=weights)

    def update(self, choices):
        if self.constant_choices is None:
            self.constant_choices = choices[:]
            self.constant_choices[self.index] = None
        else:
            for i, choice in enumerate(choices):
                if self.constant_choices[i] != choice:
                    self.constant_choices[i] = None

WaitWhatBot

Not the most competitive bot and definitely not GTO, but will stifle the score of any "always 1" or "nearly always 1" opponent in the same game as in such a scenario WaitWhatBot becomes such a bot too.

Uses evolving probabilities with weighted weights both in time (more recent -> greater weight) and choice value (lower point -> greater weight).

Uses somewhat obfuscated code for a bit of a giggle.

from random import choices as weightWeight
class WaitWhatBot(object):
    def __init__(wait,what):
        weight,weightWhat=5,2
        wait.what,wait.weight=what,(weight**(weight/weight/weightWhat)+weightWhat/weightWhat)/weightWhat
        wait.whatWeight,wait.weightWeight=[wait.what==wait.weight]*int(wait.weight**weight),wait.weight
        wait.whatWhat=wait.whatWeight.pop()#wait, when we pop weight off whatWeight what weight will pop?
        wait.waitWait=tuple(zip(*enumerate(wait.whatWeight,wait.weightWeight!=wait.whatWeight)))[weightWeight==wait.weight]
    def select(what):return int(what.weight**what.whatWhat if all(not waitWait for waitWait in what.whatWeight)else weightWeight(what.waitWait,what.whatWeight)[what.weight==what.what])
    def update(waitWhat,whatWait):
        what,wait,weightWhat=set(wait for wait in whatWait[:waitWhat.what]+whatWait[waitWhat.what+1:]if wait in waitWhat.waitWait),-~waitWhat.whatWhat,waitWhat.weightWeight
        while wait not in what:
            waitWhat.whatWeight[wait+~waitWhat.whatWhat]+=weightWhat
            weightWhat/=waitWhat.weight
            wait-=~waitWhat.whatWhat
        if not wait!=(what!=weightWhat):waitWhat.whatWeight[waitWhat.whatWhat]+=weightWhat
        waitWhat.weightWeight*=waitWhat.weight