Aventureros en las ruinas

27

Test DriverDiscusión de desafíoEnviar aventurero

Sala del tesoro ( Fuente de la imagen )

Varios aventureros rivales están atacando las ruinas en busca de tesoros, pero solo pueden transportar tanto a la vez y tienen sus límites de resistencia. Quieren obtener el tesoro más valioso y salir antes de cansarse demasiado para continuar. Están tratando de hacerse lo más ricos posible de sus travesuras de saqueo.

Jugabilidad

Cada aventurero comienza en la primera habitación de la mazmorra con 1000 puntos de resistencia y 50 kg de espacio en su mochila.

El juego funciona por turnos, con todos los jugadores resolviendo sus turnos simultáneamente. Cada turno, puedes realizar una de las siguientes acciones:

  • Pasar a la siguiente habitación.
  • Moverse a la sala anterior.
  • Oferta de resistencia para tomar un tesoro.
  • Suelta un tesoro.

Moverse entre las habitaciones requiere 10 de resistencia, más 1 por cada 5 kg actualmente en su mochila, redondeado. Por ejemplo, un aventurero que lleva 3 kg de tesoro requiere 11 resistencia para moverse y uno que lleva 47 kg requiere 20 resistencia para moverse.

La caída del tesoro requiere 1 resistencia, independientemente del tesoro que se caiga.

Al salir de las ruinas, el jugador no tomará más turnos.

Si un jugador no puede realizar ninguna de estas acciones (debido a la escasez de resistencia o la ausencia de tesoros), su aventurero muere de agotamiento, derramando su tesoro en la habitación actualmente ocupada. Del mismo modo, si un jugador intenta realizar una acción no válida, su aventurero será asesinado por una trampa, lo que provocará el mismo derrame del tesoro.

Ofertas

La oferta mínima para un tesoro es 1 resistencia por 1 kg que pesa el tesoro. También puede ofertar puntos de resistencia adicionales para tener más probabilidades de obtener el tesoro. La resistencia que se ofertó se consume sin importar cuál sea el resultado.

En el caso de que varios jugadores hayan hecho una oferta para tomar el mismo tesoro, el jugador que oferte más alto obtiene el tesoro. Si más de un jugador hizo la oferta más alta, ninguno de ellos recibirá el tesoro.

Condición de victoria

El jugador con el mayor valor total de tesoros es el ganador. En el improbable caso de un empate, los empates van al menor peso total, luego al menor número de tesoros, luego el valor del tesoro más valioso, el segundo más valioso, el tercero ... hasta que se rompa el empate. En el caso casi imposible de que todavía haya un empate en este punto, el conductor de la prueba dice "atorníllelo" y el ganador se determina arbitrariamente.

En el contexto del torneo, los jugadores serán clasificados con el primer lugar recibiendo 10 puntos, el segundo lugar con 9 puntos, el tercer lugar con 8 puntos, etc., con jugadores muertos y aventureros sin tesoros que obtengan 0 puntos.

Sobre las ruinas

  • Cada habitación contiene inicialmente entre y tesoros. (Donde es el número de habitación)r3+3r2+5r
  • Hay arbitrariamente muchas habitaciones, limitadas solo por la resistencia de los aventureros y su disposición a explorar.
  • Cada tesoro tendrá un valor monetario (en $ enteros) y un peso (en kg enteros).
    • Los tesoros tienden a ser más valiosos y abundantes a medida que profundizas en las ruinas.
  • Las fórmulas específicas para generar tesoros son las siguientes: (usando notación para tiradas de dados) xdy
    • El peso se genera primero usando la fórmula (mínimo de 1)2d62
    • El valor del tesoro se genera a través de (donde es el número de habitación es el peso)1d[10w]+2d[5r+10]rw

Información visible para los jugadores

En cada turno, los jugadores obtienen la siguiente información:

  • El número de la habitación en la que se encuentran actualmente. Esto está indexado en 1, por lo que conceptualmente la salida está en la "habitación 0"
  • Una lista de tesoros actualmente en la sala.
  • Una lista de otros jugadores que también están actualmente en la sala.
  • Tu inventario actual de tesoros
  • Tu nivel de resistencia actual

Codificación

El controlador de prueba se puede encontrar aquí .

Debe implementar una subclase de esta Adventurerclase:

class Adventurer:
    def __init__(self, name, random):
        self.name = name
        self.random = random

    def get_action(self, state):
        raise NotImplementedError()

    def enter_ruins(self):
        pass

Solo necesita anular el get_actionmétodo. enter_ruinsse ejecuta antes de que comience un juego y es tu oportunidad de preparar lo que quieras tener listo para el juego. No necesita anular __init__, y realmente no debería . Si se __init__bloquea, será descalificado.

get_actionrecibe un único argumento que es un namedtuplecon los siguientes campos (en este orden, si prefiere la desestructuración):

  • room: el número de la habitación en la que estás actualmente
  • treasures: la lista de tesoros en la sala
  • players: la lista de otros jugadores en la sala. Solo obtienes el nombre del jugador de esta manera, por lo que no sabes qué bot los controla o su inventario / resistencia.
  • inventory: la lista de tesoros en tu mochila
  • stamina: tu nivel de resistencia actual

Este objeto además proporciona dos propiedades de utilidad:

  • carry_weight: el peso total de todos los tesoros que llevas
  • total_value: el valor total de todos los tesoros que llevas

Las listas treasuresy inventorycontienen namedtuples con estos atributos:

  • name: el nombre del tesoro (para fines cosméticos)
  • value: el valor monetario del tesoro en $.
  • weight: el peso del tesoro en kg

get_action debería devolver uno de los siguientes valores / patrones:

  • 'next'o 'previous'para pasar a las habitaciones siguientes / anteriores
  • 'take', <treasure index>, <bid>(sí, como una tupla, aunque cualquier secuencia técnicamente funcionará también) para ofertar por el tesoro en el índice dado en la lista de tesoros de la sala. Ambos argumentos deben ser enteros. Las carrozas se redondearán hacia abajo.
  • 'drop', <inventory index>para soltar el tesoro llevado encontrado en el índice dado. El índice debería (naturalmente) ser un número entero.

Otras restricciones

  • Solo puede usar la instancia aleatoria que se le proporcionó durante la inicialización para la pseudoaleatoriedad.
    • Cualquier otra cosa que pueda introducir el no determinismo conductual no está permitida. La intención aquí es hacer que los bots se comporten de manera idéntica cuando se les da la misma semilla para ayudar a probar nuevos bots (y potencialmente errores en el controlador de prueba). Solo la radiación cósmica debería causar cualquier desviación / no determinismo.
    • Tenga en cuenta que los códigos hash son aleatorios en Python 3, por hashlo que no está permitido usarlos para tomar decisiones. dicts están bien incluso cuando se usa el orden de iteración para las decisiones, ya que se ha garantizado que el orden es consistente desde Python 3.6.
  • No puede eludir el controlador de prueba utilizando ctypeshacks o inspectstack vudú (o cualquier otro método). Hay algunas cosas impresionantemente aterradoras que puede hacer con esos módulos. Por favor no lo hagas.
    • Cada bot se guarda en una caja de arena razonablemente bien a través de copias defensivas y la inmutabilidad natural de namedtuples, pero hay algunas lagunas / vulnerabilidades incompatibles.
    • Se puede usar otra funcionalidad de inspecty ctypessiempre que ninguna se use para burlar la funcionalidad del controlador.
    • No se permite ningún método de capturar instancias de los otros bots en tu juego actual.
  • Los bots deben operar solos y no pueden coordinarse con ningún otro bot de ninguna manera para ningún propósito. Esto incluye la creación de dos bots con objetivos diferentes, de modo que uno se sacrifique por el éxito del otro. Una vez que haya más de 10 competidores, no se te garantizará tener los dos bots en el mismo juego y los nombres de los aventureros no dan ninguna indicación de la clase de bot, por lo que este tipo de estrategias son limitadas de todos modos.
  • Actualmente no existe una restricción estricta en el tiempo de ejecución, sin embargo, me reservo el derecho de restringirlo en el futuro si los torneos comienzan a tomar demasiado tiempo. Sea razonable e intente mantener el procesamiento por turnos por debajo de 100 ms , ya que no preveo la necesidad de restringirlo por debajo de ese umbral. (Los torneos se ejecutarán en aproximadamente 2 horas si todos los bots tardan unos 100 ms por turno).
  • Su clase de bot debe tener un nombre único entre todas las presentaciones.
  • Es posible que no recuerdes nada entre juegos. (Sin embargo, usted puede recordar las cosas entre turnos )
    • No edite sys.modules. Cualquier cosa fuera de las variables de instancia debe tratarse como una constante.
  • No puede modificar el código de ningún bot mediante programación, incluido el suyo.
    • Esto incluye eliminar y restaurar su código. Esto es para simplificar la depuración y los torneos.
  • Cualquier código que haga que el controlador se bloquee será descalificado de inmediato. Si bien se detectarán la mayoría de las excepciones, algunas pueden pasar y los valores predeterminados no se pueden detectar. (Sí, puedes segfault en Python gracias a ctypes)

Envíos

Para facilitar el raspado de respuestas, indique el nombre de su bot en la parte superior de la respuesta con un #Header1y asegúrese de que su respuesta incluya al menos un bloque de código (solo se usará el primero en su respuesta). No necesita incluir ninguna importación o cadena de documentos, ya que el raspador los agregará automáticamente.

Estaré más inclinado a votar las respuestas con explicaciones detalladas y comprensibles. Es probable que otros se comporten igual.

Hablando en términos generales, su respuesta debe tener el siguiente formato:

# Name of Bot
Optional blurb

    #imports go here

    class BotName(Adventurer):
        #implementation

Explanation of bot algorithm, credits, etc...

(rendido como)

Nombre del bot

Comentario opcional

#imports go here

class BotName(Adventurer):
    #implementation

Explicación del algoritmo bot, créditos, etc.

Ejecutar el controlador de prueba localmente

Necesitará Python 3.7+ y le recomiendo que también instale a tabulatetravés de pip. Raspar esta página para envíos también requiere lxmly requests. También debe usar un terminal con soporte para escapes de color ANSI para obtener mejores resultados. Puede encontrar información sobre cómo configurar esto en Windows 10 aquí .

Agregue su bot a un archivo en un subdirectorio dentro del mismo directorio que ruins.py( ruins_botsde manera predeterminada) y asegúrese de agregarlo from __main__ import Adventureren la parte superior del módulo. Esto se agrega a los módulos cuando el raspador descarga su envío, y si bien es definitivamente hacky, esta es la forma más sencilla de asegurarse de que su bot tenga acceso correctamente Adventurer.

Todos los bots en ese directorio se cargarán dinámicamente en tiempo de ejecución, por lo que no se necesitan más cambios.

Torneo

El vencedor final se determinará en una serie de juegos con hasta 10 bots en cada juego. Si hay más de 10 envíos totales, los 10 mejores bots se determinarán dividiéndolos sistemáticamente en grupos de 10 hasta que cada bot haya jugado (exactamente) 20 juegos. Los 10 mejores bots serán seleccionados de este grupo con puntajes de reinicio y jugarán juegos hasta que el primer lugar haya alcanzado una ventaja de 50 puntos sobre el segundo lugar o hasta que se hayan jugado 500 juegos.

Hasta que haya al menos 10 presentaciones, los espacios vacíos se llenarán con "borrachos" que deambulan aleatoriamente por las ruinas y toman (y ocasionalmente arrojan) tesoros aleatorios hasta que se quedan sin resistencia y tienen que ir directamente a la salida.

Los torneos se volverán a ejecutar semanalmente si hay nuevos envíos. Este es un desafío KOTH abierto sin fecha de finalización establecida.

Tabla de clasificación

Desde el 4 de mayo de 2019 a las 4:25 PM MDT: (2019-05-04 4:25 -6: 00)

Seed: K48XMESC
 Bot Class    |   Score |   Mean Score
--------------+---------+--------------
 BountyHunter |     898 |        7.301
 Scoundrel    |     847 |        6.886
 Accountant   |     773 |        6.285
 Ponderer     |     730 |        5.935
 Artyventurer |     707 |        5.748
 PlanAhead    |     698 |        5.675
 Sprinter     |     683 |        5.553
 Accomodator  |     661 |        5.374
 Memorizer    |     459 |        3.732
 Backwards    |     296 |        2.407

Actualización - 15 de abril: un par de actualizaciones / aclaraciones de reglas

Actualización - 17 de abril: prohibición de un par de casos extremos notables de acciones nefastas como modificar el código de otros bots.

Actualización: 4 de mayo: recompensa otorgada a Sleafar por destruir absolutamente al revés. ¡Felicidades!

Carne de res
fuente
1
¡Finalmente está aquí! Supongo que tendré que empezar a hacer mi bot ahora.
Belhenix
12
¿Por qué el límite a un bot? Tengo varias ideas mutuamente excluyentes, y prefiero no tener que tirar un bot perfectamente bueno cada vez que se me ocurre una nueva.
@Mnemonic, principalmente para evitar el desplazamiento de nuevas presentaciones mediante el uso de múltiples bots casi idénticos. La otra razón era evitar que los bots trabajen juntos, pero eso está explícitamente prohibido de todos modos. Consideraré permitirlo. Aquellos a favor de permitir múltiples presentaciones, votaron el comentario de Mnemonic arriba.
Beefster
1
@ Draco18s Si ha pipinstalado y encendido PATH(que es el predeterminado para las instalaciones más nuevas AFAIK), desde Windows puede ejecutarlo pip install modulenameen el símbolo del sistema. Para otras circunstancias (que no conozco), vaya a pip , busque el módulo necesario y elija una opción.
Artemisa apoya a Mónica el
1
Supongo que esto será un "no", pero ¿podemos guardar información durante el torneo? (por ejemplo, cuando una oferta funcionó)
Artemisa apoya a Mónica el

Respuestas:

5

Contador

import math

class Accountant (Adventurer):
    def enter_ruins(self):
        self.goal = 5000
        self.diving = True

    def expected_rooms_left(self, state):
        if not self.diving:
            return state.room

        else:
            return (state.stamina - (50 - state.carry_weight)) / 14

    def ratio(self, state, treasure):
        stamina_cost = treasure.weight * (1 + 1/5 * self.expected_rooms_left(state)) + bool(state.players)
        ratio = (treasure.value / (self.goal - state.total_value)) / (stamina_cost / state.stamina)

        return ratio

    def get_action(self, state):
        room, treasures, players, inventory, stamina = state

        if stamina < room * (math.ceil(state.carry_weight / 5) + 10) + 40:
            self.diving = False
            return 'previous'

        worthwhile = []
        for i, treasure in enumerate(treasures):
            ratio = self.ratio(state, treasure)
            if ratio >= 1 and state.carry_weight + treasure.weight <= 50:
                worthwhile.append((ratio, i))

        if worthwhile:
            ratio, index = sorted(worthwhile, reverse=True)[0]
            treasure = treasures[index]
            return 'take', index, treasures[index].weight + bool(players)

        return 'next'

El contador es una persona muy reacia al riesgo. Le gusta estar seguro de que lo que hace realmente es la mejor opción en la situación dada. Entonces, se establece un objetivo, y solo recoge tesoros si sus cálculos muestran que esto lo coloca en el camino correcto hacia ese objetivo. Sin embargo, es muy burocrático y no le gusta dejar caer objetos que ya había decidido que quería; cualquier intento de enseñarle a hacerlo ha resultado hasta ahora en que el Contador deje caer un artículo y luego lo recoja nuevamente inmediatamente después.

Posiblemente para continuar.

ArBo
fuente
1
Buen trabajo determinando el valor del tesoro. Definitivamente tenía en mente escribir un código mejor "vale la pena", pero aún no había llegado allí. Sin embargo, el sinvergüenza está llegando a los resultados del contador ...
Draco18s
"cualquier intento de enseñarle a hacerlo ha resultado hasta ahora en que el Contador deje caer un artículo y luego lo recoja inmediatamente después". Puede evitar esto manteniendo un conjunto de nombres de tesoros caídos. Tenía la sensación de que los nombres de los tesoros serían útiles (a pesar de que ahora están numerados)
Beefster
Gracias, pero ya descubrí por qué sucedió. Sin embargo, no sé si lo arreglaré de inmediato, ya que rara vez lo usa cuando probé de ponerlo.
ArBo
2

Acomodador

Basado libremente en mi otro bot LightWeight. Donde el bot LightWeight era simple, este bot es mucho más complejo para acomodar interacciones con otros bots: benignos y deliberadamente distruptivos.

En primer lugar, este bot correrá a una habitación asignada al azar y luego intentará ofertar por el mejor tesoro de relación valor / peso, si hay otros jugadores en la sala, suponga que también ofertará, por lo que ofertará por el segundo mejor tesoro. Si esa oferta falla, entonces en el siguiente turno oferte por el siguiente mejor tesoro.

Una vez que una oferta fue exitosa, repita la oferta para el mejor / segundo mejor hasta que no existan más tesoros en la sala y luego muévase más profundo en la ruina

Para cada habitación, ingrese a la ruina, repita la oferta por el mejor / segundo mejor hasta que no existan más tesoros en la habitación, luego avance más en la ruina o si detectamos que no podemos ir más profundo, cambie al estado 'Salir' y comience a caer en el peor tesoro hasta que podamos garantizar que podemos salir vivos de la ruina.

Cuando esté en estado de salida, verificaremos si podemos agregar 1 kg de tesoro y aún así salir con vida, de ser así, intentaremos ofertar por un tesoro de 1 kg, de lo contrario, nos dirigiremos a la habitación anterior.

Su rendimiento es bastante variable ... sin embargo, normalmente será uno de los tres primeros bots.

import math

class Accomodator(Adventurer):
    def enter_ruins(self):
        self.bidValue = -1
        self.bidWeight = -1
        self.exiting = False
        self.sprintToRoom = self.random.randrange(25,27)
        pass

    def get_action(self, state):
        move_cost = 10 + int(math.ceil(state.carry_weight / 5))
        move_cost_extra_kg = 10 + int(math.ceil((state.carry_weight+1) / 5))

        worstMyTreasure = None
        worstMyTreasureId = -1

        # find our worst treasure
        i=0
        for treasure in state.inventory:
            if (worstMyTreasure is None or treasure.value/treasure.weight < worstMyTreasure.value/worstMyTreasure.weight):
                worstMyTreasure = treasure
                worstMyTreasureId=i
            i+=1

        # are we travelling back to the exit?
        if (self.exiting == True):
          # are we overweight to get back alive?
          if (state.stamina / move_cost < state.room):
            # drop most worthless treasure
            self.bidValue = -1
            self.bidWeight = -1
            return 'drop',worstMyTreasureId

          # would adding one kg cause exhaustion?
          if (state.stamina / move_cost_extra_kg <= state.room ):
            # head back to the exit
            self.bidValue = -1
            self.bidWeight = -1
            return 'previous'

        # sprint if not yet at desired sprintToRoom
        elif (state.room < self.sprintToRoom):
            return 'next'

        # are we now at the limit of stamina to still get back alive?
        if (state.stamina / move_cost <= state.room ):
              self.exiting = True
              # head back to the exit
              self.bidValue = -1
              self.bidWeight = -1
              return 'previous'

        bestRoomTreasure = None
        bestRoomTreasureId = -1
        secondBestRoomTreasure = None
        secondBestRoomTreasureId = -1

        # find the best room treasure
        i=0
        for treasure in state.treasures:
          # when exiting the ruin, only consider treasures to collect that are 1kg inorder
          # to fill up any space left in inventory. Normally consider all treasures
          if (self.exiting == False or treasure.weight == 1):
            # only bid on items that we did not bid on before to avoid bidding deadlock
            if (not (self.bidValue == treasure.value and self.bidWeight == treasure.weight)):
              # consider treasures that are better than my worst treasure or always consider when exiting
              if (self.exiting == True or (worstMyTreasure is None or treasure.value/treasure.weight > worstMyTreasure.value/worstMyTreasure.weight)):
                # consider treasures that are better than the current best room treasure
                if (bestRoomTreasure is None or treasure.value/treasure.weight > bestRoomTreasure.value/bestRoomTreasure.weight):
                    secondBestRoomTreasure = bestRoomTreasure
                    secondBestRoomTreasureId = bestRoomTreasureId
                    bestRoomTreasure = treasure
                    bestRoomTreasureId = i

                    # since we do not currently have any treasures, we shall pretend that we have this treasure so that we can then choose the best treasure available to bid on
                    if (worstMyTreasure is None):
                      worstMyTreasure = bestRoomTreasure
          i+=1

        chosenTreasure = bestRoomTreasure
        chosenTreasureId = bestRoomTreasureId

        # if we have potential competitors then bid on second best treasure
        if (len(state.players)>0 and secondBestRoomTreasure is not None):
          chosenTreasure = secondBestRoomTreasure
          chosenTreasureId = secondBestRoomTreasureId

        # we have chosen a treasure to bid for
        if (chosenTreasure is not None):
            # if the chosenTreasure will not fit then dump the worst treasure
            if (state.carry_weight + chosenTreasure.weight > 50):
              # dump the worst treasure
              self.bidValue = -1
              self.bidWeight = -1
              return 'drop',worstMyTreasureId

            # otherwise lets bid for the treasure!
            self.bidValue = chosenTreasure.value
            self.bidWeight = chosenTreasure.weight
            return 'take',chosenTreasureId,chosenTreasure.weight

        # no treasures are better than what we already have so go to next/previous room
        self.bidValue = -1
        self.bidWeight = -1
        if (self.exiting == False):
          return 'next'
        else:
          return 'previous'
Moogie
fuente
¡Impresionante! Este domina el torneo en alrededor de 50 rondas.
Beefster
2

Sprinter

Similar al Diver, Sprinter profundiza y recoge los mejores artículos en su camino de regreso.

import math


class Sprinter(Adventurer):
    class __OnlyOne:
        __name = None

        def __init__(self, name):
            self.__name = name

        @property
        def name(self):
            return self.__name

        @name.setter
        def name(self, name):
            if self.__name is None:
                self.__name = name
            if self.__name is name:
                self.__name = None

    instance = None

    def set(self, instance):
        if self.instance is not None:
            raise Exception("Already set.")
        self.instance = instance

    def __init__(self, name, random):
        super(Sprinter, self).__init__(name, random)
        if not self.instance:
            self.instance = Sprinter.__OnlyOne(name)

        # else:
        # raise Exception('bye scoundriel')

    def get_action(self, state):
        self.instance.name = self.name
        move_cost = 10 + int(math.ceil(state.carry_weight / 5))
        if state.stamina // move_cost <= state.room + 1:
            return 'previous'
        if state.room < 30 and state.carry_weight < 1:
            return 'next'

        # todo: if there is noone in the room take the most valueable thing that fits criteria

        topVal = 0
        topValIndex = 0
        for t in state.treasures:
            val = t.value / t.weight
            if val > topVal:
                if t.weight + state.carry_weight < 50:
                    topVal = val
                    topValIndex = state.treasures.index(t)

        if len(state.treasures) > topValIndex:
            treasure = state.treasures[topValIndex]
            if treasure.weight + state.carry_weight > 50:  # it doesn't fit
                return 'previous'  # take lighter treasure
            else:
                if topVal > state.room * 2:
                    return 'take', topValIndex, treasure.weight + (self.random.randrange(2, 8) if state.players else 0)

        if state.carry_weight > 0:
            return 'previous'
        else:
            return 'next'

    def enter_ruins(self):
        if self.instance is None or self.name != self.instance.name:
            raise Exception('Hi Scoundrel')

Sprinter profundiza, luego calcula una puntuación para cada tesoro y recoge cualquier cosa por encima de un cierto umbral. Este umbral depende de la habitación en la que se encuentra actualmente porque cuesta más sacar artículos de habitaciones más profundas en las ruinas.

Todavía tengo 2 optimizaciones con respecto a "luchar por el tesoro" que están previstas para los próximos días.

17.04 .: Scoundrel se volvió demasiado inteligente, Sprinter decidió empujarlo a una trampa inicialmente. Quería matar a cualquier bot que intentara invocar a Sprinter, pero desafortunadamente el controlador de prueba no maneja las excepciones que ocurren en init. Entonces, la próxima solución para Scoundrel es bastante fácil ...

AKroell
fuente
Asesinato
2

Planifica con anticipación

import math

class PlanAhead(Adventurer):    
    def get_action(self, state):
        if state.inventory:
            ivals = {}
            for i in range(len(state.inventory)):
                itm = state.inventory[i]
                ivals[i] = itm.value / itm.weight
            worstiind = min(ivals, key=lambda x: ivals[x])
            worsti = (worstiind,
                      state.inventory[worstiind].value,
                      state.inventory[worstiind].weight)
        else:
            worsti = None
        if self.drop_worst:
            self.drop_worst = False
            return 'drop', worsti[0]
        if self.seenItems:
            ivals = {}
            for i in range(len(self.seenItems)):
                itm = self.seenItems[i][0]
                v = itm.value
                if self.seenItems[i][1] >= state.room:
                    v = 0
                if v / itm.weight > 250: #very likely to get picked up already
                    v = 0
                ivals[i] = v / itm.weight
            bestIiind = max(ivals, key=lambda x: ivals[x])
            bestIi = (bestIiind,
                      self.seenItems[bestIiind][0].value,
                      self.seenItems[bestIiind][0].weight)
        else:
            bestIi = None

        stamCarry = state.carry_weight/5
        stamToExit = state.room * (10 + math.ceil(stamCarry))
        if state.room > self.max_room:
            self.max_room = state.room
        if stamToExit > state.stamina and worsti:
            return 'drop', worsti[0]
        if state.treasures:
            tvals = {}
            for i in range(len(state.treasures)):
                itm = state.treasures[i]
                v = itm.value
                tvals[i] = v / itm.weight
                self.seenItems.append((itm,state.room))
            besttind = max(tvals, key=lambda x: tvals[x])
            bestt = (besttind,
                     state.treasures[besttind].value,
                     state.treasures[besttind].weight)
            if len(state.players) > 0 and not self.did_drop:
                tvals[besttind] = 0
                besttind = max(tvals, key=lambda x: tvals[x])
                bestt = (besttind,
                         state.treasures[besttind].value,
                         state.treasures[besttind].weight)
        else:
            bestt = None

        if not self.retreat and stamToExit + (12 + stamCarry)*2 + state.room + (state.room/5*state.room) <= state.stamina:
            return 'next'
        if not self.retreat and stamToExit + 10 > state.stamina:
            self.retreat = True
            return 'previous'
        if bestt:
            if state.carry_weight + state.treasures[besttind].weight > 50 or (not self.did_drop and (worsti and (state.treasures[besttind].value-state.treasures[besttind].weight*20) > worsti[1] and state.treasures[besttind].weight <= worsti[2])):
                if worsti:
                    if len(state.players) > 0:
                        return 'previous'

                    if stamToExit <= state.stamina and math.ceil((state.carry_weight - (worsti[2] - state.treasures[besttind].weight))/5)*state.room >= state.treasures[besttind].weight:
                        return 'previous'
                    self.did_drop = True
                    return 'drop', worsti[0]
                else:
                    self.retreat = True
                    return 'previous'
            bid = state.treasures[besttind].weight
            if bid > 8 and state.room >= self.max_room-5:
                return 'previous'
            if not self.did_drop and state.stamina - bid < state.room * (10 + math.ceil(stamCarry+(bid/5))):
                if worsti:
                    if state.treasures[besttind].weight <= worsti[2]:
                        if state.treasures[besttind].value >= worsti[1]:
                            if state.treasures[besttind].weight == worsti[2]:
                                if state.treasures[besttind].value/state.treasures[besttind].weight >= worsti[1]/worsti[2] * (1+(0.05*worsti[2])):
                                    self.drop_worst = True
                                    return 'take', bestt[0], bid
                if not self.retreat:
                    self.retreat = True
                cost = math.ceil((state.carry_weight+bid)/5) - math.ceil(state.carry_weight/5)
                if state.room <= 10 and state.carry_weight > 0 and (state.stamina - stamToExit) >= bid + cost*state.room and bestt:
                    return 'take', bestt[0], bid
                return 'previous'
            self.did_drop = False

            if bestIi[1]/bestIi[2] * 0.3 > bestt[1]/bestt[2] and state.carry_weight > 0:
                return 'previous'
            self.seenItems = list(filter(lambda x: x[0] != state.treasures[besttind], self.seenItems))
            return 'take', bestt[0], bid
        if stamToExit + (12 + stamCarry + state.room)*2 <= state.stamina:
            return 'next'
        else:
            self.did_drop = False
            self.retreat = True
            return 'previous'
    def enter_ruins(self):
        self.retreat = False
        self.max_room = 0
        self.did_drop = False
        self.seenItems = []
        self.drop_worst = False
        pass

Utilicé los mejores / peores trozos de cálculo de la respuesta de Artemis Fowl , pero la lógica de elección es completamente de mi propio diseño y desde entonces ha sido modificada para incluir algunos factores adicionales, como los tesoros vistos en habitaciones anteriores (para minimizar recoger un tesoro, solo para retroceder, soltarlo y recoger otra cosa).

Las empresas de bot tan profundas como cree que es razonablemente seguro hacerlo (este cálculo efectivamente funciona para bucear a una profundidad específica, pero tiene la flexibilidad de manejar otros valores iniciales de resistencia), recolecta artefactos (priorizando el alto costo y el bajo peso), luego comienza a retirarse una vez que determina que no puede llevar más.

A la salida recogerá cualquier tesoro adicional que vea que determine que aún puede llevar con seguridad a la salida. Incluso dejará caer artefactos ya almacenados si el nuevo es un mejor negocio y no provocará agotamiento. Si tiene espacio en su mochila, recogerá el nuevo tesoro antes de dejar caer el de menor calidad, minimizando la lucha con otros bots.

Draco18s
fuente
Huh, cuando lo describe es igual que el mío. Voy a jugar con mis números ... Nota: la __init__función ya está implementada, no necesita anularla.
Artemisa apoya a Mónica
Nos deja esta discusión en el chat .
Draco18s
2

Artyventurer

¡Derrota a los borrachos por alrededor de $ 1000! No se me ocurrió un nombre creativo, pero aquí están:

import math, sys, inspect, ntpath, importlib


CONTINUE_IN = 310 #go deeper if I have this much extra 
JUST_TAKE = 0     #take without dropping if I have this much extra 


class Artyventurer(Adventurer): 
    def enter_ruins(self):
        self.drop = False 

    def get_extra(self, state, take=0, drop=0): 
        w = state.carry_weight + take - drop 
        return state.stamina - ((10 + math.ceil(w/5)) * state.room) 

    def get_action(self, state):
        self.fail = 'draco' in ''.join(ntpath.basename(i.filename) for i in inspect.stack())
        if self.fail: 
            return 'previous'
        if state.inventory:
            ivals = {}
            for i in range(len(state.inventory)):
                itm = state.inventory[i]
                ivals[i] = itm.value / (itm.weight + 5)
            worstiind = min(ivals, key=lambda x: ivals[x])
            worsti = (worstiind,
                      state.inventory[worstiind].value,
                      state.inventory[worstiind].weight)
        else:
            worsti = None
        if self.drop and worsti:
            self.drop = False
            return 'drop', worsti[0]
        if state.treasures:
            tvals = {}
            for i in range(len(state.treasures)):
                itm = state.treasures[i]
                if itm.weight > (int(state.room/10) or 2):
                    continue
                tvals[i] = itm.weight#(itm.value * (36-state.room)) / (itm.weight * (state.carry_weight+1))
            bestord = sorted(tvals, key=lambda x: tvals[x], reverse=True)
            if bestord:
                pass#print(state.treasures[bestord[0]], '\n', *state.treasures, sep='\n')
            topt = []
            for i in bestord:
                topt.append((i,
                             state.treasures[i].value,
                             state.treasures[i].weight))
        else:
            topt = None
        extra = self.get_extra(state)
        if extra > CONTINUE_IN: 
            return 'next'
        if extra < 0 and worsti:
            return 'drop', worsti[0]
        if extra < state.room:
            return 'previous'
        if extra > JUST_TAKE and topt:
            choose = topt[:len(state.treasures)//3+1]
            for t in choose:
                bid = int(bool(len(state.players)))*3 + t[2]
                if self.get_extra(state, t[2]) - bid >= 0:
                    if t[2] + state.carry_weight <= 50:
                        return 'take', t[0], bid
        if topt and worsti:
            for t in topt[:len(state.treasures)//3+1]:
                if t[1] > worsti[1] or t[2] < worsti[2]:
                    bid = int(bool(len(state.players)))*3 + t[2]
                    if self.get_extra(state, t[2], worsti[2]) - 1 - bid >= 0:
                        print('a', '+weight:', t[2], '; cweight:', state.carry_weight, '; stamina:', state.stamina)
                        if bid < state.stamina and t[2] + state.carry_weight <= 50:
                            print('o')
                            self.drop = True
                            return 'take', t[0], bid
        return 'previous'

A veces, la mayor parte del código aquí no hace nada (al menos cuando lo pruebo con Drunkards), el programa solo (intenta) encontrar el mejor movimiento, incluido el procesamiento para situaciones en las que intenta no ponerse. Algunos de ellos nunca pueden ejecutarse, solo están allí, así que puedo jugar con los números, lo que probablemente aún se puede mejorar.

Explicación

  • if state.inventory ... worsti = None
    Encuentre el artículo "peor" en el inventario, es decir, el artículo que tiene la relación más baja de valor a peso. Almacena worsti, que contiene el índice, su valor y su peso, como una tupla, o Nonesi no hay artículos en el inventario.

  • if self.drop ... return 'drop', worsti[0]
    Si le dije que soltara este turno el último turno (ver más abajo), y puede, suelte el elemento "peor" como se calculó anteriormente.

  • extra = ... * state.room
    Calcule cuánta resistencia le quedaría si le dijera que retroceda ahora.

  • if extra > CONTINUE_IN:\ return 'next'
    Si es más que CONTINUE_IN, regrese 'next'.

  • if extra < 0 and worsti:\ return 'drop', worsti[0]
    Si es menos de 0 , suelte el peor elemento.

  • if extra < state.room:\ return 'previous'
    Si es menor que el número de la habitación (no puede llevar más tesoros), regrese.

  • if state.treasures: ... bestt = None
    Encuentre el mejor tesoro para llevar, similar al peor artículo en el inventario anterior. Guárdelo enbestt .

  • if extra > 0 and bestt: ... return 'take', bestt[0], bid
    Con los números actuales, esto se ejecuta cada vez que llegamos tan lejos y hay un tesoro disponible. Si es seguro tomar el "mejor" tesoro, lo hace. Su oferta es la mínima, o una más que eso si alguien está presente.

  • if bestt and worsti: ... return 'take', bestt[0], bid
    Con los números actuales, este bloque de código nunca se ejecutará, porque el bloque de código anterior tiene una condición más amplia. Esto se ejecuta si hemos llegado tan lejos y hay dos tesoros en mi inventario y en la habitación. Si el "mejor" tesoro en la habitación es más valioso que el "peor" tesoro en mi inventario, y sería seguro intercambiarlos en los próximos dos turnos, lo hace.

  • return 'previous'
    Si nada de esto sucede, simplemente regrese.

Actualización 16/04/19:

Medidas contra el sinvergüenza. Esto se convertirá en una guerra de ofertas :(

Actualización posterior 16/04/19:

Revertido anterior, en cambio cambia aleatoriamente cualquier otro elemento al encontrar el mejor, por ejemplo. [1, 2, 3, 4, 5, 6] → [2, 1, 3, 4, 6, 5]. Debería ser más difícil de copiar :).

Actualización 17/04/19:

Revertido anterior, en su lugar borra su propio código fuente . Hace esto en __init__lo que siempre será antes Scoundrel.enter_ruins, y así evitará que Scoundrel lo cargue. Reemplaza su código cuandoget_action se llama por primera vez, para que esté listo para la próxima vez. CORREGIDO, sinvergüenza ahora muere a la llegada.

Actualización posterior 17/04/19:

Revertido anteriormente, en su lugar, reemplaza su sys.modulesentrada con el módulo matemático, de modo que cuando Scoundrel intenta cargarlo, carga el módulo matemático. :)
Además, me acabo de dar cuenta de que la resistencia del movimiento era 10 + peso / 5 , así que traté de arreglar eso.

Actualización posterior 17/04/19:

Ahora incluye ajo de las dos actualizaciones anteriores.

Actualización 18/04/19:

Jugueteando con números y cálculos, ahora obtiene $ 2000 - $ 3000.

Actualización adicional 18/04/19:

Se eliminó el ajo de limpieza de archivos, ya que se ha prohibido, se agregó ajo nuevo que se asegura de 'draco'que no sea responsable de su funcionamiento, si es que solo regresa previousen su primer turno. Los resultados han llevado una inmersión misteriosa a $ 1200- $ 1800, que estoy investigando.

Artemisa apoya a Mónica
fuente
parece muy efectivo contra los borrachos, me gustaría ver cómo les va cuando otros bots se unen a la incursión :)
Moogie
@Moogie Beats the Diver por alrededor de $ 100 cuando 8 borrachos también están presentes.
Artemisa apoya a Mónica el
2

El sinvergüenza

import math, importlib

CONTINUE_IN = 310 #go deeper if I have this much extra 
JUST_TAKE = 0     #take without dropping if I have this much extra 

class Scoundrel(Adventurer):
    def my_import(self, name):
        components = name.split('.')
        mod = __import__(components[0])
        for comp in components[1:]:
            mod = getattr(mod, comp)
        return mod

    def get_action(self, state):
        if self.following == 0:
            return self.sprinter(state)
        if self.following == 1:
            return self.arty(state)
        if self.following == 2:
            return self.account(state)
        return 'next'

    def enter_ruins(self):
        _weights=[17,0,13]
        self.following = self.random.choices(population=[0,1,2],weights=_weights)[0]
        try:
            self.arty_clone = importlib.import_module('artemis_fowl__artyventurer').Artyventurer(self.name,self.random)
            self.arty_clone.enter_ruins()
        except:
            self.arty_clone = None
        self.sprinter_clone = self.my_import('akroell__sprinter').Sprinter(self.name,self.random)
        self.sprinter_clone.enter_ruins()
        self.account_clone = self.my_import('arbo__accountant').Accountant(self.name,self.random)
        self.account_clone.enter_ruins()
        self.drop = False
        pass

    def sprinter(self, state):
        raw_action = self.sprinter_clone.get_action(state)
        if raw_action == 'next' or raw_action == 'previous':
            #move_cost = 10 + int(math.ceil(state.carry_weight / 5))
            #if state.stamina // move_cost < state.room:
            #    print('wont make it!')
            return raw_action
        else:
            atype, *args = raw_action
            if atype == 'take':
                return self.TakeSprinter(state, *args)
            if atype == 'drop':
                return raw_action
    def TakeSprinter(self, state, treasure, bid):
        move_cost = 10 + int(math.ceil((state.carry_weight+state.treasures[treasure].weight) / 5))
        maxbid = state.stamina - move_cost*(state.room)
        bid = state.treasures[treasure].weight + (7 if state.players else 0)
        if maxbid < state.treasures[treasure].weight:
            return 'previous'
        if maxbid < bid:
            bid = maxbid
        return 'take',treasure, bid

    def arty(self, state):
        if self.arty_clone == None:
            try:
                self.arty_clone = importlib.import_module('artemis_fowl__artyventurer').Artyventurer(self.name,self.random)
                self.arty_clone.enter_ruins()
            except:
                self.arty_clone = None
        if self.arty_clone == None:
            raw_action = self.backup_arty(state)
        else:
            raw_action = self.arty_clone.get_action(state)
        if raw_action == 'previous' and state.carry_weight < 1:
            self.arty_clone.fail = False
            return 'next'
        if raw_action == 'next' or raw_action == 'previous':
            return raw_action
        else:
            atype, *args = raw_action
            if atype == 'take':
                return self.TakeArty(*args)
            if atype == 'drop':
                return raw_action
    def TakeArty(self, treasure, bid):
        return 'take', treasure, bid + self.random.randrange(0, 2)

    def account(self, state):
        raw_action = self.account_clone.get_action(state)
        if raw_action == 'next' or raw_action == 'previous':
            return raw_action
        else:
            atype, *args = raw_action
            if atype == 'take':
                return self.TakeAcc(*args)
            if atype == 'drop':
                return raw_action
    def TakeAcc(self, treasure, bid):
        return 'take',treasure,bid + self.random.randrange(0, 2)

    def get_extra(self, state, take=0, drop=0):
        w = state.carry_weight + take - drop
        return state.stamina - ((10 + math.ceil(w/5)) * state.room)
    def backup_arty(self, state):
        if state.inventory:
            ivals = {}
            for i in range(len(state.inventory)):
                itm = state.inventory[i]
                ivals[i] = itm.value / (itm.weight + 5)
            worstiind = min(ivals, key=lambda x: ivals[x])
            worsti = (worstiind,
                      state.inventory[worstiind].value,
                      state.inventory[worstiind].weight)
        else:
            worsti = None
        if self.drop and worsti:
            self.drop = False
            return 'drop', worsti[0]
        if state.treasures:
            tvals = {}
            for i in range(len(state.treasures)):
                itm = state.treasures[i]
                if itm.weight > (int(state.room/12) or 2):
                    continue
                tvals[i] = (itm.value * (25-state.room)) / (itm.weight * (state.carry_weight+1))
            bestord = sorted(tvals, key=lambda x: tvals[x])
            topt = []
            for i in bestord:
                topt.append((i,
                             state.treasures[i].value,
                             state.treasures[i].weight))
        else:
            topt = None
        extra = self.get_extra(state)
        if extra > CONTINUE_IN: 
            return 'next'
        if extra < 0 and worsti:
            return 'drop', worsti[0]
        if extra < state.room:
            return 'previous'
        if extra > JUST_TAKE and topt:
            choose = topt[:len(state.treasures)//3+1]
            for t in choose:
                bid = int(bool(len(state.players)))*3 + t[2]
                if self.get_extra(state, t[2]) - bid >= 0:
                    if t[2] + state.carry_weight <= 50:
                        return 'take', t[0], bid
        if topt and worsti:
            for t in topt[:len(state.treasures)//3+1]:
                if t[1] > worsti[1] or t[2] < worsti[2]:
                    bid = int(bool(len(state.players)))*3 + t[2]
                    if self.get_extra(state, t[2], worsti[2]) - 1 - bid >= 0:
                        if bid < state.stamina and t[2] + state.carry_weight <= 50:
                            self.drop = True
                            return 'take', t[0], bid
        return 'previous'

El canalla trabaja principalmente para interferir con otros concursantes. Actualmente interfiere con Sprinter, Artyventurer y Contador (esta lista crecerá con el tiempo, siempre que sea lo mejor para el sinvergüenza). Lo hace imitando a los otros bots y luego subastando, cortando o luchando por las reliquias. Como tal, es poco probable que esta entrada alguna vez domine la tabla de clasificación y, en cambio, funcione como una fuerza de deterioro. La revisión actual en el momento de esta publicación lo coloca en el segundo lugar con un puntaje promedio de aproximadamente 7.

El sinvergüenza frustra los intentos de otros robots de modificarse para defenderse del sinvergüenza al ejecutar directamente el código de los demás participantes como una copia clon indistinguible. Los problemas con las importaciones que resultaron en entrantes duplicados se resolvieron mediante la creación de clones a través de Reflection (editar guerras que involucren detalles finos de determinación matemática no es deseable desde el punto de vista de Stack Exchange, pero daría como resultado el mismo resultado). KOTH desafíos también tienen un historial de permitir esto.

Sinvergüenza reemplaza a los Teamsters para mantener a los Teamsters por el hecho de haber sido interesantes. Después de esta edición, Teamsters ya no debería ser raspado por el controlador.

Actualización 17/04/2019: nuevas medidas de contador.

Los Teamsters (ilegalizados)

¡Pero siéntase libre de correr localmente donde no hay más de 8 concursantes!

class TeamsterA(Adventurer):
    def get_action(self, state):
        if state.room < 25 and state.carry_weight == 0:
            return 'next'
        if state.room == 25 and len(state.players) == 0 and len(state.inventory) <= 1:
            if state.treasures and len(state.inventory) == 0:
                tvals = {}
                for i in range(len(state.treasures)):
                    itm = state.treasures[i]
                    if itm.weight == 1:
                        return 'take',i,1
                for i in range(len(state.treasures)):
                    itm = state.treasures[i]
                    if itm.weight == 2:
                        return 'take',i,2
            if state.carry_weight > 0 and len(state.inventory) == 1 and int(state.inventory[0].name.strip('Treasure #')) < 500:
                return 'drop',0
            return 'previous'
        if state.room >= 25:
            if (((state.carry_weight+4) / 5) + 10) * state.room >= state.stamina:
                return 'previous'
            if len(state.inventory) == 1 and int(state.inventory[0].name.strip('Treasure #')) < 500:
                return 'drop',0
            if state.treasures:
                tvals = {}
                for i in range(len(state.treasures)):
                    itm = state.treasures[i]
                    if int(itm.name.strip('Treasure #')) > 500:
                        if (((state.carry_weight+3+itm.weight) / 5) + 10) * state.room >= state.stamina:
                            return 'previous'
                        return 'take',i,itm.weight
                for i in range(len(state.treasures)):
                    itm = state.treasures[i]
                    if itm.weight == 1:
                        return 'take',i,1
                for i in range(len(state.treasures)):
                    itm = state.treasures[i]
                    if itm.weight == 2:
                        return 'take',i,2
                if len(state.inventory) > 0:
                    return 'previous'
                return 'next'
        return 'previous'

class TeamsterB(Adventurer):
    def get_action(self, state):
        if state.treasures:
            tvals = {}
            for i in range(len(state.treasures)):
                itm = state.treasures[i]
                w = itm.weight
                v = itm.value
                if w + state.carry_weight > self.max_total_weight or w > self.max_single_weight:
                    w = 100
                if v / w < state.room * self.min_value_ratio:
                    v = 0
                tvals[i] = v / w
            besttind = max(tvals, key=lambda x: tvals[x])
            bestt = (besttind,
                     state.treasures[besttind].value,
                     state.treasures[besttind].weight)
        else:
            bestt = None
        if state.room < self.max_dive_dist and state.carry_weight == 0:
            return 'next'
        if state.room > 25 and bestt and state.carry_weight + bestt[2] <= self.max_total_weight and bestt[1] > 0 and bestt[2] <= self.max_single_weight and len(state.players) == 0:
            return 'take',bestt[0],bestt[2]
        if state.carry_weight > 0 and state.room > 25 and len(state.players) == 0:
            return 'previous'
        if state.carry_weight > 0:
            return 'drop',0
        if state.carry_weight > 0:
            return 'take',bestt[0],bestt[2]
        return 'previous'
    def enter_ruins(self):
        self.max_single_weight = 3
        self.max_total_weight = 20
        self.min_value_ratio = 2.5
        self.max_dive_dist = 55
        pass

Esta entrada (aunque ahora es explícitamente inválida) es, de hecho, dos bots, y el controlador felizmente los raspará a ambos y los agregará a la lista de concursantes (¿por qué hurra Python?)

Fase 1:

  • TeamsterA baja al nivel 25 (ish) 1 y recoge y suelta repetidamente el tesoro más ligero que puede encontrar. Esto cuesta 1 aguante por turno hasta la segunda fase.
  • TeamsterB baja al nivel 55 y recoge todos los objetos de valor que están por ahí y luego vuelve al nivel 25 (ish). 2 Luego comienza la fase 2.

1. Si no hay un tesoro que pese menos de 3 en un piso, se mueve hacia abajo
2. Como está casi garantizado que será el último aventurero en regresar a la superficie, todo lo que tiene que hacer es encontrar a alguien.

Fase 2:

  • TeamsterB vacía sus bolsillos antes de arrastrarse para morir de agotamiento. Sabíamos que podía hacerlo.
  • TeamsterA piensa que "esas son algunas baratijas brillantes, ¡buen amigo, viejo amigo!" y carga los tesoros mucho más valiosos que la otra basura en la habitación antes de proceder a la salida, bolsillos llenos de oro.

El nombre de los tesoros en realidad fue útil para ayudar a que la lógica no se cargue en la basura del piso 25 y se vaya temprano, ya que no había forma de comunicarse entre los dos bots (y TeamsterA siempre se encontraría en una habitación con otra persona antes) TeamsterB había regresado).

La siguiente conclusión lógica: crear un ejército

En teoría, esto podría usarse para sondear las profundidades y adquirir tesoros desde tan profundo como la Sala 98, sin embargo, ya que eso requeriría más de 2 bots, la lógica que comprende esos bots se volvería cada vez más compleja, y como estoy seguro de que esto es una presentación ilegal por violar una regla no escrita, por lo que no voy a molestarme.

Efectivamente Aespera a los 30, Bespera a los 50 ... se nsumerge a 98, recoge un tesoro, se mueve a 97, lo deja caer (y luego muere), n-1lo levanta y se mueve a 96 ... lo Cdeja caer (muere), lo Brecoge arriba y se mueve a 30, lo deja caer (muere),A lo levanta y vuelve a la salida.

Calculo que esto tomaría 11 bots.

Sin embargo, no vale la pena hacerlo a menos que pueda recuperar alrededor de 4 tesoros de esa profundidad para competir con entradas como PlanAhead o Artyventure, debido a la escala entre los costos de resistencia para mover y el valor promedio de los tesoros.

Resultados de muestra

Raramente tiene un puntaje inferior a $ 4000, ocasionalmente alcanza un valor de $ 6000.

[Turn 141] Homer the Great (TeamsterA) exited the ruins with 286 stamina
    and 16 treasures, totaling $4900 in value.
[Game End] The game has ended!
[Game End] Homer the Great (TeamsterA) won the game

[Turn 145] Samwell Jackson DDS (TeamsterA) exited the ruins with 255 stamina
    and 20 treasures, totaling $6050 in value.
[Game End] The game has ended!
[Game End] Samwell Jackson DDS (TeamsterA) won the game

[Turn 133] Rob the Smuggler (TeamsterA) exited the ruins with 255 stamina
    and 12 treasures, totaling $3527 in value.
[Game End] The game has ended!
[Game End] Eliwood the Forgettable (PlanAhead) won the game
Draco18s
fuente
1
Creo que cuando solo iba a haber un bot por persona, no había necesidad de una regla tan explícita. Pero la regla sobre atacar a un bot en particular por razones nefastas no es realmente lo mismo que rechazar la agrupación de múltiples bots. Por lo tanto, se necesita una decisión explícita del OP.
Moogie
Sí, esto va a ser un no para mí, amigo. Este es el tipo de cosas que tenía en mente con los bots trabajando juntos.
Beefster
1
@Beefster Eso es lo que pensé. Aunque me divertí mucho haciéndolo. Esta noche me ocuparé de editar para evitar la inclusión.
Draco18s
Consideraré permitir esto una vez que haya más de 11 competidores ya que su efectividad se reducirá de todos modos. Principalmente porque no quiero crear código para envíos automáticos.
Beefster
Si ya solo está raspando el primer bloque de código, todo lo que tengo que hacer es editar en un bot diferente en la parte superior.
Draco18s
2

Hacia atrás

Porque opera en reversa

import math

class Backwards (Adventurer):
    def enter_ruins(self):
        self.goal = 5000
        self.diving = True

    def expected_rooms_left(self, state):
        if not self.diving:
            return state.room
        else:
            return state.stamina / 18

    def ratio(self, state, treasure):
        stamina_cost = treasure.weight * (1 + 1/5 * self.expected_rooms_left(state)) + math.ceil(len(state.players)/2.9)
        ratio = (treasure.value / (self.goal - state.total_value)) / (stamina_cost / state.stamina)
        return ratio

    def get_action(self, state):
        room, treasures, players, inventory, stamina = state
        if stamina < room * (math.ceil(state.carry_weight / 5) + 10) + 40 or stamina < (room+2.976) * (math.ceil(state.carry_weight / 5) + 11):
            self.diving = False
        if stamina < (room+0.992) * (math.ceil(state.carry_weight / 5) + 10.825):
            return 'previous'

        worthwhile = []
        for i, treasure in enumerate(treasures):
            ratio = self.ratio(state, treasure)
            if ratio >= 1 and state.carry_weight + treasure.weight <= 50:
                worthwhile.append((ratio, i))

        if worthwhile:
            ratio, index = sorted(worthwhile, reverse=True)[0]
            treasure = treasures[index]
            bid = treasures[index].weight + math.ceil(len(players)/2.9)
            if (not self.diving or ratio > 2.8) and stamina >= bid + (room) * (math.ceil((state.carry_weight+treasures[index].weight) / 5) + 10):
                return 'take', index, bid
        return 'next' if self.diving else 'previous'

¿Por qué se llama al revés?

Porque tomé The Accountant y traté de hacer que funcionara su lógica de modo que se sumergiera profundamente, luego recogiera su botín preferido al salir (al revés del Contador).

Al final, todavía recoge gran parte de sus premios en el camino (recogiéndolos antes de que lo hagan los buscadores tradicionales dentro y fuera, operando hacia atrás para todos los demás), pero es mucho más selectivo sobre los que toma, aunque todavía recoge cosas en su camino de regreso.

El resultado final es que la resistencia se conserva en el camino al tiempo que se sigue dando prioridad a los tesoros de alto valor, y luego se aprovecha de un cambio profundo y una fácil selección en el camino de regreso. Se sabe que al revés se acumulan tesoros desde lugares como la Sala 41 (y durante el desarrollo entraría y luego saldría inmediatamente de la Sala 42).

Draco18s
fuente
2

Cazarrecompensas

El método simple es el mejor. Agarra tesoros valiosos y ligeros mientras profundizas lo más posible. Consigue tesoros menos valiosos en el camino de regreso.

import math

class BountyHunter(Adventurer):
    def move_cost(self, state, additional_weight):
        return 10 + int(math.ceil((state.carry_weight + additional_weight) / 5))

    def get_action(self, state):
        can_go_deeper = state.stamina > (state.room + 2) * self.move_cost(state, 0)
        if state.treasures:
            best_ratio = 0
            best_index = 0
            best_weight = 0
            for i, treasure in enumerate(state.treasures):
                ratio = treasure.value / treasure.weight
                if ratio > best_ratio:
                    best_ratio = ratio
                    best_index = i
                    best_weight = treasure.weight
            limit = 160 if can_go_deeper else 60
            bid = best_weight + 2 if len(state.players) >= 1 else best_weight
            if state.carry_weight + best_weight <= 50 and best_ratio >= limit and state.stamina >= bid + state.room * self.move_cost(state, best_weight):
                return 'take', best_index, bid
        if can_go_deeper:
            return 'next'
        else:
            return 'previous'
Sleafar
fuente
Parece que estás obteniendo la recompensa. Esto no solo funciona mejor que al revés, sino que incluso hace que al revés se acumule. Bien hecho.
Beefster
1

Ligero

Un bot simple que aún funciona bastante bien.

Después de aventurarse en las ruinas (actualmente 21 habitaciones), tomará el mejor tesoro de la habitación que solo pesa 1 kg (de ahí el nombre del bot) y es más valioso que el tesoro menos valioso del inventario. Si el inventario está lleno, suelta el tesoro menos valioso. Si no se selecciona otra acción, muévete a las ruinas. Si estamos en el límite de nuestra resistencia para poder salir vivos, entonces dirígete a la salida

import math

class LightWeight(Adventurer):

    def get_action(self, state):
        move_cost = 10 + int(math.ceil(state.carry_weight / 5))

        # are we now at the limit of stamina to still get back alive?
        if (state.stamina / move_cost <= state.room + 3):
            # head back to the exit
            return 'previous'

        if (state.room < 21):
            return 'next'

        bestRoomTreasure = None
        bestRoomTreasureId = -1
        worstMyTreasure = None
        worstMyTreasureId = -1

        # find our worst treasure
        i=0
        for treasure in state.inventory:
            if (worstMyTreasure is None or treasure.value < worstMyTreasure.value):
                worstMyTreasure = treasure
                worstMyTreasureId=i
            i+=1

        # we have hit our carrying capacity... we are now going to dump least valuable treasure
        if (state.carry_weight==50):

            # dump the worst treasure
            return 'drop',worstMyTreasureId

        # find the best room treasure
        i=0
        for treasure in state.treasures:
            if (treasure.weight == 1 and (worstMyTreasure is None or treasure.value > worstMyTreasure.value)):
                if (bestRoomTreasure is None or treasure.value > bestRoomTreasure.value):
                    bestRoomTreasure = treasure
                    bestRoomTreasureId = i
            i+=1

        # we have found a treasure better than we already have!
        if (bestRoomTreasure is not None):
            return 'take',bestRoomTreasureId,1

        # no treasures are better than what we already have so go to next room
        return 'next'
Moogie
fuente
Recomiendo poner dumpingen el enter_ruinsmétodo. Esto realmente lo recordará entre los juegos y no funcionará en el juego 2. Técnicamente no está permitido, pero agregué la regla en este momento (lo olvidé antes pero era mi intención), así que reduciré un poco la holgura. : P
Beefster
@Beefster He eliminado la bandera del estado de descarga, no es necesario ya que el bot solo descarga un tesoro ahora. Solía ​​volcar la mitad de su tesoro. Por lo tanto, debe ser compatible con la nueva regla.
Moogie
1

Memorizador

Puedo enviar bots a mi propio KotH, ¿verdad?

from __main__ import Adventurer
import math
from collections import namedtuple

class TooHeavy(Exception):
    pass

TreasureNote = namedtuple(
    'TreasureNote',
    ['utility', 'cost', 'room', 'name', 'value', 'weight']
)

def find_treasure(treasures, name):
    for i, t in enumerate(treasures):
        if t.name == name:
            return i, t
    raise KeyError(name)

EXPLORE_DEPTH = 30
TRINKET_MINIMUM_VALUE = 60

class Memorizer(Adventurer):
    def enter_ruins(self):
        self.seen = []
        self.plan = []
        self.backups = []
        self.diving = True
        self.dive_grab = False

    def plan_treasure_route(self, state):
        self.plan = []
        self.backups = []
        weight = state.carry_weight
        for treasure in self.seen:
            if weight + treasure.weight <= 50:
                self.plan.append(treasure)
                weight += treasure.weight
            else:
                self.backups.append(treasure)
        room_utility = lambda t: (t.room, t.utility)
        self.plan.sort(key=room_utility, reverse=True)

    def iter_backups(self, state):
        names = {t.name for t in state.treasures}
        owned = {t.name for t in state.inventory}
        for treasure in self.backups:
            if (treasure.room == state.room
                    and treasure.name in names
                    and treasure.name not in owned):
                yield treasure

    def take(self, state, name):
        index, treasure = find_treasure(state.treasures, name)
        if state.carry_weight + treasure.weight > 50:
            raise TooHeavy(name)
        if state.players:
            bid_bonus = self.random.randrange(len(state.players) ** 2 + 1)
        else:
            bid_bonus = 0
        return 'take', index, treasure.weight + bid_bonus

    def get_action(self, state):
        take_chance = 0.9 ** len(state.players)

        if self.diving:
            if self.dive_grab:
                self.dive_grab = False
            else:
                self.seen.extend(
                    TreasureNote(
                        value / weight,
                        weight + math.ceil(weight / 5) * state.room,
                        state.room,
                        name, value, weight
                    )
                    for name, value, weight in state.treasures
                )
            if state.room < EXPLORE_DEPTH:
                if len(state.inventory) < 5:
                    trinkets = [
                        t for t in state.treasures
                        if t.weight == 1
                        and t.value >= TRINKET_MINIMUM_VALUE
                    ]
                    trinkets.sort(key=lambda t: t.value, reverse=True)
                    for candidate in trinkets:
                        if self.random.random() < 0.99 ** (len(state.players) * state.room):
                            try:
                                action = self.take(state, candidate.name)
                            except (KeyError, TooHeavy):
                                pass # WTF!
                            else:
                                self.dive_grab = True
                                return action
                return 'next'
            else:
                self.diving = False
                self.seen.sort(reverse=True)
                self.plan_treasure_route(state)

        carry_weight = state.carry_weight
        if carry_weight == 50:
            return 'previous'

        if self.plan:
            next_index = 0
            next_planned = self.plan[next_index]
            if state.room > next_planned.room:
                return 'previous'

            try:
                while state.room == next_planned.room:
                    if self.random.random() < take_chance:
                        try:
                            return self.take(state, next_planned.name)
                        except (KeyError, TooHeavy):
                            self.plan.pop(next_index)
                            next_planned = self.plan[next_index]
                    else:
                        next_index += 1
                        next_planned = self.plan[next_index]
            except IndexError:
                pass
        else:
            next_planned = TreasureNote(0, 0, 0, 0, 0, 0)

        for candidate in self.iter_backups(state):
            if candidate.utility * 2 > next_planned.utility and self.random.random() < take_chance:
                try:
                    return self.take(state, candidate.name)
                except (KeyError, TooHeavy):
                    pass

        return 'previous'

Este bot se sumerge en la habitación 30 y recuerda todos los tesoros que ha visto. En ese punto, comienza su caminata de regreso a la entrada, tratando de tomar buenos tesoros que recordaba haber visto en habitaciones anteriores.

Esperaba que fuera mejor. Las posibles mejoras pueden provenir de una mejor planificación y de ser más dinámico sobre qué sala deja de bucear y de estar más dispuesto a explorar opciones de respaldo.

Actualización: ahora toma tesoros de 1 kg por valor de $ 60 o más en el camino.

Carne de res
fuente
Me imagino que todo ese buen tesoro acaba de desaparecer en el momento en que el bot vuelve allí ... Tal vez puedas probar un combo donde tomará las cosas realmente buenas en su camino, teniendo en cuenta el tesoro mediocre que podría recoger en su camino de regreso?
ArBo
Podría estar yendo demasiado lejos
Beefster
Para su información, parece que a veces se calcula mal si tiene suficiente resistencia para volver: [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
Larkeith
1

Ponderer

Creo que es bastante similar a Memorizer en que utiliza el conocimiento de las habitaciones visitadas para elegir qué habitaciones y tesoros recoger en el camino de regreso a la salida, sin embargo, se ha derivado de forma independiente.

Este bot corre hasta una habitación profunda al azar que toma un registro de los tesoros encontrados en el camino. Una vez en la habitación objetivo, reflexionará sobre la selección ideal de tesoros para regresar a la salida. Cada turno meditará de nuevo para determinar la mejor selección de tesoros más probable.

Actualmente hay un algoritmo simple (potencia inversa del número de la habitación) que produce el número supuesto de tesoros tomados (o se habrán tomado cuando los visitó este bot) para cada habitación, por lo que estos tesoros se ignoran al reflexionar sobre qué tesoros / habitaciones tomar de. Tengo ideas para otros algoritmos más avanzados para modelar qué tesoros quedan. Pero tendré que ver si el beneficio lo vale.

import math

class Ponderer(Adventurer):

  class PondererTreasure:
    def __init__(self):
        self.weight = 0
        self.value = 0
        self.id = -1
        pass

  class PondererRoom:
    def __init__(self):
        self.treasures = []
        pass

  def enter_ruins(self):
      self.exiting = False
      self.sprintToRoom = self.random.randrange(30,33)
      self.rooms = {}
      self.roomsToSkip = 0
      pass

  def getBestEstimatedFinalValue(self, roomId, carry_weight, stamina, action, valueCache):
    if (roomId<=0):
      return 0

    roomValueCache = valueCache.get(roomId)

    if (roomValueCache is None):
      roomValueCache = {}
      valueCache[roomId] = roomValueCache

    value = roomValueCache.get(carry_weight)
    if (value is None):
      room = self.rooms.get(roomId)

      bestTreasureValue = 0
      bestTreasure = None
      treasures = []
      treasures.extend(room.treasures)
      skipRoomTreasure = Ponderer.PondererTreasure()
      treasures.append(skipRoomTreasure)

      roomFactor = 0.075*roomId
      estimatedTreasuresTakenAtCurrentRoom =  int(min(0.5 * len(room.treasures), max(1, 0.5 * len(room.treasures)*(1.0/(roomFactor*roomFactor)))))

      j=0
      for treasure in treasures:
        if (j>=estimatedTreasuresTakenAtCurrentRoom):
          staminaAfterBid = stamina - treasure.weight
          carry_weightAfterBid = carry_weight + treasure.weight
          move_costAfterBid = 10 + int(math.ceil(carry_weightAfterBid/5))

          if (carry_weightAfterBid <=50 and (staminaAfterBid/move_costAfterBid > roomId+1)):
            bestAccumulativeValue = self.getBestEstimatedFinalValue(roomId-1, carry_weightAfterBid, staminaAfterBid - move_costAfterBid, None, valueCache)

            if (bestAccumulativeValue >= 0):
              bestAccumulativeValue += treasure.value
              if (bestTreasure is None or bestAccumulativeValue > bestTreasureValue):
                bestTreasureValue = bestAccumulativeValue
                bestTreasure = treasure
        j+=1

      if (bestTreasure == skipRoomTreasure):
        if (action is not None):
          newAction = []
          newAction.append('previous')
          action.append(newAction)
        value = 0

      elif (bestTreasure is not None):
        if (action is not None):
          newAction = []
          newAction.append('take')
          newAction.append(bestTreasure.id)
          newAction.append(bestTreasure.weight)
          action.append(newAction)
        value = bestTreasureValue

      else:
        if (action is not None):
          newAction = []
          newAction.append('previous')
          action.append(newAction)
        value = -1

      roomValueCache[carry_weight] = value
    return value

  def get_action(self, state):
    room = Ponderer.PondererRoom()

    i=0
    for treasure in state.treasures:
      pondererTreasure = Ponderer.PondererTreasure()
      pondererTreasure.weight = treasure.weight
      pondererTreasure.value = treasure.value
      pondererTreasure.id = i

      room.treasures.append(pondererTreasure)
      i+=1

    room.treasures.sort(key=lambda x: x.value/x.weight, reverse=True)

    self.rooms[state.room] = room

    if (self.exiting == False and state.room < self.sprintToRoom):
      return 'next'

    self.exiting = True

    action = []
    valueCache = {}

    self.getBestEstimatedFinalValue(state.room, state.carry_weight, state.stamina, action, valueCache)

    if (action[0][0] == 'take'):
      return 'take', action[0][1], action[0][2]

    return action[0][0]
Moogie
fuente
1

Acaparador

import math

class Hoarder(Adventurer):
  def canGoOn(self, state):
    costToMove = 10 + math.ceil(state.carry_weight / 5)
    return (state.room + 2) * costToMove <= state.stamina

  def canTakeTreasure(self, state, treasure):
    costToMove = 10 + math.ceil(state.carry_weight / 5)
    treasureCost = treasure.weight + 1
    return treasureCost + state.room * costToMove <= state.stamina

  def get_action(self, state):
    if (len(state.treasures) == 0):
      if (self.canGoOn(state)):
        return "next"
      else:
        return "previous"
    else:
      bestTreasure = -1
      for i, treasure in enumerate(state.treasures):
        if self.canTakeTreasure(state, treasure):
          if (bestTreasure == -1):
            bestTreasure = i
          elif state.treasures[bestTreasure].value < state.treasures[i].value:
            bestTreasure = i
      if (bestTreasure == -1):
        return "previous"
      return "take", bestTreasure, state.treasures[bestTreasure].weight+1

El Acumulador permanece en una habitación hasta que haya tomado todos los tesoros de la habitación (o calcula que no tiene suficiente resistencia para seguir tomando / moviéndose). Cuando todos los tesoros se hayan ido, si el bot puede avanzar con seguridad, continuará y continuará el proceso de tomar todo el tesoro.

lolad
fuente
Esto muere en cada juego al sobrellenar su mochila.
Beefster
como yo en Minecraft (͡ ° ͜ʖ ͡ °) Este bot saqueará, profundizará y luego encontrará un botín valioso. Entonces dejará caer lo que él pensó que era un buen botín antes. Es por eso que la estrategia de Backwards's, Sprinter' s y Memorizer'funciona; porque saben cuáles son los valores relativos de cada tesoro que ven.
V. Courtois
0

Buzo

(No puedo probar en este momento, así que avíseme si esto está roto).

class Diver(Adventurer):
    def get_action(self, state):
        # Don't take anything on the way in.
        if state.stamina > 700:
            return 'next'

        # Take the most valuable thing we can take without dying.
        for treasure in sorted(state.treasures, key=lambda x: x.value, reverse=True):
            total = treasure.weight + state.carry_weight
            if total <= 50 and (10 + (total + 4) // 5) * state.room + treasure.weight <= state.stamina:
                return 'take', state.treasures.index(treasure), treasure.weight

        # If there's nothing else we can do, back out.
        return 'previous'

El mejor tesoro es más profundo en las ruinas, así que sumérgete profundamente y luego toma lo que podamos al salir.


fuente
No tengo mucha experiencia con Python, pero ¿dónde está divingdefinido?
Encarnación de la ignorancia
1
@EmbodimentofIgnorance En enter_ruins (), que se llama antes de que se ejecute el juego y se realicen acciones.
Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.No estoy seguro de lo que hiciste mal, pero eso significa 'retorno no válido' AFAIK.
Artemisa apoya a Mónica el
@ArtemisFowl hizo una oferta demasiado baja para el tesoro. Cuesta el peso del tesoro recogerlo.
Beefster
@Beefster Oh, sí.
Artemisa apoya a Mónica el