Menor número único de KoTH

27

Crie um bot para escolher o menor número único.

(Com base em um experimento psicológico que ouvi há muitos anos atrás, mas não consegui encontrá-lo novamente.)

Regras

  • Cada jogo consistirá em 10 bots selecionados aleatoriamente, jogando 1000 rodadas.
  • A cada rodada, todos os bots selecionam um número inteiro de 1 a 10 (inclusive). Quaisquer bots que escolherem o mesmo valor serão excluídos e o bot restante com o menor valor receberá um ponto.
  • No caso de nenhum bot escolher um valor único, nenhum ponto será concedido.
  • No final de 1000 rodadas, o bot com mais pontos (ou todos os bots empatados com mais pontos) vence o jogo.
  • O torneio dura 200 * jogos (número de jogadores).
  • O bot com a maior porcentagem de vitórias vence o torneio.

Especificações

Bots devem ser classes Python 3 e devem implementar dois métodos: selecte update.
Bots serão construídos com um índice.
selecté passado sem argumentos e retorna a escolha do bot para a rodada atual.
updateé aprovada uma lista das escolhas feitas por cada bot na rodada anterior.

Exemplo

class Lowball(object):
    def __init__(self, index):
        # Initial setup happens here.
        self.index = index
    def select(self):
        # Decision-making happens here.
        return 1
    def update(self, choices):
        # Learning about opponents happens here.
        # Note that choices[self.index] will be this bot's choice.
        pass

Controlador

import numpy as np

from bots import allBotConstructors
allIndices = range(len(allBotConstructors))
games = {i: 0 for i in allIndices}
wins = {i: 0 for i in allIndices}

for _ in range(200 * len(allBotConstructors)):
    # Choose players.
    playerIndices = np.random.choice(allIndices, 10, replace=False)
    players = [allBotConstructors[j](i) for i, j in enumerate(playerIndices)]

    scores = [0] * 10
    for _ in range(1000):
        # Let everyone choose a value.
        choices = [bot.select() for bot in players]
        for bot in players:
            bot.update(choices[:])

        # Find who picked the best.
        unique = [x for x in choices if choices.count(x) == 1]
        if unique:
            scores[choices.index(min(unique))] += 1

    # Update stats.
    for i in playerIndices:
        games[i] += 1
    bestScore = max(scores)
    for i, s in enumerate(scores):
        if s == bestScore:
            wins[playerIndices[i]] += 1

winRates = {i: wins[i] / games[i] for i in allIndices}
for i in sorted(winRates, key=lambda i: winRates[i], reverse=True):
    print('{:>40}: {:.4f} ({}/{})'.format(allBotConstructors[i], winRates[i], wins[i], games[i]))

Informação adicional

  • Nenhum bot jogará um jogo contra si mesmo.
  • No caso improvável de um bot ser incluído em menos de 100 jogos, o torneio será executado novamente.
  • Os bots podem armazenar o estado entre as rodadas, mas não entre os jogos.
  • Não é permitido acessar o controlador ou outros bots.
  • O número de jogos e o número de rodadas por jogo estão sujeitos a aumentar se os resultados forem muito variáveis.
  • Quaisquer bots que geram erros ou dão respostas inválidas (não-entradas, valores fora de [1, 10] etc.) serão desqualificados e o torneio será executado novamente sem eles.
  • Não há limite de tempo para as rodadas, mas posso implementar uma se os robôs demorarem demais para pensar.
  • Não há limite para o número de envios por usuário.
  • O prazo para inscrições é 23:59:59 UTC na sexta-feira, 28 de setembro. O torneio está encerrado para inscrições.

Resultados

                BayesBot: 0.3998 (796/1991)
      WhoopDiScoopDiPoop: 0.3913 (752/1922)
           PoopDiScoopty: 0.3216 (649/2018)
                   Water: 0.3213 (660/2054)
                 Lowball: 0.2743 (564/2056)
                Saboteur: 0.2730 (553/2026)
                OneUpper: 0.2640 (532/2015)
         StupidGreedyOne: 0.2610 (516/1977)
          SecondSaboteur: 0.2492 (492/1974)
                    T42T: 0.2407 (488/2027)
                     T4T: 0.2368 (476/2010)
          OpportunityBot: 0.2322 (454/1955)
              TheGeneral: 0.1932 (374/1936)
             FindRepeats: 0.1433 (280/1954)
                  MinWin: 0.1398 (283/2025)
             LazyStalker: 0.1130 (226/2000)
               FollowBot: 0.1112 (229/2060)
                Assassin: 0.1096 (219/1999)
           MostlyAverage: 0.0958 (194/2024)
             UnchosenBot: 0.0890 (174/1955)
                 Raccoon: 0.0868 (175/2015)
               Equalizer: 0.0831 (166/1997)
       AvoidConstantBots: 0.0798 (158/1980)
WeightedPreviousUnchosen: 0.0599 (122/2038)
               BitterBot: 0.0581 (116/1996)
               Profiteur: 0.0564 (114/2023)
              HistoryBot: 0.0425 (84/1978)
            ThreeFourSix: 0.0328 (65/1984)
                 Stalker: 0.0306 (61/1994)
             Psychadelic: 0.0278 (54/1943)
              Unpopulist: 0.0186 (37/1994)
             PoissonsBot: 0.0177 (35/1978)
         RaccoonTriangle: 0.0168 (33/1964)
              LowHalfRNG: 0.0134 (27/2022)
              VictoryPM1: 0.0109 (22/2016)
            TimeWeighted: 0.0079 (16/2021)
             TotallyLost: 0.0077 (15/1945)
            OneTrackMind: 0.0065 (13/1985)
              LuckySeven: 0.0053 (11/2063)
          FinalCountdown: 0.0045 (9/2000)
                Triangle: 0.0039 (8/2052)
           LeastFrequent: 0.0019 (4/2067)
                Fountain: 0.0015 (3/1951)
             PlayerCycle: 0.0015 (3/1995)
                  Cycler: 0.0010 (2/1986)
               SecureRNG: 0.0010 (2/2032)
             SneakyNiner: 0.0005 (1/2030)
            I_Like_Nines: 0.0000 (0/1973)

fonte
2
@Mnemonic Alguma novidade?
user1502040
4
@Herohtar Coloquei-o em execução antes de sair para o trabalho. Com alguma sorte, isso deve ser feito quando eu chegar em casa.
1
@Mnemonic Já terminou?
user1502040
2
@ Justin Está funcionando agora e não parece estar travando, mas eu definitivamente não me importaria com a ajuda se essa execução falhar.
1
@MihailMalostanidis Crie um arquivo chamado bots.pyno mesmo diretório que contém todos os bots. No final, crie uma lista dos construtores:allBotConstructors = [Lowball, BayesBot, ...]

Respostas:

10

BayesBot

Tenta fazer a escolha ideal usando um modelo estatístico simples.

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
user1502040
fonte
10

Evite robôs constantes

Acompanhe quais robôs sempre retornaram o mesmo valor e pule esses valores. Dos valores restantes, selecione-os aleatoriamente, mas incline significativamente para valores mais baixos.

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
Restabelecer Monica
fonte
10

WaitWhatBot

Não é o bot mais competitivo e, definitivamente, não o GTO , mas diminui a pontuação de qualquer oponente "sempre 1" ou "quase sempre 1" no mesmo jogo que em um cenário em que o WaitWhatBot também se torna um bot.

Utiliza probabilidades em evolução com pesos ponderados, tanto no tempo (mais recente -> maior peso) quanto no valor de escolha (ponto mais baixo -> maior peso).

Usa código um tanto ofuscado para dar uma risadinha.

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
Jonathan Allan
fonte
9
Quanto peso o WaitWhatBot comprou, se o WaitWhatBot comprasse apenas peso?
Roman Odaisky
set ([... for ... in ...]) ≡ {... for ... in ...}, por sinal
Roman Odaisky
@RomanOdaisky Na verdade, eu aconselhei alguém disso outro dia para jogar golfe!
Jonathan Allan
5

Perseguidor

No início do jogo, esse bot escolhe aleatoriamente um índice específico como alvo. Em seguida, persegue o alvo por todo o jogo, copiando o número que ele escolheu na rodada anterior.

import random

class Stalker(object):
  def __init__(self, index):
    # choose a random target to stalk that isn't ourself
    self.targetIndex = random.choice([x for x in range(10) if x != index])
    # get a random number to start with since we haven't seen our target's value yet
    self.targetValue = random.randint(1, 10)
  def select(self):
    return self.targetValue
  def update(self, choices):
    # look at what our target chose last time and do that
    self.targetValue = choices[self.targetIndex]
Herohtar
fonte
4

Estúpido ganancioso

class StupidGreedyOne(object):
    def __init__(self, index):
        pass
    def select(self):
        return 1
    def update(self, choices):
        pass

Este bot assume que outros bots não querem amarrar.

Sei que é o mesmo que o exemplo fornecido, mas tive o pensamento antes de ler isso até agora. Se isso for incongruente com a forma como os desafios do KoTH são executados, me avise.

Engenheiro Toast
fonte
Em geral, prefiro não ter bots duplicados, mas não me importo de deixá-lo.
1
@Mnemonic bem tecnicamente, não é um tolo, pois não inicializa self.index.
Hidefromkgb 14/09
@Mnemonic Sem problemas! Honestamente, este é o meu primeiro KoTH e o meu primeiro em Python, então apenas segui os dois primeiros pôsteres e não o mudei, apesar da minha suspeita de que eu devesse ter. Eu também não tinha certeza se você incluiria o Lowball em seus testes ou se realmente era apenas um exemplo para o post.
Engenheiro brinde
Não se preocupe. Bem-vindo ao maravilhoso mundo do KoTH!
2
Você exibe uma "granada de ace": puzzling.stackexchange.com/questions/45299/…
kaine
4

HistoryBot

import random

class HistoryBot(object):
    def __init__(self, index):
        self.pastWins = []
    def select(self):
        if not self.pastWins:
            return 1
        return random.choice(self.pastWins)
    def update(self, choices):
        unique = [x for x in choices if choices.count(x) == 1]
        if unique:
            self.pastWins.append(min(unique))

Implementação do comentário de user2390246:

Que tal isso então? Comece com 1. Após a primeira rodada, acompanhe os valores vencedores e escolha aleatoriamente entre eles com probabilidade igual ao número de ocorrências. Por exemplo, se os valores vencedores nas três primeiras rodadas são [2, 3, 2] e na quarta rodada, escolha [2] com p = 2/3 e [3] com p = 1/3.

user48543
fonte
4

OneUpper

class OneUpper(object):
    def __init__(self, index):
        self.index = index
    def select(self):
        return 2
    def update(self, choices):
        pass

Os bots de todos os outros estão apontando para 1 ou aleatoriamente, então por que não apenas apontar para 2?

Zackary Murphy
fonte
4

Fluir como água

Evita algoritmos básicos de detecção constante de bots, duplicando cada número, avançando lentamente em direção a valores mais baixos se não estiverem ocupados.

class Water(object):
    def __init__(self, index):
        self.index = index
        self.round = 0
        self.play = 4
        self.choices = [0]*10

    def select(self):
        if self.round > 0 and self.round%2 == 0:
            if not max([1, self.play - 1]) in self.choices:
                self.play -= 1
        return self.play

    def update(self, choices):
        self.round += 1
        self.choices = choices
TCFP
fonte
Estou curioso, o seu bot está de alguma forma relacionado à minha fonte ? Ambos são "orientados para a água", haha.
RedClover 14/09/18
Honestamente, meu plano inicial era criar um bot de palpite fixo que duvidava de certos números, que era a minha motivação para o processo de tomada de decisão do bot. Quando eu o visualizei, estava pensando em um fluxo lento, que inspirou o nome. Gritar para o tema da água embora :)
TCFP
Portanto, isso está ficando em 3º ou 4º (geralmente 3º) em todos os testes que executo. Isso é incrível para uma estratégia tão simples.
Robert Fraser
4

Totalmente perdido

class TotallyLost(object):
    def __init__(self, index):
        self.index = index
        self.round = 0
        self.numbers = [4,8,1,5,1,6,2,3,4,2]
    def select(self):
        return self.numbers[self.round % len(self.numbers)]
    def update(self, choices):
        self.round = self.round + 1
Robert Fraser
fonte
4

A contagem regressiva final

class FinalCountdown(object):
    def __init__(self, index):
        self.round = -1
    def select(self):
        self.round += 1
        return (10 - self.round // 100)
    def update(self, choices):
        pass

Experimente online!

Retorna 10 para as primeiras 100 rodadas, 9 para as próximas 100 e assim por diante.

Laikoni
fonte
4

Opportunitybot

Este bot mantém o controle do número mais baixo não escolhido por outros bots a cada rodada (o menor número disponível ou oportunidade) e reproduz o número que tem sido esse número com mais frequência.

class OpportunityBot(object):
    def __init__(self, index):
        self.index = index
        self.winOccasions = [0,0,0,0,0,0,0,0,0,0]

    def select(self):
        return self.winOccasions.index(max(self.winOccasions))+1

    def update(self, choices):
        choices.pop(self.index)
        succeeded = [choices.count(i)==0 for i in range(1,11)]
        self.winOccasions[succeeded.index(True)] += 1
Mattermonkey
fonte
4

PatterMatcher

Procura seções repetidas nos envios dos bots, tenta prever e evitar os números.

class PatternMatcher(object):
    def __init__(self, index):
        self.bots=[[]]*9
        self.index=index
    def select(self):
        minVisible=3    #increase these if this bot is to slow
        minOccurences=2
        predictions=set()
        for bot in self.bots:     
            #match patters of the form A+(B+C)*minOccurences+B and use C[0] as a prediction      
            for lenB in range(minVisible,len(bot)//(minVisible+1)+1):
                subBot=bot[:-lenB]
                patterns=[] 
                for lenBC in range(lenB,len(subBot)//minOccurences+1):
                    BC=subBot[-lenBC:]
                    for i in range(1,minOccurences):
                        if BC!=subBot[-lenBC*i-lenBC:-lenBC*i]:
                            break
                    else:
                        patterns.append(BC)
                predictions|={pattern[lenB%len(pattern)] for pattern in patterns}
        other=set(range(1,11))-predictions
        if other: return min(other)
        else: return 1                

    def update(self, choices):
        j = 0
        for i,choice in enumerate(choices):
            if i == self.index:
                continue
            self.bots[j].append(choice)
            j += 1

Triângulo

A chance de escolher n é (10-n)/45

import random
class Triangle(object):
    def __init__(self, index):pass
    def select(self):return random.choice([x for x in range(1, 11) for _ in range(10 - x)])
    def update(self, choices):pass

TimeWeighted

A probabilidade de um bot escolher um número é proporcional a (10-n)*Δt. A primeira rodada é idêntica ao triângulo.

import random
class TimeWeighted(object):
    def __init__(self, index):
        self.last=[0]*10
        self.round=1 
    def select(self):
        weights=[(self.round-self.last[i])*(10-i) for i in range(10)]
        return 1+random.choice([x for x in range(10) for _ in range(weights[x])])

    def update(self, choices):
        for c in choices:
            self.last[c-1]=self.round
        self.round+=1

Menos frequente

Envia o número que ocorre com menos frequência, se for igual, pega o número mais baixo.

class LeastFrequent(object):
    def __init__(self, index):self.frequenties=[0]*10
    def select(self):return 1+self.frequenties.index(min(self.frequenties))
    def update(self, choices):
        for c in choices:
            self.frequenties[c-1]+=1

Tempo mais longo

O mesmo que com as freqüências, mas com o maior tempo entre as apresentações.

class LongestTime(object):
    def __init__(self, index):
        self.frequencies=[0]*10
        self.round=1
    def select(self):return 1+self.frequencies.index(min(self.frequencies))
    def update(self, choices):
        for c in choices:
            self.frequencies[c-1]=self.round
        self.round+=1

Sabotador

Envia o número mais baixo que foi enviado da última vez.

class Saboteur(object):
    def __init__(self, index):self.last=[1]
    def select(self):return min(self.last)
    def update(self, choices):self.last=choices

SecondSaboteur

Envia o segundo número mais baixo enviado pela última vez

class SecondSaboteur(object):
    def __init__(self, index):self.last=[1,2]
    def select(self):return min({i for i in self.last if i!=min(self.last)})
    def update(self, choices):self.last=choices

Profiteur

Envia o número mais baixo não enviado da última vez

class Profiteur(object):
    def __init__(self, index):self.last=set()
    def select(self):return min(set(range(1, 11))-self.last, default=1)
    def update(self, choices):self.last=set(choices)

Desculpe, fiquei um pouco empolgado com a ideia de novos bots ao implementar o anterior uma vez. Eu não tinha certeza de qual seria o melhor e estou curioso sobre o desempenho de cada um deles. Você pode encontrá-los todos aqui: https://repl.it/@Fejfo/Lowest-Unique-Number

fejfo
fonte
Agradável. Você pode modificar o Saboteur para ignorar sua última opção (a menos que seja intencional). Além disso, acho que você pode precisar lidar com alguns casos especiais: o que o SecondSaboteur deve fazer se todo bot escolher o mesmo valor em alguma rodada e o que o Profiteur deve fazer se todo bot escolher um valor diferente? Você pode precisar de um parêntese final no Profiteur depois set(range(10).
Restabeleça Monica
O PatternMatcher parece ter algum tipo de loop infinito ou local onde fica preso.
Robert Fraser
3

O melhor bot de 50% de RNG

import random

class LowHalfRNG(object):
    def __init__(self, index):
        pass
    def select(self):
        return random.randint(1, 5)
    def update(self, choices):
        pass

Eu estava prestes a postar um bot aleatório, mas hidefromkgb postou antes de mim (postando eles estão se tornando um alvo fácil para a KGB, não uma boa maneira de se esconder). Esta é a minha primeira resposta KOTH, apenas esperando vencer o bot rng.

maxb
fonte
3

The Cycler

Este bot simplesmente percorre cada um dos números em seus turnos. Apenas por diversão, ele inicializa o contador com seu índice.

class Cycler(object):
  def __init__(self, index):
    self.counter = index # Start the count at our index
  def select(self):
    return self.counter + 1 # Add 1 since we need a number between 1-10
  def update(self, choices):
    self.counter = (self.counter + 1) % 10
Herohtar
fonte
3

OneTrackMind

Esse bot escolhe aleatoriamente um número e fica com ele por 50 rodadas, depois escolhe outro e repete.

import random

class OneTrackMind(object):
    def __init__(self, index):
        self.round = 0;
        self.target = random.randint(1,10)
    def select(self):
        return self.target
    def update(self, choices):
        self.round += 1;
        if self.round % 50 == 0:
            self.target = random.randint(1,10)
Jo.
fonte
3

Sete da sorte

class LuckySeven(object):
    def __init__(self, index):
        pass
    def select(self):
        return 7
    def update(self, choices):
        pass

Estou com sorte hoje! Estou jogando tudo fora no 7!

AlexRacer
fonte
3

Minha ideia é que a estratégia depende mais do número de bots do que da avaliação real das estratégias.

Com um número significativo de bots, as opções são:

  • Robôs "gananciosos", com o objetivo de fazer com que os números 1-3 abaixo de 10 bots sejam "inteligentes" e com o objetivo de obter os números 1-3 inferiores, o melhor é permitir que esses robôs interfiram entre eles.

  • Robôs "inteligentes" que, quando percebem que o 4 é sempre escolhido, vão para outro lugar.

  • Robôs "aleatórios" e "constantes". Não há muito o que fazer aqui.

Então, eu aposto no # 4.

class LazyStalker(object):
    def __init__(self, index):
        pass
    def select(self):
        return 4
    def update(self, choices):
        pass
SJuan76
fonte
2

O bot essencial do RNG

import secrets

class SecureRNG(object):
    def __init__(self, index):
        pass
    def select(self):
        return secrets.randbelow(10) + 1
    def update(self, choices):
        pass
hidefromkgb
fonte
2

Assassino

Fica nas sombras e depois aponta para o palpite mais baixo atual. Corre.

class Assassin(object):
    def __init__(self, index):
        self.index = index
        self.round = 0
        self.choices = [0]*10

    def select(self):
        if self.round == 0:
            return 10
        else:
            return min(self.choices)

    def update(self, choices):
        self.round += 1
        self.choices = choices
        self.choices[self.index] = 10
TCFP
fonte
2

FollowBot

Copie o vencedor da última rodada, ou pelo menos a melhor seleção minimamente empatada, se não houver vencedor.

import collections

class FollowBot(object):
    def __init__(self, index):
        self.lastround = []

    def select(self):
        counter = collections.Counter(self.lastround)
        counts = [(count,value) for (value,count) in counter.items()]
        counts.sort()
        if len(counts) >= 1:
            return counts[0][1]
        else:
            return 1

    def update(self, choices):
        self.lastround = choices
RM
fonte
2

Psicadélico

A única maneira de vencer uma guerra nuclear é ficar louco. Então, eu vou deixar todos os bot preditivos do torneio insanos.

class Psychadelic(object):
    def __init__(self, index):
        self.index = index
    def select(self):
        return random.randint(1, self.index + 1)
    def update(self, choices):
        pass
Joshua
fonte
2

UnchosenBot

class UnchosenBot(object):
    def __init__(self, index):
        self.index = index
        self.answer = 0
    def select(self):
        if self.answer == 0:
            return 1
        return self.answer
    def update(self, choices):
        self.answer = 0
        del choices[self.index]
        for x in range(1, 11):
            if x not in choices:
                self.answer = x
                return

Toma as escolhas da última rodada e escolhe o menor número não escolhido (ignorando a escolha de UnchosenBot, é claro).

Spitemaster
fonte
2

Whoop-di-scoop-di-cocô

class WhoopDiScoopDiPoop(object):
    def __init__(self, index):
        self.index = index
        self.guess = 1
        self.tenure = 0
        self.perseverance = 4

    def select(self):
        return self.guess

    def update(self, choices):
        others = {c for i, c in enumerate(choices) if i != self.index}
        for i in range(1, self.guess):
            if i not in others:
                self.guess = i
                self.tenure = 0
                self.perseverance += 1
                return
        if self.guess not in others:
            self.tenure = 0
            return
        self.tenure += 1
        if self.tenure > self.perseverance:
            if self.guess == 10:
                return
            self.guess += 1
            self.tenure = 0

Poop-di-scoopty

class PoopDiScoopty(object):
    def __init__(self, index):
        self.index = index
        self.guess = 1
        self.tenure = 0
        self.perseverance = 4

    def select(self):
        return self.guess

    def update(self, choices):
        others = [c for i, c in enumerate(choices) if i != self.index]
        for i in range(1, self.guess):
            if i not in others:
                self.guess = i
                self.tenure = 0
                self.perseverance += 1
                return
        if self.guess not in others:
            self.tenure = 0
            return
        self.tenure += others.count(self.guess) # this is the change
        if self.tenure > self.perseverance:
            if self.guess == 10:
                return
            self.guess += 1
            self.tenure = 0

Eu nunca vi ou toquei Python, isso é não-tônico?

Mihail Malostanidis
fonte
1
Adicione a linha <!-- language: lang-python -->antes do bloco de código para habilitar o realce da sintaxe
Herman L
@HermanL Alucinei uma pythonetiqueta na pergunta e pensei que seria automática, mas escrevi algo ruim.
Mihail Malostanidis
1
Quanto à pitonicidade, o código é bastante bom, exceto pelo fato de ser considerado pitonista others = [c for i, c in enumerate(choices) if i != self.index], ou porque, posteriormente, você só usa essa variável para testes de associação, em { }vez de [ ]construir um em setvez de um list.
Roman Odaisky
if (self.guess)também é muito antitônico.
Jonathan Frech
Eu não tenho idéia de como essas parênteses self.guesschegaram lá! Deve ter sido um dos formatadores.
Mihail Malostanidis
2

Fonte

Um bot simples escolhe primeiro o número mais baixo e, se outro bot o escolher, aumentará o contador - o piso fica cheio e a água flui. Quando atinge 11, reinicia para 1 - a água é bombeada de volta ao topo.

class Fountain:

    def __init__(self, index, target=10):

        # Set data
        self.index = index
        self.pick  = 1
        self.target = target+1

    def select(self):

        # Select the number
        return self.pick

    def update(self, choices: list):

        # Remove self from the list
        choices.pop(self.index)  # I hope `choices[:]` is passed, not `choices`.

        # While the selected number is occupied
        while self.pick in choices:

            # Pick next number
            self.pick += 1

            # If target was reached
            if self.pick == self.target:

                # Reset to 1
                self.pick = 1
Trevo vermelho
fonte
Na sua forma atual, seu bot ficará preso no loop while se os outros bots tiverem escolhido todos os números de 1 a 8. Você pretendia definir target10?
Emil
@Emil É verdade, era originalmente assim, mudou
RedClover
2

PoissonsBot

Selecione os números de uma distribuição de Poisson com tendência a valores mais baixos. Ajuste o parâmetro médio da distribuição para cima se estivermos empatados e se houver suposições abaixo de nós. O tamanho da etapa fica progressivamente menor à medida que o jogo avança.

from numpy.random import poisson
import math

class PoissonsBot(object):
    def __init__(self, index):
        self.index = index
        self.mean = 2
        self.roundsleft = 1000

    def select(self):
        self.roundsleft = max(self.roundsleft-1, 2)
        return max(min(poisson(self.mean),10),1)

    def update(self, choices):
        myval = choices[self.index]
        nequal = len([c for c in choices if c==myval])
        nless = len([c for c in choices if c<myval])
        step = math.log10(self.roundsleft)
        if nequal > 1:
            self.mean += nequal/step
        self.mean -= nless/step
        self.mean = max(self.mean, 0.3)
thegreatemu
fonte
2

MinWin

Mantém uma contagem contínua dos valores vencedores e dos valores mínimos não selecionados (onde o valor mínimo não selecionado é considerado apenas se for menor que o valor vencedor). Ele seleciona aleatoriamente entre esses valores mínimos e vencedores.

import random

class MinWin:

    def __init__(self, index):
        self.index = index
        self.mins = list(range(1, 11))
        self.wins = list(range(1, 11))

    def select(self):
        return min(random.choice(self.mins), random.choice(self.wins))

    def update(self, choices):
        counts = [0] * 10
        for x in choices:
            counts[x - 1] += 1

        if 0 in counts and (1 not in counts or counts.index(0) < counts.index(1)):
            self.mins.append(counts.index(0) + 1)
        if 1 in counts:
            self.wins.append(counts.index(1) + 1)
Curtis Bechtel
fonte
2

PlayerCycle

Percorre os jogadores. A escolha do jogador atual (pode ser eu mesmo) agora é a escolha deste bot. Começa a imprimir 8, porque porque não? Desculpe, não posso python, este provavelmente é um código incorreto.

import itertools
class PlayerCycle(object):
    def __init__(self, index):
        self.a = itertools.cycle(range(10))
        self.b = 8
    def select(self):
        return self.b
    def update(self, choices):
        self.b = choices[next(self.a)]

Edit: Obrigado à Triggernometry por melhorar meu código com itertools

ev3commander
fonte
Seu código funciona bem, mas você pode adicionar um intertools.cycle () para um para que ele passe automaticamente de 0 a 9 e você não precise fazer incrementos ou verificações - Experimente on-line!
Triggernometry
2

Guaxinim

Escolha o número mais baixo não escolhido na rodada anterior, exceto nossa própria escolha anterior, que poderá ser escolhida novamente desta vez. Na primeira rodada, escolha 1. (Dados 9 oponentes e 10 opções, é garantido que haja um valor disponível.)

Eu vim com isso de forma independente, mas agora vejo pelo menos 2 bots anteriores que são essencialmente os mesmos.

class Raccoon(object):
    def __init__(self, index):
        self.index = index
        self.last_round = None
        self.domain = None
    def select(self):
        # Return the lowest number not chosen last time.
        if self.domain is None:
            return 1
        else:
            # This finds the smallest element of domain, not present in last_round
            return min(self.domain-self.last_round)
    def update(self, choices):
        last_round = choices[:]
        last_round[self.index] = 0 # don't include our own choice
        self.last_round = set(last_round)
        if self.domain is None:
            self.domain = set(range(1,len(choices)+1))

Guaxinim Triângulo

Combina Guaxinim e Triângulo: entre os valores não escolhidos, escolha um com base na probabilidade do triângulo reverso.

import random
class RaccoonTriangle(object):
    def __init__(self, index):
        self.index = index
        self.unchosen = set([1,])
        self.domain = None
    def select(self):
        # Return the lowest number not chosen last time.
        if self.domain is None:
            return random.randint(1,self.index+1)
        else:
            # Reverse triangle weights for unchosen values
            weighted_choices = [u for i,u in enumerate(sorted(self.unchosen),0) for _ in range(len(self.unchosen)-i)]
            return random.choice(weighted_choices)
    def update(self, choices):
        last_round = choices[:] # make a copy
        last_round[self.index] = 0 # don't include our own choice
        if self.domain is None:
            self.domain = set(range(1,len(choices)+1))
        self.unchosen = self.domain - set(last_round)
Mecânico Quântico
fonte
Erro:AttributeError: 'RaccoonTriangle' object has no attribute 'boundaries'
Renzeee 25/09
1
Sim, desculpe. Eu acho que consertei. Eu estava no meio de testes, quando parei.
Quantum Mechanic
1

O General

O general sempre luta a última guerra (s) .

import numpy
import random

class TheGeneral:
    def __init__(self, index):
        self.round = 0
        self.index = index
        self.would_have_won = [0] * 10

    def select(self):
        if self.round <= 100:
            return random.choice((list(numpy.nonzero(self.would_have_won)[0]) + [0, 1])[:2]) + 1

        return random.choice(numpy.argsort(self.would_have_won)[-2:]) + 1

    def update(self, choices):
        for i, s in enumerate(numpy.bincount([c - 1 for i, c in enumerate(choices)
            if i != self.index], minlength=10)):

            if s == 0:
                self.would_have_won[i] += 1
            elif s == 1:
                break

        self.round += 1
Roman Odaisky
fonte
1

Aleatório sem repetição

import secrets

class NoRepeats(object):
    def __init__(self, index):
        self.lastround = secrets.randbelow(10) + 1

    def select(self):
        i = secrets.randbelow(10) + 1
        while i == self.lastround:
             i = secrets.randbelow(10) + 1
        self.lastround = i
        return self.lastround

    def update(self, choices):
        pass

Bot escolhe aleatoriamente, mas evita escolher o mesmo número da rodada anterior.

Draco18s
fonte