Le plus petit numéro unique KoTH


27

Créez un bot pour choisir le plus petit numéro unique.

(Basé sur une expérience de psychologie dont j'ai entendu parler il y a de nombreuses années mais que je n'ai pas pu retrouver.)

Règles

  • Chaque jeu sera composé de 10 bots sélectionnés au hasard jouant 1000 tours.
  • Chaque tour, tous les bots sélectionnent un entier de 1 à 10 (inclus). Tous les bots qui choisissent la même valeur seront exclus et le bot restant avec la plus petite valeur recevra un point.
  • Dans le cas où aucun bot ne choisit une valeur unique, aucun point ne sera attribué.
  • À la fin de 1000 tours, le bot avec le plus de points (ou tous les bots à égalité avec le plus de points) remporte la partie.
  • Le tournoi durera 200 * (nombre de joueurs) matchs.
  • Le bot avec le pourcentage de victoire le plus élevé remporte le tournoi.

Caractéristiques

Les bots doivent être des classes Python 3 et doivent implémenter deux méthodes: selectet update.
Les bots seront construits avec un index.
selectne reçoit aucun argument et renvoie le choix du bot pour le tour en cours.
updateest passé une liste des choix effectués par chaque bot au tour précédent.

Exemple

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

Manette

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

Information additionnelle

  • Aucun bot ne jouera dans un match contre lui-même.
  • Dans le cas peu probable où un bot est inclus dans moins de 100 matchs, le tournoi sera relancé.
  • Les bots peuvent stocker l'état entre les tours, mais pas entre les jeux.
  • L'accès au contrôleur ou à d'autres robots n'est pas autorisé.
  • Le nombre de parties et le nombre de tours par partie sont susceptibles d'augmenter si les résultats sont trop variables.
  • Tous les bots qui soulèvent des erreurs ou donnent des réponses invalides (non-ints, valeurs en dehors de [1, 10], etc.) seront disqualifiés et le tournoi sera relancé sans eux.
  • Il n'y a pas de limite de temps pour les tours, mais je peux en appliquer un si les bots prennent trop de temps pour réfléchir.
  • Il n'y a pas de limite au nombre de soumissions par utilisateur.
  • La date limite pour les soumissions est 23:59:59 UTC le vendredi 28 septembre. Le tournoi est maintenant fermé pour les soumissions.

Résultats

                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 Des nouvelles?
user1502040

4
@Herohtar Je l'ai mis en marche avant de partir travailler. Avec un peu de chance, cela devrait être fait à mon retour à la maison.

1
@Mnemonic A-t-il déjà fini?
user1502040

2
@Justin Il fonctionne en ce moment et ne semble pas planter, mais cela ne me dérangerait certainement pas de l'aide si cette course échoue.

1
@MihailMalostanidis Créez un fichier appelé bots.py dans le même répertoire contenant tous les bots. À la fin, créez une liste des constructeurs:allBotConstructors = [Lowball, BayesBot, ...]

Réponses:


10

BayesBot

Tente de faire le choix optimal à l'aide d'un modèle statistique 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

10

Évitez les robots constants

Gardez une trace des robots qui ont toujours renvoyé la même valeur et ignorez ces valeurs. Parmi les valeurs restantes, sélectionnez-les au hasard, mais biaisez de manière significative vers des valeurs plus faibles.

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

Pas le bot le plus compétitif et certainement pas GTO , mais étouffera le score de tout adversaire "toujours 1" ou "presque toujours 1" dans le même jeu que dans un tel scénario WaitWhatBot devient aussi un tel bot.

Utilise des probabilités évolutives avec des poids pondérés à la fois dans le temps (plus récent -> plus grand poids) et dans la valeur de choix (point inférieur -> plus grand poids).

Utilise du code quelque peu obscurci pour un petit fou rire.

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
Combien de poids WaitWhatBot aurait-il acheté, si WaitWhatBot n'achèterait que du poids?
Roman Odaisky

set ([… for… in…]) ≡ {… for… in…}, soit dit en passant
Roman Odaisky

@RomanOdaisky J'ai en fait avisé quelqu'un de cela juste l'autre jour pour un golf!
Jonathan Allan

5

Stalker

Au début du jeu, ce bot choisit au hasard un index spécifique comme cible. Il traque ensuite tout le jeu, en copiant le numéro choisi au tour précédent.

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

Stupid Greedy One

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

Ce bot suppose que les autres bots ne veulent pas égaler.

Je me rends compte que c'est la même chose que l'exemple fourni, mais j'ai pensé avant de lire jusque-là. Si cela ne correspond pas à la façon dont les défis KoTH sont gérés, faites-le moi savoir.


En général, je préfère ne pas avoir de bots en double, mais cela ne me dérange pas de le laisser.

1
@Mnemonic bien techniquement ce n'est pas dupe, car il ne s'initialise pas self.index.
hidefromkgb

@Mnemonic Pas de problème! Honnêtement, c'est mon premier KoTH et mon premier quoi que ce soit en Python donc j'ai juste suivi les deux premières affiches et je ne l'ai pas changé malgré mes soupçons que j'aurais dû. Je ne savais pas non plus si vous alliez inclure Lowball dans vos tests ou si c'était vraiment juste un exemple pour le post.
Ingénieur Toast

Pas de soucis. Bienvenue dans le monde merveilleux de KoTH!

2
Vous Thew une "grenade 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))

Implémentation du commentaire de user2390246:

Et alors? Commencez par 1. Après le premier tour, gardez une trace des valeurs gagnantes et choisissez-les au hasard avec une probabilité égale au nombre d'occurrences. Par exemple, si les valeurs gagnantes dans les trois premiers tours sont [2, 3, 2] puis dans le quatrième tour, choisissez [2] avec p = 2/3 et [3] avec 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

Les robots de tous les autres visent soit 1 ou au hasard, alors pourquoi ne pas viser seulement 2?


4

Débit comme l'eau

Évite les algorithmes de base de détection de bots constants en doublant sur chaque nombre, progressant lentement vers des valeurs plus faibles s'ils ne sont pas occupés.

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

Je suis curieux, votre robot est-il en quelque sorte lié à ma fontaine ? Les deux sont "orientés vers l'eau", haha.
RedClover

Honnêtement, mon plan initial était de faire un bot de conjecture fixe qui devinerait certains nombres, ce qui était ma motivation pour le processus de prise de décision du bot. Quand je l'ai visualisé, je pensais à un flux lent qui a inspiré le nom. Mais
criez au

Donc, cela devient le 3e ou le 4e (généralement le 3e) dans chaque test que je lance. C'est assez étonnant pour une stratégie aussi simple.
Robert Fraser

4

Complètement perdu

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

Le compte à rebours 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

Essayez-le en ligne!

Renvoie 10 pour les 100 premiers tours, 9 pour les 100 suivants et ainsi de suite.


4

Opportunitybot

Ce bot garde une trace du plus petit nombre non choisi par les autres bots à chaque tour (le plus petit nombre disponible ou opportunité), et joue le nombre qui a été ce nombre le plus fréquemment.

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

Recherche les sections répétitives dans les soumissions des robots, essaie de prévoir et d'éviter les nombres.

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

Triangle

La chance de choisir n est (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é qu'un bot choisisse un nombre est proportionnelle à (10-n)*Δt. Le premier tour est identique au triangle.

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

Moins fréquent

Soumet le nombre le moins fréquent, s'il est égal, prendre le plus bas.

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

Le plus longtemps

Identique à la fréquence mais avec le plus long délai entre les soumissions.

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

Saboteur

Soumet le numéro le plus bas qui a été soumis la dernière fois.

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

Soumet le deuxième numéro le plus bas qui a été soumis la dernière fois

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

Soumet le plus petit nombre non soumis la dernière fois

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)

Désolé, je me suis un peu emporté, j'ai eu l'idée de nouveaux robots lors de la mise en œuvre de la précédente. Je ne savais pas lequel serait le meilleur et je suis curieux de connaître les performances de chacun d'eux. Vous pouvez tous les trouver ici: https://repl.it/@Fejfo/Lowest-Unique-Number


Agréable. Vous pourriez envisager de modifier Saboteur pour ignorer son propre dernier choix (sauf si cela est intentionnel). En outre, je pense que vous devrez peut-être gérer certains cas particuliers: que devrait faire SecondSaboteur si chaque bot choisit la même valeur dans un tour, et que devrait faire Profiteur si chaque bot choisit une valeur différente? Vous devrez peut-être une parenthèse de fin dans Profiteur après set(range(10).
Rétablir Monica le

PatternMatcher semble avoir une sorte de boucle infinie ou un endroit où il se coince.
Robert Fraser

3

Le top 50% RNG bot

import random

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

J'étais sur le point de poster un bot aléatoire, mais hidefromkgb a posté avant moi (en postant, ils se font une cible facile pour le KGB, pas un bon moyen de se cacher). Ceci est ma première réponse KOTH, espérant juste battre le bot rng.


3

Le cycliste

Ce bot passe simplement en revue chacun des nombres à son tour. Pour le plaisir, il initialise le compteur avec son index.

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

Ce bot choisit un nombre au hasard et s'en tient à lui pendant 50 tours, puis en choisit un autre et répète.

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

Sept chanceux

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

J'ai de la chance aujourd'hui! Je jette tout sur 7!


3

Mon idée est que la stratégie dépend plus du nombre de robots que de l'évaluation réelle des stratégies.

Avec un nombre important de bots, les options sont:

  • Robots "gourmands" visant les numéros 1-3 inférieurs 10 robots étant "intelligents" et visant à obtenir les numéros 1-3 inférieurs, le mieux est de laisser ces robots interférer entre eux.

  • Les robots "intelligents" qui, une fois qu'ils se rendent compte que 4 est toujours ramassé, iront ailleurs.

  • Robots "aléatoires" et "constants". Pas grand chose à faire ici.

Donc, je parie sur # 4.

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

2

L'essentiel du robot RNG

import secrets

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

2

Assassin

Reste dans l'ombre, puis vise la supposition la plus basse actuelle. Courir.

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

Copiez le gagnant du dernier tour, ou au moins la meilleure sélection à égalité minimale s'il n'y avait pas de gagnant.

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

La seule façon de gagner une guerre nucléaire est de devenir fou. Je vais donc rendre fou tous les bots prédictifs du tournoi.

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

Prend les choix du dernier tour et choisit le plus petit nombre non choisi (en ignorant le choix de UnchosenBot, bien sûr).


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

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

Je n'ai jamais vu ni touché Python, est-ce impythonique?


1
Ajoutez la ligne <!-- language: lang-python -->avant le bloc de code pour activer la coloration syntaxique
Herman L

@HermanL J'ai halluciné une pythonétiquette sur la question et j'ai pensé que ce serait automatique mais j'ai écrit quelque chose de mauvais.
Mihail Malostanidis

1
En ce qui concerne la pythonicité, le code est assez bon, sauf qu'il pourrait être considéré comme pythonicer others = [c for i, c in enumerate(choices) if i != self.index], ou, car par la suite vous n'utilisez cette variable que pour les tests d'appartenance, { }plutôt que de [ ]construire un setplutôt qu'un list.
Roman Odaisky

if (self.guess)est également très impythonique.
Jonathan Frech

Je n'ai aucune idée de comment ces parens self.guesssont entrés là-dedans! Doit être l'un des formateurs.
Mihail Malostanidis

2

Fontaine

Un simple bot choisit le numéro le plus bas en premier et si un autre bot le choisit aussi, il incrémentera le compteur - le sol se remplit et l'eau coule. Quand il atteint 11, il redémarre à 1 - l'eau est pompée vers le haut.

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

Dans sa forme actuelle, votre bot restera coincé dans la boucle while si les autres bots ont choisi tous les nombres de 1 à 8. Vouliez-vous définir targetà 10?
Emil

@Emil True, c'était à l'origine comme ça, changé
RedClover

2

PoissonsBot

Sélectionnez des nombres à partir d'une distribution de Poisson qui est biaisée à des valeurs inférieures. Ajustez le paramètre moyen de la distribution vers le haut si nous sommes à égalité et vers le bas s'il y a des suppositions en dessous de nous. La taille des pas diminue progressivement au fur et à mesure que le jeu avance.

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

Conserve un décompte en cours des valeurs gagnantes et des valeurs minimales non sélectionnées (où la valeur minimale non sélectionnée n'est prise en compte que si elle est inférieure à la valeur gagnante). Il sélectionne au hasard parmi ces valeurs gagnantes et minimales.

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

Parcourt les joueurs. Le choix du joueur actuel (pourrait être lui-même) est maintenant le choix de ce bot. Démarre l'impression 8, car pourquoi pas. Désolé, je ne peux pas python, c'est probablement du mauvais code.

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: Merci à Triggernometry pour l'amélioration de mon code avec itertools


Votre code fonctionne très bien, mais vous pouvez ajouter un intertools.cycle () pour un afin qu'il passe automatiquement de 0 à 9 et que vous n'ayez pas à faire d'incrémentation ou de vérification - Essayez-le en ligne!
Triggernométrie

2

Raton laveur

Choisissez le numéro le plus bas non choisi lors du tour précédent, à l'exception de notre propre choix précédent, qui pourrait être choisi à nouveau cette fois. Au premier tour, choisissez 1. (Étant donné 9 adversaires et 10 choix, il est garanti qu'il y a une valeur disponible.)

J'ai trouvé cela indépendamment, mais je vois maintenant au moins 2 robots précédents qui sont essentiellement les mêmes.

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

Triangle de raton laveur

Combine raton laveur et triangle: parmi les valeurs non choisies, choisissez-en une en fonction de la probabilité du triangle inversé.

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)

Erreur:AttributeError: 'RaccoonTriangle' object has no attribute 'boundaries'
Renzeee

1
Oui désolé. Je pense que je l'ai réparé. J'étais en train de passer des tests, quand j'ai arrêté.
Mécanicien quantique,

1

Le général

Le général se bat toujours la dernière guerre (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

Pas de répétition aléatoire

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 choisit au hasard, mais évite de choisir le même numéro que lors du tour précédent.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.