Il numero univoco più piccolo KoTH


27

Crea un bot per scegliere il numero univoco più piccolo.

(Basato su un esperimento di psicologia che ho sentito molti anni fa ma non sono stato in grado di rintracciare di nuovo.)

Regole

  • Ogni gioco sarà composto da 10 robot selezionati casualmente che giocano 1000 round.
  • Ad ogni round, tutti i robot selezionano un numero intero compreso tra 1 e 10 (incluso). Tutti i bot che scelgono lo stesso valore verranno esclusi e il bot rimanente con il valore più piccolo riceverà un punto.
  • Nel caso in cui nessun bot scelga un valore unico, non verranno assegnati punti.
  • Alla fine dei 1000 round, il bot con il maggior numero di punti (o tutti i robot con il maggior numero di punti) vince la partita.
  • Il torneo durerà 200 * (numero di giocatori) partite.
  • Il bot con la percentuale di vincita più alta vince il torneo.

specificazioni

I bot devono essere classi Python 3 e devono implementare due metodi: selecte update.
I robot saranno costruiti con un indice.
selectnon viene passato alcun argomento e restituisce la scelta del bot per il round corrente.
updateviene passato un elenco delle scelte fatte da ciascun bot nel round precedente.

Esempio

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

controllore

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

Informazioni aggiuntive

  • Nessun bot giocherà in una partita contro se stesso.
  • Nel caso improbabile che un bot sia incluso in meno di 100 partite, il torneo verrà rieseguito.
  • I robot possono memorizzare lo stato tra i round, ma non tra i giochi.
  • Non è consentito l'accesso al controller o ad altri robot.
  • Il numero di partite e il numero di round per partita sono soggetti ad aumento se i risultati sono troppo variabili.
  • Qualsiasi bot che genera errori o fornisce risposte non valide (non-ints, valori al di fuori di [1, 10], ecc.) Verrà squalificato e il torneo verrà rieseguito senza di essi.
  • Non ci sono limiti di tempo per i round, ma potrei implementarne uno se i robot impiegano troppo tempo a pensare.
  • Non vi è alcun limite al numero di invii per utente.
  • La scadenza per l'invio delle candidature è alle 23:59:59 UTC di venerdì 28 settembre. Il torneo è ora chiuso per le iscrizioni.

risultati

                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)

2
@Mnemonic Qualche novità?
user1502040

4
@Herohtar L'ho impostato in esecuzione prima di partire per lavoro. Con un po 'di fortuna, dovrebbe essere fatto quando torno a casa.

1
@Mnemonic È ancora finito?
user1502040

2
@Justin Sta funzionando in questo momento, e non sembra andare in crash, ma sicuramente non mi dispiacerebbe l'aiuto se questa corsa fallisce.

1
@MihailMalostanidis Crea un file chiamato bots.pynella stessa directory contenente tutti i bot. Alla fine, crea un elenco dei costruttori:allBotConstructors = [Lowball, BayesBot, ...]

Risposte:


10

BayesBot

Cerca di fare la scelta ottimale usando un semplice modello statistico.

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

10

Evita i robot costanti

Tieni traccia di quali robot hanno sempre restituito lo stesso valore e salta tali valori. Dei valori rimanenti, selezionali in modo casuale, ma distorti significativamente verso valori più bassi.

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

10

WaitWhatBot

Non è il bot più competitivo e sicuramente non GTO , ma bloccherà il punteggio di qualsiasi avversario "sempre 1" o "quasi sempre 1" nello stesso gioco come in uno scenario simile WaitWhatBot diventa anche un bot.

Utilizza le probabilità in evoluzione con pesi ponderati sia nel tempo (più recente -> maggiore peso) che nel valore di scelta (punto più basso -> maggiore peso).

Usa un po 'di codice offuscato per un po' di risatina.

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

9
Quanto peso avrebbe acquistato WaitWhatBot, se WaitWhatBot avesse acquistato peso?
Roman Odaisky,

set ([... for ... in ...]) ≡ {... for ... in ...}, a proposito
Roman Odaisky,

@RomanOdaisky In realtà ho avvisato qualcuno proprio l'altro giorno per un golf!
Jonathan Allan,

5

Molestatore

All'inizio del gioco, questo bot sceglie casualmente un indice specifico come bersaglio. Quindi insegue il bersaglio per l'intero gioco, copiando il numero che ha scelto nel round precedente.

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]

4

Stupido goloso

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

Questo bot presuppone che altri robot non vogliano legare.

Mi rendo conto che questo è lo stesso dell'esempio fornito, ma ho pensato prima di leggere così lontano. Se questo è incongruo con come vengono affrontate le sfide KoTH, fammi sapere.


In generale preferisco non avere robot duplicati, ma non mi dispiace lasciarlo.

1
@Mnemonic, tecnicamente, non è un duplicato, in quanto non si inizializza self.index.
hidefromkgb

@Mnemonic Nessun problema! Onestamente, questo è il mio primo KoTH e il mio primo in Python, quindi ho appena seguito i primi due poster e non l'ho cambiato nonostante il mio sospetto che avrei dovuto. Inoltre non ero sicuro che avresti incluso Lowball nei tuoi test o era davvero solo un esempio per il post.
Ingegnere Toast,

Nessun problema. Benvenuti nel meraviglioso mondo di KoTH!

2
Ti sei fatto un "asso granata": 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))

Implementazione del commento dell'utente2390246:

E allora? Inizia con 1. Dopo il primo round, tieni traccia dei valori vincenti e scegli casualmente da essi con probabilità pari al numero di occorrenze. Ad esempio, se i valori vincenti nei primi tre round sono [2, 3, 2], quindi nel round quattro, scegli [2] con p = 2/3 e [3] con p = 1/3.


4

OneUpper

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

I robot di tutti gli altri mirano a 1 o casuali, quindi perché non puntare solo a 2?


4

Scorri come l'acqua

Evita gli algoritmi di base per il rilevamento di bot costanti raddoppiando su ciascun numero, avanzando lentamente verso valori più bassi se non sono occupati.

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

Sono curioso, il tuo bot è in qualche modo legato alla mia fontana ? Entrambi sono "orientati all'acqua", ahah.
RedClover

Onestamente, il mio piano iniziale era quello di creare un bot di ipotesi fisso che raddoppiasse determinati numeri, che era la mia motivazione per il processo decisionale del bot. Quando l'ho visualizzato, pensavo a un flusso lento, che ha ispirato il nome. Grida al tema dell'acqua :)
TCFP

Quindi questo sta ottenendo il 3 ° o il 4 ° (di solito il 3 °) in ogni test che eseguo. È piuttosto sorprendente per una strategia così semplice.
Robert Fraser,

4

Totalmente perso

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

4

Il conto alla rovescia finale

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

Provalo online!

Restituisce 10 per i primi 100 round, 9 per i successivi 100 e così via.


4

Opportunitybot

Questo bot tiene traccia del numero più basso non scelto da nessun altro robot ogni round (il numero più basso disponibile o opportunità) e riproduce il numero che è stato quel numero più frequentemente.

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

4

PatterMatcher

Cerca sezioni ripetute negli invii dei robot, cerca di prevederne i numeri.

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

Triangolo

La possibilità di scegliere 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

La probabilità che un bot scelga un numero è proporzionale (10-n)*Δt. Il primo round è identico al triangolo.

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

LeastFrequent

Invia il numero che si verifica meno frequentemente, se sono uguali, prendi il numero più basso.

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

Il periodo più lungo

Come per le frequenti ma con il tempo più lungo tra le presentazioni.

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

Sabotatore

Invia il numero più basso che è stato inviato l'ultima volta.

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

Invia il secondo numero più basso che è stato inviato l'ultima volta

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

Invia il numero più basso non inviato l'ultima volta

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)

Mi dispiace di essermi un po 'lasciato prendere dall'idea di nuovi robot durante l'implementazione della precedente. Non ero sicuro di quale sarebbe stato il migliore e sono curioso di sapere come si esibiscono. Puoi trovarli tutti qui: https://repl.it/@Fejfo/Lowest-Unique-Number


Bello. Potresti considerare di modificare Sabotatore per ignorare la sua ultima scelta (a meno che non sia intenzionale). Inoltre, penso che potresti dover gestire alcuni casi speciali: cosa dovrebbe fare SecondSaboteur se ogni bot sceglie lo stesso valore in qualche round, e cosa dovrebbe fare Profiteur se ogni bot sceglie un valore diverso? Dopo potresti aver bisogno di una parentesi finale in Profiteur set(range(10).
Ripristina Monica il

PatternMatcher sembra avere una sorta di loop infinito o luogo in cui si blocca.
Robert Fraser,

3

Il miglior bot RNG al 50%

import random

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

Stavo per pubblicare un bot casuale, ma hidefromkgb ha pubblicato prima di me (pubblicando si stanno facendo un bersaglio facile per il KGB, non un buon modo per nascondersi). Questa è la mia prima risposta KOTH, sperando solo di battere il bot rng.


3

Il ciclista

Questo bot scorre semplicemente attraverso ciascuno dei numeri a sua volta. Solo per divertimento, inizializza il contatore con il suo indice.

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

3

OneTrackMind

Questo robot sceglie casualmente un numero e lo mantiene per 50 round, quindi ne sceglie un altro e si ripete.

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)

3

Lucky Seven

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

Mi sento fortunato oggi! Sto buttando tutto su 7!


3

La mia idea è che la strategia dipende più dal numero di robot che dalla valutazione effettiva delle strategie.

Con un numero significativo di bot, le opzioni sono:

  • I robot "avidi" che puntano ai numeri 1-3 inferiori che 10 robot sono "intelligenti" e mirano a ottenere i numeri 1-3 inferiori, la cosa migliore è lasciare che quei robot interferiscano tra di loro.

  • I robot "intelligenti" che, una volta realizzati, ne prendono sempre 4, andranno altrove.

  • Robot "casuali" e "costanti". Non c'è molto da fare qui.

Quindi, scommetto sul n. 4.

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

2

Il bot RNG essenziale

import secrets

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

2

Assassino

Rimane nell'ombra, quindi punta all'ipotesi più bassa attuale. Correre.

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

2

FollowBot

Copia il vincitore dell'ultimo round, o almeno la migliore selezione minimamente vincolata se non c'era vincitore.

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

2

psychadelic

L'unico modo per vincere una guerra nucleare è di impazzire. Quindi farò impazzire ogni robot predittivo nel torneo.

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

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

Prende le scelte dell'ultimo round e sceglie il numero non scelto più basso (ignorando la scelta di UnchosenBot, ovviamente).


2

Whoop-di-paletta-di-cacca

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

Non ho mai visto o toccato Python, non è questo pitonico?


1
Aggiungi la riga <!-- language: lang-python -->prima del blocco di codice per abilitare l'evidenziazione della sintassi
Herman L

@HermanL Ho allucinato un pythontag sulla domanda e ho pensato che sarebbe stato automatico, ma ho scritto qualcosa di brutto.
Mihail Malostanidis,

1
Per quanto riguarda la pitonicità, il codice è abbastanza buono, tranne per il fatto che potrebbe essere considerato pythonicer da dire others = [c for i, c in enumerate(choices) if i != self.index], o, poiché successivamente si utilizza quella variabile solo per i test di appartenenza, { }piuttosto che [ ]costruire un setanziché un list.
Roman Odaisky,

if (self.guess)è anche molto poco sincero.
Jonathan Frech,

Non ho idea di come siano self.guessarrivati quei genitori ! Deve essere stato uno dei formatter.
Mihail Malostanidis,

2

Fontana

Un semplice bot, seleziona prima il numero più basso e, se lo sceglie anche qualsiasi altro bot, incrementa il contatore: il pavimento viene riempito e l'acqua scorre verso il basso. Quando raggiunge 11, si riavvia a 1 - l'acqua viene pompata di nuovo verso l'alto.

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

Nella sua forma attuale il tuo bot rimarrà bloccato nel ciclo while se gli altri bot hanno scelto tutti i numeri da 1 a 8. Volevi impostare targetsu 10?
Emil,

@Emil True, originariamente era così, cambiato
RedClover

2

PoissonsBot

Seleziona i numeri da una distribuzione di Poisson che è distorta su valori più bassi. Regola il parametro medio della distribuzione in alto se siamo in pareggio e in basso se ci sono ipotesi sotto di noi. La dimensione del passo si riduce progressivamente man mano che il gioco procede.

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)

2

MinWin

Mantiene un conteggio corrente dei valori vincenti e dei valori minimi non selezionati (in cui il valore minimo non selezionato viene considerato solo se è inferiore al valore vincente). Seleziona casualmente tra questi valori vincenti e minimi.

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)

2

PlayerCycle

Scorre i giocatori. La scelta del giocatore attuale (potrebbe essere auto) ora è la scelta di questo bot. Inizia a stampare 8, perché perché no. Mi dispiace non posso pitone, questo è probabilmente un codice errato.

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

Modifica: grazie a Triggernometry per aver migliorato il mio codice con itertools


Il tuo codice funziona bene, ma puoi aggiungere un intertools.cycle () per a in modo che passi automaticamente da 0-9 e non devi eseguire incrementi o controlli - Provalo online!
Triggernometria

2

Procione

Scegli il numero più basso non scelto nel turno precedente, ad eccezione della nostra scelta precedente, che potrebbe essere scelta nuovamente questa volta. Nel primo turno, scegli 1. (Dati 9 avversari e 10 scelte, è garantito un valore disponibile).

Ho escogitato questo in modo indipendente, ma ora vedo almeno 2 robot precedenti che sono essenzialmente gli stessi.

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

Triangolo di procione

Combina Raccoon e Triangle: dai valori non scelti, scegline uno in base alla probabilità del triangolo 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)

Errore:AttributeError: 'RaccoonTriangle' object has no attribute 'boundaries'
Renzeee,

1
Sì scusa. Penso di averlo corretto. Ero nel bel mezzo di prove di scrittura, quando ho lasciato.
Meccanico quantistico,

1

Il generale

Il generale combatte sempre l'ultima 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

1

Casuale senza ripetizione

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

Il bot sceglie casualmente, ma evita di selezionare lo stesso numero del round precedente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.