Échange d'éléphants blancs

11

C'est Noël en juillet, alors quelle meilleure façon de célébrer qu'un échange virtuel de cadeaux d'éléphants blancs!

Pour ce défi King of the Hill, vous devez créer un bot qui joue dans une simulation d' échange d'éléphants blancs , en essayant d'obtenir le cadeau le plus précieux possible.

Regles du jeu

  • Le jeu se jouera sur plusieurs tours, chacun composé d'un nombre variable de tours.
  • Configuration de la manche : Il y aura autant de cadeaux qu'il y aura de joueurs dans le jeu, chacun étant évalué de manière uniforme et aléatoire dans la plage [0 ... 1), cette valeur étant inconnue jusqu'à ce que le cadeau soit "ouvert". Les joueurs seront placés dans un ordre aléatoire dans une file d'attente. Le premier joueur sera sauté de l'avant de la file d'attente.
  • Quand c'est le tour d'un joueur, il peut soit ouvrir un cadeau, soit voler le cadeau d'un autre joueur, en passant le tour au joueur dont le cadeau a été volé.
    • Chaque cadeau peut être volé jusqu'à 3 fois.
    • Vous ne pouvez pas voler le joueur qui vient de vous voler.
    • Chaque joueur ne peut avoir qu'un seul présent à la fois.
  • Après l'ouverture d'un cadeau, le jeu passe au joueur suivant surgi de l'avant de la file d'attente. Ce sera le prochain joueur dans l'ordre du tour qui n'a pas encore eu de tour.
  • Fin du tour : Lorsque tous les cadeaux ont été ouverts, le tour se termine et la valeur du cadeau détenu par chaque joueur est ajoutée au score de ce joueur. Un nouveau tour commence, chaque joueur ne tenant plus de présent et l'ordre des joueurs est mélangé.
  • Fin de la partie : la partie se termine lorsqu'au moins un joueur a marqué 100 500 points, la victoire étant attribuée au joueur ayant la plus grande valeur totale de cadeaux.

Codage

Toutes les soumissions doivent être compatibles avec Python 3.7. Vous devez écrire une classe qui hérite directement de WhiteElephantBot. Par exemple:

class FooBot(WhiteElephantBot):
    # Your implementation here

Vous pouvez fournir une __init__méthode (qui prend un argument name) dans votre classe bot, qui doit appeler super().__init__(name). Votre classe doit avoir une take_turnméthode qui attend les arguments suivants dans cet ordre:

  • players: La liste des noms des joueurs, dans l'ordre du tour, de tous les joueurs qui n'ont pas encore de cadeaux.
  • presents: Un dictionnaire qui mappe les noms des joueurs à 2 tuples contenant la valeur actuelle détenue par ce joueur et le nombre de fois que celui-ci a été volé. Cela n'inclura que les autres joueurs qui détiennent actuellement des cadeaux.
  • just_stole: Si la dernière action effectuée a été un vol, ce sera le nom du joueur qui vient de voler. Sinon, ça le sera None.

Chaque argument sera immuable ou un nouvel objet de sorte que la mutation de l'un d'eux n'aura aucun effet sur le jeu. Vous pouvez conserver une copie de tout argument si vous le souhaitez.

Un exemple de valeur pour presents:

{
    'Alice':   (0.35, 0),
    'Bob':     (0.81, 2),
    'Charlie': (0.57, 1)
}

Votre take_turnméthode doit retourner le nom du joueur que vous souhaitez voler ou Noneouvrir un cadeau. S'il déclenche une exception, retourne autre chose qu'un strou None, ou le nom d'un joueur que vous ne pouvez pas voler, vous ouvrirez un cadeau par défaut.

Votre constructeur sera appelé au début de chaque tour, donc vous ne vous souvenez pas de l'état d'un tour à l'autre.

En héritant de WhiteElephantBot, vous aurez accès à une steal_targetsméthode qui prendra les cadeaux dict just_stoleet retournera une liste de noms de joueurs que vous pouvez voler.

Tous les modules dont votre script a besoin doivent être importés en haut de votre entrée.

Pilote de test

Le pilote de test peut être trouvé ici . Vous n'avez pas besoin d'inclure from white_elephant import WhiteElephantBotdans votre réponse publiée, mais un module local devra le faire.

Concurrents de base

  • Aléatoire : choisit aléatoirement d'ouvrir un nouveau cadeau ou de voler, la cible de vol étant choisie de manière uniforme et aléatoire.
  • Gourmand : volez le cadeau le plus précieux qui puisse être volé. Si aucun cadeau ne peut être volé, ouvrez un cadeau.
  • Nice : ouvre toujours un nouveau cadeau. Ne vole jamais.

Règles supplémentaires

  • Il est de votre responsabilité d'attraper toutes les exceptions. Si votre classe ne parvient pas à intercepter une exception, elle sera disqualifiée. De plus, veuillez ne pas intercepter les interruptions clavier.
  • N'utilisez pas de fichiers ou d'autres méthodes pour contourner l'impossibilité d'enregistrer l'état entre les jeux. Vous ne pouvez pas, par exemple, enregistrer un état de réseau neuronal dans un fichier à mi-parcours.
  • Votre bot doit être autonome dans le code de classe et les constantes associées.
  • Vous ne pouvez utiliser que les importations de bibliothèque standard.
  • Il n'y a aucune exigence de performance stricte. Soyez raisonnable et prudent. Si les performances deviennent un problème, je me réserve le droit d'ajouter des délais.
  • Une entrée par personne. Si vous soumettez plus d'une entrée, vos bots peuvent ne pas fonctionner ensemble. Je vais autoriser plusieurs entrées par personne pour le moment, bien que je puisse le réinterroger plus tard si cela devient un problème.
  • Il s'agit d'un concours ouvert sans date de fin distincte. Il sera réexécuté chaque fois que je le pourrai en cas de changements importants.

EDIT1: Changement du score gagnant de 100 à 500 afin que les classements soient plus cohérents. Le pilote d'essai a un nouveau bugfix et reflète également les changements de score de victoire.

EDIT2: Note de clarification sur les importations requises.


Classement (au 8 août 2018)

  1. SampleBot (500.093)
  2. LastMinuteBot (486.163)
  3. RobinHood (463.160)
  4. OddTodd (448.825)
  5. GreedyBot (438.520)
  6. SecondPlaceBot (430.598)
  7. ThresholdBot (390.480)
  8. Joueur (313.362)
  9. NiceBot (275.536)
  10. RandomBot (256.172)
  11. GoodSamaritan (136.298)
Beefster
la source
Peut-il y avoir un certain nombre de vols consécutifs? Quand j'ai joué, il y a généralement une limite de 2 interceptions ou quelque chose, et la troisième personne devrait en ouvrir un. Cela empêche le même cadeau d'être volé plus d'une fois par tour.
mbomb007
@ mbomb007 Oui. Le vol en chaîne est illimité, sauf par les autres règles qui rendent certains cadeaux insensibles au vol: chaque cadeau ne peut être volé que 3 fois et vous ne pouvez pas voler le joueur qui vient de vous voler.
Beefster
Pouvez-vous voler un cadeau et ensuite voler à nouveau l'original que vous aviez?
Erik the Outgolfer le
@EriktheOutgolfer: oui, tant qu'il y aura un autre virage entre les deux. Vous ne pouvez pas voler à nouveau immédiatement après le vol de votre cadeau.
Beefster du
1
Échange Yankee!? Quelle est la prochaine étape, une fête d'anniversaire partagée?
ngm

Réponses:

3

LastMinuteBot

(Un grand merci à @Mnemonic pour le squelette du code, car je connais à peine Python.)

class LastMinuteBot(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):
        targets = self.steal_targets(presents, just_stole)
        if len(targets) <= 1:
            return None

        target = None

        # If most of the presents are already distributed, try to steal an 
        #  un-restealable gift of high value
        if len(presents) > (len(players) + len(presents)) * 0.75:
            at_threshold = [t for t in targets if presents[t][1]==2 and presents[t][0]>=0.8]
            if at_threshold:
                target = max(at_threshold, key=lambda x: presents[x][0])

        # Otherwise, take the best available
        if not target:
            target = max(targets, key=lambda x: presents[x][0])

        return target if presents[target][0] > 0.5 else None

Profitez du fait que les cadeaux ne peuvent pas être volés plus de trois fois, faites le troisième vous-même si vous trouvez un cadeau de grande valeur et que la plupart des cadeaux ont été ouverts.

Sundar - Rétablir Monica
la source
Simple, mais magnifique
r_j
2

Odd Todd

class OddTodd(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):

        targets = self.steal_targets(presents, just_stole)

        # if none to steal, pick present
        if len(targets) <= 1:
            return None

        # steals the best gift that he can, as long as he's the 1st/3rd steal
        targets = [t for t in targets if presents[t][1] % 2 == 0]
        if targets:
            return max(targets, key=lambda x:presents[x][0])

        else:
            return None

Vole le meilleur cadeau qu'il peut, mais ne veut pas être la deuxième personne à voler un cadeau, car s'il lui est volé, il ne peut pas le récupérer.

brian_t
la source
Erreur de syntaxe à la ligne 11. Vous avez besoin d'un ==au lieu d'un =dans votre liste de compréhension.
Beefster
fixe, merci! N'utilisez pas beaucoup Python.
brian_t
1

SecondPlaceBot

class SecondPlaceBot(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):
        targets = self.steal_targets(presents, just_stole)
        if len(targets) <= 1:
            return None

        # If most of the presents are already distributed, take the second best.
        if len(presents) > (len(players) + len(presents)) * 0.8:
            target = sorted(targets, key=lambda x: presents[x][0])[-2]
        # Otherwise, take the best and hope someone steals it later.
        else:
            target = max(targets, key=lambda x: presents[x][0])

        return target if presents[target][0] > 0.5 else None

Tout le monde va se battre pour le cadeau le plus précieux. Le prochain meilleur cadeau est presque aussi bon, mais beaucoup moins susceptible d'être volé.


la source
1

ThresholdBot

import random

class ThresholdBot(WhiteElephantBot):
    def __init__(self, name):
        self.name = name
        # Choose a minimum value to be happy.
        self.goal = 1 - random.random() ** 2

    def take_turn(self, players, presents, just_stole):
        # Find who has a gift that's sufficiently valuable.
        targets = self.steal_targets(presents, just_stole)
        targets = [x for x in targets if presents[x][0] >= self.goal]
        targets = sorted(targets, key=lambda x: presents[x][0])

        if not targets:
            return None

        # Choose a target (biased toward the best gifts).
        weighted = []
        for i, target in enumerate(targets, 1):
            weighted += [target] * i ** 2
        return random.choice(weighted)

Nous ne nous soucions pas vraiment d'obtenir le meilleur cadeau, juste quelque chose d' assez bon . Tant qu'il y a quelque chose qui mérite d'être volé, nous le ferons.


la source
1

SampleBot

import random

class SampleBot(WhiteElephantBot):
    def rollout(self, values, counts, just_stole, next_move):
        targets = set()
        move_chosen = False
        for i, (v, n) in enumerate(zip(values, counts)):
            if v and n < 3 and i != just_stole and i != 0:
                targets.add(i)
        for i in range(len(values)):
            if values[i]:
                break
            while True:
                if not targets:
                    break
                if move_chosen:
                    j = max(targets, key=lambda i: values[i])
                    if values[j] < 0.5:
                        break
                else:
                    move_chosen = True
                    if next_move is None:
                        break
                    j = next_move
                values[i] = values[j]
                counts[i] = counts[j] + 1
                values[j] = 0
                counts[j] = 0
                if just_stole is not None and counts[just_stole] < 3:
                    targets.add(just_stole)
                if j in targets:
                    targets.remove(j)
                just_stole = i
                i = j
            values[i] = random.random()
            for player in (just_stole, i):
                if player is not None and values[player] and counts[player] < 3:
                    targets.add(player)
        return values[0]
    def take_turn(self, players, presents, just_stole, n_rollouts=2000):
        names = [self.name] + players + list(presents.keys())
        values = [presents[name][0] if name in presents else None for name in names]
        counts = [presents[name][1] if name in presents else 0 for name in names]
        if just_stole is not None:
            just_stole = names.index(just_stole)
        targets = [None]
        for i, (v, n) in enumerate(zip(values, counts)):
            if v and n < 3 and i != just_stole and i != 0:
                targets.append(i)
        if len(targets) == 1:
            return targets[0]
        scores = [0. for _ in targets]
        n = n_rollouts // len(targets)
        for i, target in enumerate(targets):
            for _ in range(n):
                scores[i] += self.rollout(list(values), list(counts), just_stole, target) / float(n)
        target_index = targets[scores.index(max(scores))]
        if target_index is None:
            return None
        return names[target_index]

Exécute 2000 simulations avec chaque joueur agissant avec gourmandise et choisit la meilleure action.

user1502040
la source
Que fait exactement ce bot?
Beefster
@Beefster Exécute 2000 parties aléatoires avec chaque joueur agissant avec gourmandise et choisit le coup avec le score final moyen le plus élevé.
user1502040
Erreur de nom. Vous devez importer au hasard.
Beefster
1

Robin des Bois

class RobinHood(WhiteElephantBot):       
    def take_turn(self, players, presents, just_stole):
        #get the possible steal targets
        targets = self.steal_targets(presents, just_stole)
        #who stole his gift?
        targets = [x for x in targets if presents[x][1] > 0]
        #sort by value
        targets = sorted(targets, key=lambda x: presents[x][0])        
        #only steal back if it's worth it        
        targets = [x for x in targets if presents[x][0] > 0.5]

        if len(targets)>0:
           return targets.pop()

Voler les riches qui n'ont pas gagné leur cadeau

r_j
la source
Vous avez une erreur d'indentation.
Beefster
0

Bon samaritain

class GoodSamaritan(WhiteElephantBot):     
    def take_turn(self, players, presents, just_stole):  
        targets = self.steal_targets(presents, just_stole)

         #if only one player has a gift, don't steal it!
        if len(presents)<=1 or len(targets)==0:
             return None
        else:       
             #Steal the worst present  
             return min(targets, key=lambda x: presents[x][0])

Donnez aux malchanceux une autre chance de bonne fortune

r_j
la source
0

Joueur

class Gambler(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):        
        #get the possible steal targets
        targets = self.steal_targets(presents, just_stole)        

        #last player 
        if len(players)==0:
            #lets gamble! Try and get the highest score
            return None

        #If you are not last, steal the best gift that can be restolen so maybe you can become the last player
        targets = [t for t in targets if presents[t][1]<2 ]
        if targets:
            return max(targets, key=lambda x: presents[x][0])   

Le joueur est accro, il essaie d'être le dernier joueur, puis il joue sur un nouveau cadeau pour battre tous les autres joueurs.

r_j
la source
0

Top3Bot

class Top3Bot(WhiteElephantBot):
    def __init__(self, name):
        super().__init__(name)
        self.firstturn = True

    def take_turn(self, players, presents, just_stole):
        if self.firstturn:
            num_presents = len(players) + len(presents) + 1
            self.value_limit = (num_presents - 3) / num_presents
            self.firstturn = False

        targets = self.steal_targets(presents, just_stole)

        if players:
            targets += None

        return max(
            targets,
            key=lambda name: self.steal_ranking(name, presents, len(players))
        )


    def steal_ranking(self, name, presents, presents_remaining):
        if name is None:
            return (0, 0)

        present_value = presents[name][0]
        num_steals = presents[name][1]
        if present_value >= self.value_limit:
            if num_steals == 2:
                return (5, present_value)
            elif  num_steals == 0:
                return (4, -presemt_value)
            elif num_steals == 1 and presents_remaining == 0:
                return (3, -present_value)
            else:
                return (-1, present_value)
        else:
            if num_steals < 2:
                return (2, present_value)
            else:
                return (-2, present_value)

Ce bot n'essaie pas d'obtenir le meilleur cadeau possible, mais essaie d'obtenir un cadeau qui est évalué> = (n-3) / n, où n est le nombre de cadeaux. Dans la plupart des cas, il y aura des cadeaux très appréciés, et Top3Bot essaiera de mettre la main sur l'un d'entre eux, mais il ne se soucie pas vraiment de ceux qu'il reçoit.

Chouette noire Kai
la source
votre argument __init__manqueself
Beefster