El número único más pequeño KoTH

27

Crea un bot para elegir el número único más pequeño.

(Basado en un experimento de psicología del que escuché hace muchos años, pero no he podido localizarlo nuevamente).

Reglas

  • Cada juego constará de 10 bots seleccionados al azar que juegan 1000 rondas.
  • Cada ronda, todos los bots seleccionan un número entero del 1 al 10 (inclusive). Cualquier bot que elija el mismo valor será excluido, y el bot restante con el valor más pequeño recibirá un punto.
  • En el caso de que ningún bot elija un valor único, no se otorgarán puntos.
  • Al final de 1000 rondas, el bot con más puntos (o todos los bots empatados con más puntos) gana el juego.
  • El torneo durará 200 * (número de jugadores) juegos.
  • El bot con el mayor porcentaje de victorias gana el torneo.

Presupuesto

Los bots deben ser clases de Python 3 y deben implementar dos métodos: selecty update.
Los bots se construirán con un índice.
selectse pasa sin argumentos y devuelve la elección del bot para la ronda actual.
updateSe pasa una lista de las elecciones realizadas por cada bot en la ronda anterior.

Ejemplo

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]))

Información Adicional

  • Ningún bot jugará en un juego contra sí mismo.
  • En el caso improbable de que se incluya un bot en menos de 100 juegos, el torneo se volverá a ejecutar.
  • Los bots pueden almacenar el estado entre rondas, pero no entre juegos.
  • No está permitido acceder al controlador u otros bots.
  • El número de juegos y el número de rondas por juego están sujetos a aumento si los resultados son demasiado variables.
  • Cualquier bot que genere errores o dé respuestas no válidas (no entradas, valores fuera de [1, 10], etc.) será descalificado y el torneo se volverá a ejecutar sin ellos.
  • No hay límite de tiempo para las rondas, pero puedo implementar uno si los robots tardan demasiado en pensar.
  • No hay límite en el número de envíos por usuario.
  • La fecha límite para las presentaciones es a las 23:59:59 UTC del viernes 28 de septiembre. El torneo ahora está cerrado para las presentaciones.

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)

fuente
2
@Mnemonic ¿Alguna noticia?
user1502040
44
@Herohtar Lo configuré antes de irme a trabajar. Con suerte, debería hacerse cuando llegue a casa.
1
@Mnemonic ¿Ya ha terminado?
user1502040
2
@Justin Se está ejecutando en este momento, y no parece estar fallando, pero definitivamente no me importaría la ayuda si esta ejecución falla.
1
@MihailMalostanidis Cree un archivo llamado bots.pyen el mismo directorio que contiene todos los bots. Al final, cree una lista de los constructores:allBotConstructors = [Lowball, BayesBot, ...]

Respuestas:

10

BayesBot

Intenta hacer la elección óptima utilizando un modelo estadístico simple.

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
fuente
10

Evita los bots constantes

Mantenga un registro de qué bots siempre han devuelto el mismo valor y omita esos valores. De los valores restantes, selecciónelos al azar, pero sesgados significativamente hacia valores más bajos.

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
Reinstalar a Mónica
fuente
10

WaitWhatBot

No es el bot más competitivo y definitivamente no es un GTO , pero sofocará el puntaje de cualquier oponente "siempre 1" o "casi siempre 1" en el mismo juego que en ese escenario WaitWhatBot también se convierte en un bot así.

Utiliza probabilidades evolutivas con pesos ponderados tanto en el tiempo (más reciente -> mayor peso) como en el valor de elección (punto más bajo -> mayor peso).

Utiliza código algo ofuscado para un poco de risa.

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
fuente
99
¿Cuánto peso habría comprado WaitWhatBot, si WaitWhatBot hubiera comprado peso?
Roman Odaisky
conjunto ([... para ... en ...]) ≡ {... para ... en ...}, por cierto
Roman Odaisky
@RomanOdaisky ¡Realmente le dije a alguien eso el otro día para jugar al golf!
Jonathan Allan
5

Acosador

Al comienzo del juego, este bot elige aleatoriamente un índice específico como objetivo. Luego acecha al objetivo de todo el juego, copiando el número que eligió en la ronda 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
fuente
4

Estúpido codicioso

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

Este bot supone que otros bots no quieren empatar.

Me doy cuenta de que esto es lo mismo que el ejemplo proporcionado, pero tuve la idea antes de leer tan lejos. Si esto es incongruente con la forma en que se ejecutan los desafíos de KoTH, avíseme.

Tostadas de ingeniero
fuente
En general prefiero no tener bots duplicados, pero no me importa dejarlo.
1
@Mnemonic bien técnicamente no es un engaño, ya que no se inicializa self.index.
hidefromkgb
@Mnemonic ¡No hay problema! Honestamente, este es mi primer KoTH y mi primer elemento en Python, así que seguí los primeros dos carteles y no lo cambié a pesar de mi sospecha de que debería haberlo hecho. Tampoco estaba seguro de si ibas a incluir Lowball en tus pruebas o si realmente era solo un ejemplo para la publicación.
Engineer Toast
Sin preocupaciones. ¡Bienvenido al maravilloso mundo de KoTH!
2
Eres una "granada as": 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))

Implementación del comentario del usuario2390246:

¿Qué hay de esto entonces? Comience con 1. Después de la primera ronda, haga un seguimiento de los valores ganadores y elija al azar de ellos con una probabilidad igual al número de ocurrencias. Por ejemplo, si los valores ganadores en las primeras tres rondas son [2, 3, 2], luego en la cuarta ronda, elija [2] con p = 2/3 y [3] con p = 1/3.

usuario48543
fuente
4

OneUpper

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

Los bots de todos los demás apuntan a 1 o al azar, entonces ¿por qué no solo apuntar a 2?

Zackary Murphy
fuente
4

Fluir como el agua

Evita los algoritmos básicos de detección constante de bots al duplicar cada número, avanzando lentamente hacia valores más bajos si no están 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
fuente
Tengo curiosidad, ¿su bot está relacionado de alguna manera con mi Fuente ? Ambos están "orientados al agua", jaja.
RedClover
Honestamente, mi plan inicial era hacer un robot de adivinanzas fijo que doble adivinara ciertos números, lo cual fue mi motivación para el proceso de toma de decisiones del robot. Cuando lo visualicé, estaba pensando en un flujo lento, que inspiró el nombre. Sin embargo
agradecemos
Entonces esto está obteniendo el 3er o 4to (generalmente el 3er) en cada prueba que ejecuto. Eso es bastante sorprendente para una estrategia tan simple.
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
fuente
4

La cuenta atrás 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

Pruébalo en línea!

Devuelve 10 para las primeras 100 rondas, 9 para las siguientes 100 y así sucesivamente.

Laikoni
fuente
4

Opportunitybot

Este bot realiza un seguimiento del número más bajo no elegido por ningún otro bot en cada ronda (el número más bajo disponible u oportunidad), y reproduce el número que ha sido ese número con mayor frecuencia.

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
fuente
4

PatterMatcher

Busca secciones repetidas en las presentaciones de los bots, intenta predecir y evitar sus 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

La posibilidad de elegir n es (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

Tiempo ponderado

La probabilidad de que un bot elija un número es proporcional a (10-n)*Δt. La primera ronda es idéntica al 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 frecuente

Envía el número menos frecuente, si son iguales, toma el más bajo.

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

El tiempo mas largo

Igual que con los frecuentes pero con el mayor tiempo entre envíos.

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

Saboteador

Envía el número más bajo que se envió la ú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

Envía el segundo número más bajo que se envió la ú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

Envía el número más bajo no enviado la ú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)

Lo siento, me dejé llevar un poco, obteniendo la idea de nuevos bots mientras implementaba el anterior una vez. No estaba seguro de cuál sería el mejor y tengo curiosidad sobre el rendimiento de cada uno de ellos. Puede encontrarlos todos aquí: https://repl.it/@Fejfo/Lowest-Unique-Number

fejfo
fuente
Agradable. Puede considerar modificar Saboteur para ignorar su propia última opción (a menos que sea intencional). Además, creo que es posible que deba manejar algunos casos especiales: ¿qué debe hacer SecondSaboteur si cada bot elige el mismo valor en alguna ronda, y qué debe hacer Profiteur si cada bot elige un valor diferente? Es posible que necesite un paréntesis final en Profiteur después set(range(10).
Mónica
PatternMatcher parece tener algún tipo de bucle infinito o lugar donde se atasca.
Robert Fraser
3

El bot 50% superior 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

Estaba a punto de publicar un bot aleatorio, pero hidefromkgb publicó antes que yo (al publicar se están convirtiendo en un objetivo fácil para el KGB, no es una buena manera de esconderse). Esta es mi primera respuesta KOTH, solo esperando vencer al bot rng.

maxb
fuente
3

El ciclador

Este bot simplemente recorre cada uno de los números en sus turnos. Solo por diversión, inicializa el contador con su í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
fuente
3

Sin preocupaciones

Este bot elige aleatoriamente un número y lo mantiene durante 50 rondas, luego elige otro y lo repite.

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
fuente
3

Siete de la suerte

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

Me siento afortunado hoy! ¡Estoy tirando todo el 7!

AlexRacer
fuente
3

Mi idea es que la estrategia depende más del número de bots que de la evaluación real de las estrategias.

Con un número significativo de bots, las opciones son:

  • Robots "codiciosos" que apuntan a los números 1-3 inferiores 10 bots son "inteligentes" y buscan obtener los números 1-3 inferiores, lo mejor es dejar que esos bots interfieran entre ellos.

  • Robots "inteligentes" que, una vez que se dan cuenta de que 4 siempre es recogido, irán a otro lado.

  • Robots "aleatorios" y "constantes". No hay mucho que hacer aquí.

Entonces, apuesto al # 4.

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

El bot de RNG esencial

import secrets

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

Asesino

Permanece en las sombras, luego apunta a la conjetura más baja actual. Correr.

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
fuente
2

FollowBot

Copie el ganador de la última ronda, o al menos la mejor selección mínimamente empatada si no hubo ganador.

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
fuente
2

Psicodélico

La única forma de ganar una guerra nuclear es volverse loco. Así que voy a hacer que cada bot predictivo en el torneo sea una locura.

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
fuente
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 las elecciones de la última ronda y elige el número más bajo sin elegir (ignorando la elección de UnchosenBot, por supuesto).

Spitemaster
fuente
2

Whoop-di-scoop-di-poop

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

Nunca he visto o tocado Python, ¿esto no es pitónico?

Mihail Malostanidis
fuente
1
Agregue la línea <!-- language: lang-python -->antes del bloque de código para habilitar el resaltado de sintaxis
Herman L
@HermanL Aluciné una pythonetiqueta en la pregunta y pensé que sería automática, pero escribí algo malo.
Mihail Malostanidis
1
En cuanto a la pitonicidad, el código es bastante bueno, excepto que podría considerarse pythonicer decir others = [c for i, c in enumerate(choices) if i != self.index], o, porque posteriormente solo usa esa variable para las pruebas de membresía, en { }lugar de [ ]construir una en setlugar de una list.
Roman Odaisky
if (self.guess)También es muy poco pitónico.
Jonathan Frech
¡No tengo idea de cómo self.guessentraron esos padres ! Debe haber sido uno de los formateadores.
Mihail Malostanidis
2

Fuente

Un bot simple, elige primero el número más bajo y si cualquier otro bot también lo elige, incrementará el contador: el piso se llena y el agua fluye hacia abajo. Cuando llega a 11, se reinicia a 1: el agua se bombea a la parte superior.

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
Trébol rojo
fuente
En su forma actual, su bot se quedará atascado en el ciclo while si los otros bots han elegido todos los números del 1 al 8. ¿Quería establecer target10?
Emil
@Emil True, originalmente era así, cambió
RedClover, el
2

PoissonsBot

Seleccione números de una distribución de Poisson que esté sesgada a valores más bajos. Ajuste el parámetro medio de la distribución hacia arriba si estamos empatados y hacia abajo si hay conjeturas debajo de nosotros. El tamaño del paso se hace progresivamente más pequeño a medida que avanza el juego.

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
fuente
2

MinWin

Mantiene una cuenta corriente de los valores ganadores y los valores mínimos no seleccionados (donde el valor mínimo no seleccionado solo se considera si es menor que el valor ganador). Selecciona aleatoriamente entre estos valores ganadores y mínimos.

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
fuente
2

PlayerCycle

Ciclos a través de los jugadores. La elección del jugador actual (podría ser uno mismo) ahora es la elección de este bot. Comienza a imprimir 8, porque por qué no. Lo siento, no puedo python, este es probablemente un código incorrecto.

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)]

Editar: Gracias a Triggernometry por mejorar mi código con itertools

ev3commander
fuente
Su código funciona bien, pero puede agregar un intertools.cycle () para que automáticamente pase de 0 a 9 y no tenga que hacer incrementos o verificaciones. ¡ Pruébelo en línea!
Triggernometry
2

Mapache

Elija el número más bajo no elegido en la ronda anterior, excepto nuestra propia elección anterior, que podría elegirse nuevamente esta vez. En la primera ronda, elige 1. (Dado 9 oponentes y 10 opciones, se garantiza que habrá un valor disponible).

Se me ocurrió esto de forma independiente, pero ahora veo al menos 2 bots anteriores que son esencialmente lo mismo.

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))

Triángulo De Mapache

Combina mapache y triángulo: de los valores no elegidos, elija uno basado en la probabilidad de triángulo inverso.

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ánica cuántica
fuente
Error:AttributeError: 'RaccoonTriangle' object has no attribute 'boundaries'
Renzeee
1
Si, lo siento. Creo que lo arreglé. Estaba en medio de las pruebas de escritura, cuando lo dejé.
Mecánico cuántico
1

El general

El general siempre lucha la ú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
fuente
1

No repetir al azar

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 elige al azar, pero evita elegir el mismo número que hizo la ronda anterior.

Draco18s
fuente