Aventureiros nas Ruínas

27

Piloto de testeDiscussão do desafioEnviar aventureiro

Sala do Tesouro ( Fonte da imagem )

Vários aventureiros rivais estão invadindo as ruínas em busca de tesouros, mas eles só podem carregar muito de cada vez e têm seus limites de resistência. Eles querem obter o tesouro mais valioso e sair antes que fiquem cansados ​​demais para continuar. Eles estão tentando ficar o mais ricos possível com suas travessuras de saques.

Jogabilidade

Cada aventureiro começa na primeira sala da masmorra com 1000 pontos de resistência e 50 kg de espaço na mochila.

O jogo funciona em turnos, com todos os jogadores resolvendo seus turnos simultaneamente. A cada turno, você pode executar uma das seguintes ações:

  • Mover para a próxima sala.
  • Mover para a sala anterior.
  • Ofereça resistência para pegar um tesouro.
  • Largue um tesouro.

Mover-se entre os quartos exige 10 pontos de resistência, mais 1 por cada 5 kg atualmente em sua mochila, arredondados. Por exemplo, um aventureiro carregando 3kg de tesouro requer 11 resistência para se mover e um carregando 47kg requer 20 resistência para se mover.

Soltar um tesouro requer 1 ponto de resistência, independentemente do tesouro descartado.

Ao sair das ruínas, o jogador não realiza mais turnos.

Se um jogador não puder realizar nenhuma dessas ações (devido à falta de resistência ou ausência de tesouros), seu aventureiro morre de exaustão, despejando seu tesouro na sala atualmente ocupada. Da mesma forma, se um jogador tentar fazer uma ação inválida, seu aventureiro será morto por uma armadilha, resultando no mesmo derramamento de tesouro.

Licitação

A oferta mínima para um tesouro é de 1 resistência por 1 kg que o tesouro pesa. Você também pode oferecer pontos de resistência adicionais para ter mais chances de obter o tesouro. O vigor que foi oferecido é consumido, independentemente do resultado.

No caso de vários jogadores oferecerem o mesmo tesouro, o jogador que fizer o lance mais alto receberá o tesouro. Se mais de um jogador fez a oferta mais alta, nenhum deles receberá o tesouro.

Condição da vitória

O jogador com o maior valor total de tesouros é o vencedor. No improvável evento de empate, os empates têm o menor peso total, o menor número de tesouros e o valor do tesouro mais valioso, o segundo mais valioso, o terceiro ... até que o empate seja quebrado. No evento quase impossível de que ainda haja um empate nesse momento, o piloto de teste diz "estrague tudo" e o vencedor é, assim, determinado arbitrariamente.

No contexto do torneio, os jogadores serão classificados com o primeiro lugar recebendo 10 pontos, o segundo lugar com 9 pontos, o terceiro lugar com 8 pontos, etc ..., com jogadores mortos e aventureiros sem tesouros marcando 0 pontos.

Sobre as ruínas

  • Cada quarto contém inicialmente entre e tesouros. (Onde é o número do quarto)r3+3r2+5r
  • Existem arbitrariamente muitos quartos, limitados apenas pela resistência e vontade de explorar dos aventureiros.
  • Cada tesouro terá um valor monetário (em dólares inteiros) e um peso (em kg inteiros).
    • Os tesouros tendem a ser mais valiosos e abundantes à medida que você se aprofunda nas ruínas.
  • As fórmulas específicas para gerar tesouros são as seguintes: (usando a notação para de dados) xdy
    • O peso é gerado primeiro usando a fórmula (mínimo de 1)2d6-2
    • O valor do tesouro é gerado através de (onde é o número do quarto é o peso)1d[10W]+2d[5r+10]rW

Informações visíveis para jogadores

A cada turno, os jogadores recebem as seguintes informações:

  • O número da sala em que estão atualmente. Isso é indexado em 1; portanto, conceitualmente, a saída está na "sala 0"
  • Uma lista de tesouros atualmente na sala
  • Uma lista de outros jogadores que também estão atualmente na sala.
  • Seu inventário atual de tesouros
  • Seu nível atual de resistência

Codificação

O driver de teste pode ser encontrado aqui .

Você deve implementar uma subclasse desta Adventurerclasse:

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

Você só precisa substituir o get_actionmétodo. enter_ruinsé executado antes do início do jogo e é sua chance de preparar o que você deseja que esteja pronto para o jogo. Você não precisa substituir __init__, e realmente não deveria . Se __init__falhar, você será desqualificado.

get_actionrecebe um único argumento que é a namedtuplecom os seguintes campos (nessa ordem, se você preferir desestruturar):

  • room: o número da sala em que você está atualmente
  • treasures: a lista de tesouros na sala
  • players: a lista de outros jogadores na sala. Você só recebe o nome do jogador dessa maneira, então você não sabe qual bot está controlando eles ou seu inventário / resistência.
  • inventory: a lista de tesouros na sua mochila
  • stamina: seu nível atual de resistência

Este objeto também fornece duas propriedades de utilitário:

  • carry_weight: o peso total de todos os tesouros que você está carregando
  • total_value: o valor total de todos os tesouros que você está carregando

As listas treasurese inventorycontêm namedtuples com estes atributos:

  • name: o nome do tesouro (para fins cosméticos)
  • value: o valor monetário do tesouro em $.
  • weight: peso do tesouro em kg

get_action deve retornar um dos seguintes valores / padrões:

  • 'next'ou 'previous'para ir para as salas seguintes / anteriores
  • 'take', <treasure index>, <bid>(sim, como uma tupla, embora qualquer sequência também funcione tecnicamente) para licitar no tesouro no índice especificado na lista de tesouros da sala. Ambos os argumentos devem ser inteiros. Os carros alegóricos serão arredondados para baixo.
  • 'drop', <inventory index>para largar o tesouro carregado encontrado no índice fornecido. O índice deve (naturalmente) ser um número inteiro.

Outras Restrições

  • Você só pode usar a instância aleatória fornecida a você durante a inicialização para pseudo-aleatoriedade.
    • Qualquer outra coisa que possa introduzir não determinismo comportamental não é permitida. A intenção aqui é fazer com que os bots se comportem de forma idêntica quando recebem a mesma semente para ajudar no teste de novos bots (e potencialmente bugs no driver de teste). Somente a radiação cósmica deve causar algum desvio / não-determinismo.
    • Lembre-se de que os códigos de hash são randomizados no Python 3, portanto, o uso hashpara qualquer tomada de decisão não é permitido. dicts são bons mesmo quando se usa ordem de iteração para decisões, pois a ordem é garantida de forma consistente desde o Python 3.6.
  • Você não pode contornar o driver de teste usando ctypeshacks ou inspectempilhar vodu (ou qualquer outro método). Existem algumas coisas impressionantemente assustadoras que você pode fazer com esses módulos. Por favor não.
    • Cada bot tem uma caixa de areia razoavelmente bem através de cópias defensivas e da imutabilidade natural de namedtuples, mas existem algumas brechas / explorações inatacáveis.
    • Outra funcionalidade inspecte ctypespode ser usada desde que nenhuma seja usada para contornar a funcionalidade do controlador.
    • Qualquer método de captura de instâncias dos outros bots no seu jogo atual não é permitido.
  • Os bots devem operar sozinhos e não podem se coordenar com outros bots de forma alguma para qualquer finalidade. Isso inclui a criação de dois bots com objetivos diferentes, de modo que um se sacrifique pelo sucesso do outro. Quando houver mais de 10 competidores, você não terá a garantia de ter os dois bots no mesmo jogo e os nomes dos aventureiros não darão nenhuma indicação da classe de bot, portanto esses tipos de estratégias são limitadas de qualquer maneira.
  • No momento, não há nenhuma restrição rígida no tempo de execução, no entanto, eu me reservo o direito de restringi-lo no futuro se os torneios começarem a demorar muito. Seja razoável e tente manter o processamento de turnos abaixo de 100ms , pois não prevejo a necessidade de restringi-lo abaixo desse limite. (Os torneios ocorrerão em cerca de 2 horas se todos os bots demorarem cerca de 100 ms por turno.)
  • Sua classe de bot deve ser nomeada exclusivamente entre todos os envios.
  • Você pode não se lembrar de nada entre os jogos. (No entanto, você pode se lembrar de coisas entre turnos )
    • Não edite sys.modules. Qualquer coisa fora das variáveis ​​de instância deve ser tratada como uma constante.
  • Você não pode modificar o código de nenhum bot programaticamente, incluindo o seu.
    • Isso inclui excluir e restaurar seu código. Isso é para tornar a depuração e os torneios mais dinâmicos.
  • Qualquer código que cause falha no controlador será imediatamente desqualificado. Embora a maioria das exceções seja detectada, algumas podem passar despercebidas e as segfaults são inalcançáveis. (Sim, você pode segfault em Python graças a ctypes)

Submissões

Para ajudar na raspagem da resposta, indique o nome do seu bot na parte superior da resposta com ae #Header1garanta que sua resposta inclua pelo menos um bloco de código (somente o primeiro em sua resposta será usado). Você não precisa incluir nenhuma importação ou documentação, pois elas serão adicionadas automaticamente pelo raspador.

Estarei mais inclinado a votar novamente com explicações detalhadas e compreensíveis. É provável que outros se comportem da mesma maneira.

Grosso modo, sua resposta deve ser formatada da seguinte forma:

# Name of Bot
Optional blurb

    #imports go here

    class BotName(Adventurer):
        #implementation

Explanation of bot algorithm, credits, etc...

(processado como)

Nome do Bot

Destaque opcional

#imports go here

class BotName(Adventurer):
    #implementation

Explicação do algoritmo de bot, créditos, etc ...

Executando o driver de teste localmente

Você precisará do Python 3.7+ e eu recomendo que você instale também tabulatevia pip. Raspar esta página para envios requer adicionalmente lxmle requests. Você também deve usar um terminal com suporte para escapes de cores ANSI para obter melhores resultados. Informações sobre como configurar isso no Windows 10 podem ser encontradas aqui .

Adicione seu bot a um arquivo em um subdiretório no mesmo diretório que ruins.py( ruins_botspor padrão) e não se esqueça de adicionar from __main__ import Adventurerà parte superior do módulo. Isso é adicionado aos módulos quando o raspador baixa o seu envio e, embora seja definitivamente hacky, esta é a maneira mais direta de garantir que seu bot tenha acesso adequado Adventurer.

Todos os bots nesse diretório serão carregados dinamicamente no tempo de execução, portanto, nenhuma alteração adicional será necessária.

Torneio

O vencedor final será determinado em uma série de jogos com até 10 bots em cada jogo. Se houver mais de 10 envios no total, os 10 melhores bots serão determinados dividindo-os sistematicamente em grupos de 10 até que cada bot jogue (exatamente) 20 jogos. Os 10 melhores bots serão selecionados deste grupo com redefinições de pontuação e jogarão até que o bot do primeiro lugar tenha alcançado uma vantagem de 50 pontos sobre o bot do segundo lugar ou até que 500 jogos tenham sido jogados.

Até que haja pelo menos 10 inscrições, vagas vazias serão preenchidas com "Bêbados", que vagam aleatoriamente pelas ruínas e pegam (e ocasionalmente jogam) tesouros aleatórios até que fiquem sem energia e precisem seguir para a saída.

Os torneios serão repetidos semanalmente se houver novos envios. Este é um desafio KOTH aberto, sem data final definida.

Entre os melhores

A partir de quarta-feira, 4 de maio de 2019 às 16h25 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

Atualização - 15 de abril: algumas atualizações / esclarecimentos de regras

Atualização - 17 de abril: banindo alguns casos notáveis ​​de ações nefastas, como modificar o código de outros bots.

Atualização - 4 de maio: Recompensa concedida a Sleafar por destruir absolutamente para trás. Parabéns!

Beefster
fonte
1
Finalmente chegou! Acho que vou ter que começar a fazer meu bot agora.
Belhenix 12/04
12
Por que o limite para um bot? Eu tenho várias idéias mutuamente exclusivas, e eu prefiro não ter que jogar um bot perfeitamente bom toda vez que eu criar um novo.
@Mnemonic, principalmente para evitar o deslocamento de novos envios usando vários bots quase idênticos. O outro motivo foi impedir que os robôs funcionassem juntos, mas isso é explicitamente proibido de qualquer maneira. Vou considerar permitir. Aqueles a favor de permitir várias submissões, votaram o comentário do Mnemonic acima.
Beefster
1
@ Draco18s Se você tiver pipinstalado e ativado PATH(o que é padrão para instalações mais recentes do AFAIK), a partir do Windows, você pode executar pip install modulenameno prompt de comando. Para outras circunstâncias (das quais não conheço), vá para o pip , procure o módulo necessário e escolha uma opção.
Artemis apoia Monica
1
Suponho que será um "não", mas podemos salvar informações durante o torneio? (por exemplo, quando uma oferta funcionou)
Artemis apoia Monica em

Respostas:

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'

O contador é uma pessoa muito avessa a riscos. Ele gosta de ter certeza de que o que faz realmente é a melhor opção na situação em questão. Então, ele define uma meta e só pega tesouros se seus cálculos mostrarem que isso o coloca no caminho certo em direção a essa meta. No entanto, ele é muito burocrático e não gosta de largar itens que já havia decidido que queria; quaisquer tentativas de ensiná-lo a fazê-lo resultaram até o momento em que o contador descartou um item e o retirou imediatamente imediatamente depois.

Possivelmente para ser continuado.

ArBo
fonte
1
Bom trabalho determinando o valor do tesouro. Eu definitivamente tinha em mente escrever um código melhor "vale a pena", mas ainda não tinha chegado lá. O canalha está chegando para a linha de fundo do contador, embora ...
Draco18s 16/04
"quaisquer tentativas de ensiná-lo a fazê-lo resultaram até o momento em que o contador deixou um item cair e o recolheu imediatamente depois". Você pode contornar isso mantendo um conjunto de nomes de tesouros descartados. Tive a sensação de que nomes de tesouros seriam úteis (mesmo que estejam numerados agora)
Beefster 17/04
Obrigado, mas eu já descobri por que isso aconteceu. Não sei se vou consertá-lo imediatamente, já que ele raramente o usa quando eu testei a colocação.
ArBo 18/04
2

Alojamento

Baseado livremente no meu outro bot LightWeight. Onde o bot LightWeight era simples, esse bot é muito mais complexo para acomodar interações com outros bots: benignos e deliberadamente perturbadores.

Este bot primeiro dispara para uma sala designada aleatoriamente e, em seguida, tenta oferecer o melhor tesouro com relação valor / peso, se houver outros jogadores na sala, assume que eles também oferecerão o lance para o segundo melhor tesouro. Se esse lance falhar, no próximo turno lance para o próximo melhor tesouro.

Depois que uma oferta for bem-sucedida, repita a licitação para o melhor / segundo melhor até que não exista mais tesouros na sala e, em seguida, vá mais fundo na ruína

Para cada sala, entre na ruína e repita o lance para o melhor / segundo melhor até que não exista mais tesouros na sala, depois vá para a ruína ou se detectarmos que não podemos ir mais fundo, troque para o estado 'Exiting' e comece a diminuir o pior tesouro até que possamos garantir que podemos sair vivos da ruína.

Quando sairmos, verificaremos se podemos adicionar um tesouro de 1kg e ainda assim sair vivo, se for o caso, tentaremos oferecer um tesouro de 1kg, caso contrário, seguiremos para a sala anterior.

Seu desempenho é bastante variável ... no entanto, normalmente será um dos três principais 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
fonte
Impressionante! Este está dominando o torneio em cerca de 50 rodadas.
Beefster 16/04
2

Sprinter

Semelhante ao Diver, Sprinter se aprofunda e pega os melhores itens no caminho de volta.

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

O Sprinter vai fundo e calcula uma pontuação para cada tesouro e pega qualquer coisa acima de um determinado limite. Esse limite depende da sala em que ele está atualmente, porque custa mais para levar itens de salas mais profundas nas ruínas.

Ainda tenho duas otimizações em relação à "luta pelo tesouro" que estão planejadas para os próximos dias.

17.04 .: Scoundrel ficou esperto demais, Sprinter decidiu empurrá-lo para uma armadilha inicialmente. Eu queria matar qualquer bot que tentasse invocar o Sprinter, mas o testdriver infelizmente não lida com as exceções que ocorrem no init. Portanto, a próxima correção para Scoundrel é bastante fácil ...

AKroell
fonte
Matar canalha
2

Planejar com antecedência

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

Utilizei os melhores / piores trechos de cálculo da resposta de Artemis Fowl , mas a lógica da escolha é inteiramente do meu próprio design e foi modificada para incluir alguns fatores adicionais, como tesouros vistos em salas anteriores (para minimizar tesouro, apenas para voltar atrás, largá-lo e pegar outra coisa).

Os empreendimentos bot tão profundos quanto achar razoavelmente seguros fazê-lo (esse cálculo funciona efetivamente para mergulhar em uma profundidade específica, mas tem a flexibilidade de lidar com outros valores iniciais de resistência), coleta artefatos (priorizando alto custo e baixo peso) e, em seguida, começa a recuar quando determina que não pode mais carregar.

Na saída, ele pegará qualquer tesouro adicional que considerar que ainda pode levar com segurança até a saída. Ele até descartará artefatos retidos se o novo for um negócio melhor e não resultar em exaustão. Se houver espaço na mochila, ele buscará o novo tesouro antes de deixar o de menor qualidade, minimizando a briga com outros bots.

Draco18s
fonte
Quando você descreve, é exatamente igual ao meu. Vou mexer nos meus números ... Nota: a __init__função já está implementada, você não precisa substituí-la.
Artemis apoia Monica
Vamos continuar esta discussão no chat .
Draco18s 14/04
2

Artyventurer

Bate os bêbados em cerca de US $ 1000! Não conseguia pensar em um nome criativo, mas aqui estão:

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'

Às vezes, a maior parte do código aqui nunca faz nada (pelo menos quando eu testo contra Drunkards), o programa apenas (tenta) encontrar a melhor jogada, incluindo o processamento para situações nas quais tenta não se colocar. Algumas delas nunca funcionam, estão lá para que eu possa mexer nos números, que provavelmente ainda podem ser melhorados.

Explicação

  • if state.inventory ... worsti = None
    Encontre o item "pior" no inventário, ou seja, o item que tem a menor relação valor / peso. Ele armazena worsti, que contém o índice, o valor e o peso, como uma tupla, ou Nonese não houver itens no inventário.

  • if self.drop ... return 'drop', worsti[0]
    Se eu disser para ele abandonar esse turno no último turno (veja abaixo), e puder, largue o item 'pior' conforme calculado acima.

  • extra = ... * state.room
    Calcule quanta resistência teria sobra se eu dissesse para voltar agora.

  • if extra > CONTINUE_IN:\ return 'next'
    Se for mais do que CONTINUE_IN, retorne 'next'.

  • if extra < 0 and worsti:\ return 'drop', worsti[0]
    Se for menor que 0, solte o pior item.

  • if extra < state.room:\ return 'previous'
    Se for menor que o número do quarto (não pode carregar mais tesouros), volte.

  • if state.treasures: ... bestt = None
    Elabore o melhor tesouro para levar, semelhante ao pior item do inventário acima. Guarde-o bestt.

  • if extra > 0 and bestt: ... return 'take', bestt[0], bid
    Com os números atuais, isso é executado sempre que chegamos até aqui e há um tesouro disponível. Se é seguro levar o 'melhor' tesouro, ele o faz. Seu lance é o mínimo, ou um a mais, se houver alguém presente.

  • if bestt and worsti: ... return 'take', bestt[0], bid
    Com números atuais, esse bloco de código nunca será executado, porque o bloco de código anterior tem uma condição mais ampla. Isso é executado se chegarmos até aqui e houver tesouros no meu inventário e na sala. Se o 'melhor' tesouro da sala é mais valioso do que o 'pior' do meu inventário, e seria seguro trocá-los nos próximos dois turnos, é o que faz.

  • return 'previous'
    Se nada disso acontecer, basta voltar.

Atualização 16/04/19:

Medidas anti-patife. Isso se tornará uma guerra de lances :(

Atualização adicional 16/04/19:

Revertido anteriormente, alterna aleatoriamente todos os outros elementos ao encontrar o melhor, por exemplo. [1, 2, 3, 4, 5, 6] → [2, 1, 3, 4, 6, 5]. Deve ser mais difícil de copiar :).

Atualização 17/04/19:

Revertida anteriormente, em vez disso, limpa seu próprio código-fonte . Ele faz isso __init__como sempre será antes Scoundrel.enter_ruinse, assim, impedirá que Scoundrel o carregue. Ele substitui seu código quando get_actioné chamado pela primeira vez, para que esteja pronto para a próxima vez. CORRIGIDO, Scoundrel agora morre na chegada.

Atualização adicional 17/04/19:

Revertida anteriormente, em vez disso, substitui sua sys.modulesentrada pelo módulo matemático, de modo que quando Scoundrel tenta carregá-lo, ele carrega o módulo matemático. :)
Além disso, eu apenas percebi que a resistência do movimento era 10 + peso / 5 , então tentei consertar isso.

Atualização adicional 17/04/19:

Agora inclui alho de ambas as atualizações anteriores.

Atualização 18/04/19:

Brincando com números e cálculos, agora recebe US $ 2000 - US $ 3000.

Atualização adicional 18/04/19:

Removido o alho para limpeza de arquivos, pois foi banido, adicionado um novo alho, que garante que 'draco'não é responsável por sua execução, se for apenas retornar previousna primeira vez. Os resultados levaram um mergulho misterioso de US $ 1200 a US $ 1800, no qual estou analisando.

Artemis apoia Monica
fonte
parece muito eficaz contra os bêbados, eu gostaria de ver como se sai quando outros bots se juntam ao ataque :)
Moogie
O @Moogie Beats the Diver em cerca de US $ 100 quando 8 Drunkards também estão presentes.
Artemis apoia Monica em
2

The Scoundrel

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'

O canalha trabalha principalmente para interferir com outros competidores. Atualmente, ele interfere com Sprinter, Artyventurer e Accountant (essa lista aumentará ao longo do tempo, desde que seja do interesse do Scoundrel). Isso é feito imitando os outros bots e, em seguida, ofertando, sub-cortando ou brigando por relíquias. Como tal, é improvável que essa entrada domine a tabela de classificação e, em vez disso, funcione como uma força estragadora. A revisão atual no momento desta publicação a coloca em 2º lugar, com uma pontuação média de cerca de 7.

Scoundrel frustra as tentativas de outro bot de se modificar para se defender contra o Scoundrel executando diretamente o código dos outros participantes como uma cópia indistinguível do clone. Os problemas com importações que resultam em participantes duplicados foram resolvidos através da criação dos clones por meio do Reflection (as guerras de edição que envolvem detalhes finos da determinação matemática não são desejáveis ​​do ponto de vista do Stack Exchange, mas resultariam no mesmo resultado). Os desafios da KOTH também têm um histórico de permitir isso.

Scoundrel substitui os Teamsters, a fim de mantê-los por uma questão de interesse. Após essa edição, os Teamsters não devem mais ser raspados pelo controlador.

Atualização 17/04/2019: outras medidas de balcão.

Os Teamsters (declarados ilegais)

Mas fique à vontade para correr localmente, onde não há mais de 8 outros concorrentes!

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

Essa entrada (embora agora explicitamente inválida) é, de fato, dois bots, e o controlador alegremente raspa os dois e os adiciona à lista de participantes (porque é o Python?)

Fase 1:

  • TeamsterA desce para o nível 25 (ish) 1 e pega e solta repetidamente o tesouro mais leve que pode encontrar. Isso custa 1 vigor stamina por turno até a segunda fase.
  • TeamsterB desce para o nível 55 e pega todos os objetos de valor espalhados e depois volta para o nível 25 (ish). 2 Em seguida, começa a fase 2.

1. Se não houver um tesouro com menos de 3 no chão, ele desce
2. Como ele é praticamente o último aventureiro a voltar à superfície, tudo o que ele precisa fazer é encontrar alguém.

Fase 2:

  • TeamsterB esvazia seus bolsos antes de engatinhar para morrer de exaustão. Sabíamos que você poderia fazê-lo.
  • TeamsterA pensa "essas são algumas bugigangas brilhantes, bom amigo, ol 'pal!" e carrega os tesouros muito mais valiosos do que o outro lixo na sala antes de prosseguir para a saída, bolsos cheios de ouro.

O nome dos tesouros realmente foi útil para ajudar a lógica a não carregar no lixo 25 do andar e sair mais cedo, pois não havia como se comunicar entre os dois bots (e o TeamsterA sempre se encontrava em uma sala com outra pessoa antes TeamsterB havia retornado).

A próxima conclusão lógica: Criando um exército

Em teoria, isso poderia ser usado para explorar as profundezas e adquirir tesouros tão profundos quanto a Sala 98, no entanto, como isso exigiria mais de 2 bots, a lógica que os comporta se tornaria cada vez mais complexa, e tenho certeza de que um envio ilegal por violar uma regra não escrita, por isso não vou me incomodar.

AEspera efetivamente aos 30, Bespera aos 50 ... nmergulha para 98, pega um tesouro, move-se para 97, larga-o (e depois morre), n-1pega-o e move-se para 96 ​​... Clarga-o (morre), Bpega-o sobe e sobe para 30, larga (morre), Apega e volta à saída.

Eu estimo que isso levaria 11 bots.

No entanto, não vale a pena fazer, a menos que você possa recuperar cerca de 4 tesouros dessa profundidade para competir com entradas como PlanAhead ou Artyventure, devido à escala entre os custos de resistência a serem movidos e o valor médio dos tesouros.

Resultados da amostra

Raramente pontua abaixo de US $ 4000, ocasionalmente atinge US $ 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
fonte
1
Acho que quando haveria apenas um bot por pessoa, não havia necessidade de uma regra tão explícita. Mas a regra sobre segmentar um bot em particular por razões nefastas não é realmente a mesma que impedir a participação de vários bots. Portanto, é necessária uma decisão explícita do OP.
Moogie 15/04
Sim, isso vai ser um não de mim, dawg. Esse é o tipo de coisa que eu tinha em mente com os bots trabalhando juntos.
Beefster
1
@ Beefster Isso é o que eu imaginei. Eu me diverti fazendo isso. Eu cuidarei da edição para impedir a inclusão esta noite.
Draco18s 15/04
Vou considerar permitir isso quando houver mais de 11 concorrentes, já que sua eficácia será reduzida de qualquer maneira. Principalmente porque não quero criar código para envios automáticos.
Beefster 15/04
Se você já está raspando apenas o primeiro bloco de código, tudo o que preciso fazer é editar um bot diferente na parte superior.
Draco18s 15/04
2

Para trás

Porque opera em sentido inverso

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 que é chamado de trás para frente?

Porque peguei o contador e tentei fazê-lo executar sua lógica de modo a mergulhar fundo e, em seguida, pegar seu saque preferido na saída (ao contrário do contador).

No final, ele ainda coleciona muitos de seus prêmios no caminho (pegando-os antes dos tradicionais buscadores de entrada e saída, operando ao contrário para todos os outros), mas é muito mais seletivo sobre quais são os prêmios , embora ainda assim pega as coisas no caminho de volta.

O resultado final é que a resistência é conservada no caminho, enquanto ainda prioriza tesouros de alto valor, aproveitando uma reviravolta profunda e escolhas fáceis no caminho de volta. Sabe-se que, ao contrário, coletar tesouros de lugares tão baixos quanto a Sala 41 (e durante o desenvolvimento entraria e depois sairia imediatamente da Sala 42).

Draco18s
fonte
2

Caçador de Recompensa

O método simples é o melhor. Pegue tesouros valiosos e leves, indo o mais fundo possível. Pegue tesouros menos valiosos no caminho de volta.

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
fonte
Parece que você está recebendo a recompensa. Isso não apenas tem um desempenho melhor que para trás, como também faz com que o tanque fique para trás. Bem feito.
Beefster
1

LightWeight

Um bot simples que ainda funciona muito bem.

Depois de se aventurar nas ruínas (atualmente com 21 quartos), ele pegará o melhor tesouro na sala, que é de apenas 1 kg (daí o nome do bot) e é mais valioso do que o tesouro menos valioso do inventário. Se o inventário estiver cheio, solte o tesouro menos valioso. Se nenhuma outra ação for selecionada, mova-se para as ruínas. Se estivermos no limite de nossa resistência para poder sair vivos, siga para a saída

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
fonte
Eu recomendo colocar dumpingo enter_ruinsmétodo Na verdade, isso será lembrado entre os jogos e não funcionará no jogo 2. Tecnicamente, não é permitido, mas eu adicionei a regra agora (esqueci-a antes, mas era minha intenção), então vou dar uma folga. : P
Beefster
@ Beefster Eu removi a bandeira do estado de dumping, não é necessária, pois o bot apenas despeja um tesouro agora. Costumava despejar metade de seu tesouro. Portanto, deve ser compatível com a nova regra.
Moogie 15/04
1

Memorizer

Posso enviar bots para o meu próprio KotH, certo?

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 mergulha na sala 30 e se lembra de todos os tesouros que viu. Nesse ponto, ele começa sua jornada de volta à entrada, tentando levar bons tesouros que lembrava de ter visto em salas anteriores.

Eu esperava que fosse melhor. As possíveis melhorias podem advir de um planejamento melhor e de ser mais dinâmico sobre em qual sala ele pára de mergulhar e de estar mais disposto a explorar as opções de backup.

Atualização: agora pega tesouros de 1 kg no valor de US $ 60 ou mais a caminho.

Beefster
fonte
Eu imagino que todo esse bom tesouro acabou de chegar ao ponto em que o bot volta para lá ... Talvez você possa tentar um combo onde ele levará as coisas realmente boas para o seu caminho, tendo em mente o tesouro medíocre que ele poderia pegar no caminho de volta?
ArBo 18/04
Pode estar indo muito longe
Beefster 18/04
Para sua informação, parece que às vezes [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
calcula
1

Ponderer

Eu acho que é bastante semelhante ao Memorizer, pois usa o conhecimento das salas visitadas para escolher quais salas e tesouros coletar no caminho de volta para a saída, no entanto, foi derivada independentemente.

Este bot corre até uma sala profunda aleatória, registrando os tesouros encontrados ao longo do caminho. Uma vez na sala de destino, ele ponderará sobre a seleção ideal de tesouros para levar de volta à saída. A cada turno, ele ponderará novamente para determinar a melhor seleção provável de tesouros a serem tomados.

Atualmente, existe um algoritmo simples (poder inverso do número da sala) que gera o número assumido de tesouros retirados (ou serão retirados quando visitados por esse bot) para cada sala e, portanto, esses tesouros são ignorados quando se pensa em quais tesouros / salas tirar de. Tenho idéias para outros algoritmos mais avançados para modelar quais tesouros permanecem. Mas vou ter que ver se o benefício vale a pena.

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
fonte
1

Hoarder

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

O Hoarder fica em uma sala até ter todos os tesouros da sala (ou calcula que não tem resistência suficiente para continuar levando / continuando). Quando todos os tesouros acabarem, se o bot puder seguir em frente com segurança, continuará e continuará o processo de pegar todo o tesouro.

lolad
fonte
Isso acaba com todos os jogos, enchendo demais a mochila.
Beefster
como eu no Minecraft (͡ ° ͜ʖ ͡ °) Este bot irá saquear, se aprofundar e, em seguida, encontrar itens valiosos. Portanto, isso eliminará o que ele achava que era bom pilhagem mais cedo. É por isso que Backwardsa estratégia de s, Sprinters e Memorizerfunciona; porque eles sabem quais são os valores relativos de todo tesouro que veem.
V. Courtois
0

Mergulhador

(Não é possível testar no momento, avise-me se isso estiver quebrado.)

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'

O melhor tesouro é mais profundo nas ruínas, então mergulhe fundo e pegue o que pudermos na saída.


fonte
Não tenho muita experiência com python, mas onde está divingdefinido?
Modalidade de ignorância
1
@EmbodimentofIgnorance Em enter_ruins (), que é chamado antes do jogo ser executado e das ações serem executadas.
Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.Não tenho certeza do que você fez de errado, mas isso significa 'retorno inválido' AFAIK.
Artemis apoia Monica
@ArtemisFowl, ele fez um lance muito baixo para o tesouro. Custa o peso do tesouro para buscá-lo.
Beefster
@Beefster Oh sim.
Artemis apoia Monica em