Intercambio de elefante blanco

11

Es Navidad en julio, entonces, ¿qué mejor manera de celebrar que un intercambio virtual de regalos de elefante blanco?

Para este desafío de King of the Hill, debes crear un bot que juegue en una simulación de intercambio de White Elephant , tratando de obtener el regalo más valioso posible.

Reglas del juego

  • El juego se jugará en muchas rondas, cada una compuesta por un número variable de turnos.
  • Configuración de ronda : Habrá tantos regalos como jugadores en el juego, cada uno valorado aleatoriamente de manera uniforme en el rango [0 ... 1), con este valor desconocido hasta que el presente se "abra". Los jugadores se colocarán en un orden aleatorio en una cola. El primer jugador aparecerá desde el frente de la cola.
  • Cuando es el turno de un jugador, pueden abrir un presente o robar el presente de otro jugador, pasando el turno al jugador cuyo presente fue robado.
    • Cada regalo puede ser robado hasta 3 veces.
    • No puedes robarle al jugador que acaba de robarte.
    • Cada jugador solo puede tener un presente a la vez.
  • Después de abrir un regalo, el juego avanza al siguiente jugador que aparece desde el frente de la cola. Este será el siguiente jugador en orden de turno que aún no ha tenido un turno.
  • Fin de ronda : cuando todos los regalos se han abierto, la ronda finaliza y el valor del presente que tiene cada jugador se agrega a la puntuación de ese jugador. Comienza una nueva ronda, con cada jugador ahora sin ningún regalo y el orden del jugador barajado.
  • Fin del juego : el juego finalizará cuando al menos un jugador haya obtenido 100 500 puntos, y la victoria se otorgará al jugador con el mayor valor total de regalos.

Codificación

Todas las presentaciones deben ser compatibles con Python 3.7. Debe escribir una clase que herede directamente de WhiteElephantBot. Por ejemplo:

class FooBot(WhiteElephantBot):
    # Your implementation here

Puede proporcionar un __init__método (que toma un argumento name) en su clase de bot, que debe llamar super().__init__(name). Su clase debe tener un take_turnmétodo que espere los siguientes argumentos en este orden:

  • players: La lista de nombres de jugadores, en orden de turno, de todos los jugadores que aún no tienen regalos.
  • presents: Un diccionario que asigna nombres de jugadores a 2-tuplas que contienen el valor actual que tiene ese jugador y la cantidad de veces que ese regalo ha sido robado. Esto solo incluirá a otros jugadores que actualmente tienen regalos.
  • just_stole: Si la última acción tomada fue un robo, este será el nombre del jugador que acaba de robar. Si no, lo será None.

Cada argumento será inmutable o un objeto nuevo, de modo que mutar cualquiera de ellos no tendrá un efecto en el juego. Puede guardar una copia de cualquiera de los argumentos si así lo desea.

Un valor de ejemplo para presents:

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

Su take_turnmétodo debe devolver el nombre del jugador que desea robar o Noneabrir un regalo. Si genera una excepción, devuelve algo que no sea un stro None, o el nombre de un jugador del que no puedes robar, abrirás un regalo por defecto.

Se llamará a su constructor al comienzo de cada ronda, por lo que no podrá recordar el estado de una ronda a otra.

Al heredar de WhiteElephantBot, tendrás acceso a un steal_targetsmétodo que tomará los dict de los regalos just_stoley devolverá una lista de nombres de jugadores a los que puedes robar.

Cualquier módulo que necesite su script debe importarse en la parte superior de su entrada.

Conductor de prueba

El controlador de prueba se puede encontrar aquí . No necesita incluir from white_elephant import WhiteElephantBoten su respuesta publicada, sin embargo, un módulo local deberá hacerlo.

Competidores de línea de base

  • Aleatorio : elige al azar si abrir un nuevo regalo o robar, con el objetivo de robo elegido de manera uniforme al azar.
  • Codicioso : roba el regalo más valioso que se puede robar. Si no se pueden robar regalos, abra un regalo.
  • Niza : siempre abre un nuevo regalo. Nunca roba

Reglas Adicionales

  • Es su responsabilidad capturar todas las excepciones. Si su clase no logra atrapar una excepción, será descalificada. Además, no atrape KeyboardInterrupts.
  • No use archivos u otros métodos para evitar la imposibilidad de guardar el estado entre juegos. No puede, por ejemplo, guardar un estado de red neuronal en un archivo a mitad de ejecución.
  • Su bot debe ser autónomo dentro del código de clase y las constantes relacionadas.
  • Solo puede usar importaciones de biblioteca estándar.
  • No hay requisitos estrictos de rendimiento. Sea razonable y prudente. Si el rendimiento se convierte en un problema, me reservo el derecho de agregar límites de tiempo.
  • Una entrada por persona. Si envía más de una entrada, es posible que sus bots no funcionen juntos. Voy a permitir múltiples entradas por persona por ahora, aunque puedo volver a modificarlo más adelante si se convierte en un problema.
  • Esta es una competencia abierta sin fecha de finalización distinta. Se volverá a ejecutar cada vez que pueda cuando haya habido cambios significativos.

EDITAR1: Se cambió el puntaje ganador de 100 a 500 para que las clasificaciones sean más consistentes. El controlador de prueba tiene una nueva corrección de errores y también refleja los cambios en la puntuación de victorias.

EDIT2: Nota aclaratoria sobre las importaciones requeridas.


Tabla de clasificación (a partir del 8 de agosto de 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. UmbralBot (390.480)
  8. Jugador (313.362)
  9. NiceBot (275.536)
  10. RandomBot (256.172)
  11. Buen samaritano (136.298)
Carne de res
fuente
¿Puede haber algún número de robos seguidos? Cuando he jugado, generalmente hay un límite de 2 robos seguidos o algo así, y la tercera persona de tendría que abrir uno. Esto evita que el mismo regalo sea robado más de una vez por turno.
mbomb007
@ mbomb007 Sí. El robo en cadena es ilimitado, excepto por otras reglas que hacen que ciertos regalos sean inmunes al robo: cada regalo solo puede ser robado 3 veces y no puedes robarle al jugador que acaba de robarte.
Beefster
¿Puedes robar un regalo y luego volver a robar el original que tenías?
Erik the Outgolfer
@EriktheOutgolfer: sí, siempre que haya otro giro en el medio. Simplemente no puede volver a robar inmediatamente después de que le roben su regalo.
Beefster
1
¿Cambio yanqui? ¿Qué sigue, una fiesta de cumpleaños compartida?
ngm

Respuestas:

3

LastMinuteBot

(Con muchas gracias a @Mnemonic por el esqueleto del código, ya que apenas conozco 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

Aproveche el hecho de que los regalos no pueden ser robados más de tres veces, robe el tercero si encuentra un regalo de alto valor y la mayoría de los regalos se han abierto.

sundar - Restablecer a Monica
fuente
Simple, pero hermoso
r_j
2

Todd extraño

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

Roba el mejor regalo que puede, pero no quiere ser la segunda persona en robar un regalo, porque si se lo roban no puede recuperarlo.

brian_t
fuente
Error de sintaxis en la línea 11. Necesita una comprensión en ==lugar de a =en su lista.
Beefster
arreglado, gracias! No uses mucho 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

Todos lucharán por el regalo más valioso. El siguiente mejor regalo es casi tan bueno, pero es mucho menos probable que sea robado.


fuente
1

UmbralBot

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)

Realmente no nos importa obtener el mejor regalo, solo algo lo suficientemente bueno . Mientras haya algo que valga la pena robar, lo haremos.


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

Ejecuta 2000 simulaciones con cada jugador actuando con avidez y elige la mejor acción.

user1502040
fuente
¿Qué hace exactamente este bot?
Beefster
@Beefster Ejecuta 2000 juegos aleatorios con cada jugador actuando con avidez, y elige el movimiento con el puntaje final promedio más alto.
user1502040
Error de nombre Necesitas importar al azar.
Beefster
1

Robin Hood

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

Robar a los ricos que no ganaron su presente

r_j
fuente
Tienes un error de sangría.
Beefster
0

Buen samaritano

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

Dar a las personas desafortunadas otra oportunidad de buena fortuna

r_j
fuente
0

Jugador

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

El jugador es adicto, trata de ser el último jugador, luego apuesta por un nuevo regalo para vencer a todos los demás jugadores.

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

Este bot no intenta obtener el mejor regalo posible, pero intenta obtener un regalo que se valore> = (n-3) / n, donde n es el número de regalos. En la mayoría de los casos, habrá regalos valorados tanto, y Top3Bot intentará hacerse con uno de estos, pero a él realmente no le importa cuál de ellos recibe.

Black Owl Kai
fuente
su __init__falta de su selfargumento
Beefster