Авантюристы в руинах


27

Тест-водительОбсуждение задачиОтправить Авантюрист

Комната Сокровищ ( Источник изображения )

Несколько конкурирующих искателей приключений совершают набеги на руины за сокровищами, но они могут нести только столько за раз и имеют свои пределы выносливости. Они хотят получить самое ценное сокровище и уйти, прежде чем они станут слишком уставшими, чтобы продолжать. Они пытаются стать настолько богатыми, насколько это возможно от их грабежей махинаций.

Игровой процесс

Каждый искатель приключений начинается в первой комнате подземелья с 1000 очками выносливости и 50 кг места в рюкзаке.

Игра работает пошагово, все игроки решают свои ходы одновременно. Каждый ход вы можете выполнить одно из следующих действий:

  • Перейдите в следующую комнату.
  • Переместитесь в предыдущую комнату.
  • Ставка выносливость, чтобы взять сокровище.
  • Бросай сокровище.

Перемещение между комнатами требует 10 выносливости, плюс 1 на каждые 5 кг в вашем рюкзаке с округлением. Например, авантюрист, несущий 3 кг сокровищ, требует 11 выносливости, а одному, несущему 47 кг, - 20 выносливости.

Для выпадения сокровищ требуется 1 выносливость, независимо от выпавшего сокровища.

После выхода из руин игрок больше не будет делать ходов.

Если игрок не может выполнить какое-либо из этих действий (из-за нехватки выносливости или отсутствия сокровищ), его искатель приключений умирает от истощения, проливая свое удерживаемое сокровище в занятую комнату. Точно так же, если игрок попытается сделать недопустимое действие, его искатель приключений будет убит ловушкой, что приведет к тому же утечке сокровищ.

торги

Минимальная ставка для сокровища составляет 1 выносливость на 1 кг, который весит сокровище. Вы также можете предложить дополнительные очки выносливости, чтобы с большей вероятностью получить сокровище. Выносимая ставка принимается независимо от результата.

В случае, если несколько игроков предложили взять одно и то же сокровище, игрок, предложивший самую высокую ставку, получает сокровище. Если более чем один игрок сделал самую высокую ставку, ни один из них не получит сокровище.

Условие выигрыша

Игрок с наибольшей общей стоимостью сокровищ является победителем. В маловероятном случае галстука, галстуки переходят к наименьшему общему весу, затем к наименьшему количеству сокровищ, затем к стоимости самого ценного сокровища, второго самого ценного, третьего ... до тех пор, пока связь не будет разорвана. В почти невозможном случае, когда в этой точке все еще есть ничья, тест-водитель говорит: «Винт это», и, таким образом, победитель определяется произвольно.

В контексте турнира игроки будут ранжироваться с первым местом, получающим 10 очков, вторым местом с 9 очками, третьим местом с 8 очками и т. Д., С мертвыми игроками и искателями приключений без сокровищ, набравшими 0 очков.

О руинах

  • Каждый номер первоначально содержит между р3+3ир2+5сокровищ. (Гдерномер комнаты)
  • Есть произвольно много комнат, ограниченных только выносливостью искателей приключений и готовностью исследовать.
  • Каждое сокровище будет иметь денежную оценку (в целом $) и вес (в целом кг).
    • Сокровища, как правило, становятся более ценными и многочисленными, когда вы углубляетесь в руины.
  • Конкретные формулы для создания сокровищ следующие: (с использованием обозначения ИксdY для бросков костей)
    • Вес генерируется первым по формуле 2d6-2 (минимум 1)
    • Затем ценность сокровища генерируется с помощью 1d[10*вес]+2d[5*р+10] (где р - номер комнаты, а вес - вес)

Информация, видимая игрокам

На каждом ходу игроки получают следующую информацию:

  • Номер комнаты, в которой они находятся. Это 1-индексированный, так что концептуально выход находится в «комнате 0»
  • Список сокровищ в настоящее время в комнате
  • Список других игроков, которые также в настоящее время находятся в комнате.
  • Ваш текущий инвентарь сокровищ
  • Ваш текущий уровень выносливости

кодирование

Тестовый драйвер можно найти здесь .

Вы должны реализовать подкласс этого Adventurerкласса:

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

Вам нужно только переопределить get_actionметод. enter_ruinsзапускается до начала игры, и это ваш шанс подготовить все, что вы хотите подготовить к игре. Вам не нужно переопределять __init__, и вы действительно не должны . Если ваш __init__сбой, вы будете дисквалифицированы.

get_actionполучает один аргумент a namedtupleсо следующими полями (в этом порядке, если вы предпочитаете деструктуризацию):

  • room: номер комнаты, в которой вы сейчас находитесь
  • treasures: список сокровищ в комнате
  • players: список других игроков в комнате. Вы получаете только имя игрока таким образом, поэтому вы не знаете, какой бот контролирует его или его инвентарь / выносливость.
  • inventory: список сокровищ в вашем рюкзаке
  • stamina: ваш текущий уровень выносливости

Этот объект дополнительно предоставляет два служебных свойства:

  • carry_weight: общий вес всех сокровищ, которые вы несете
  • total_value: общая стоимость всех сокровищ, которые вы несете

В treasuresи inventoryсписки содержат namedtupleS с этими атрибутами:

  • name: название сокровища (для косметических целей)
  • value: денежная стоимость сокровища в $.
  • weight: вес сокровища в кг

get_action должен вернуть одно из следующих значений / шаблонов:

  • 'next'или 'previous'перейти в следующую / предыдущую комнату
  • 'take', <treasure index>, <bid>(да, как кортеж, хотя любая последовательность будет работать и технически), чтобы сделать ставку на сокровище по указанному индексу в списке сокровищ комнаты. Оба аргумента должны быть целыми числами. Поплавки будут округлены вниз.
  • 'drop', <inventory index>сбросить с собой найденное сокровище, найденное по указанному индексу. Индекс должен (естественно) быть целым числом.

Другие ограничения

  • Вы можете использовать случайный экземпляр, предоставленный вам во время инициализации, только для псевдослучайности.
    • Все остальное, что может привести к поведенческому недетерминизму, не допускается. Намерение здесь состоит в том, чтобы заставить ботов вести себя одинаково, когда им дают одинаковое начальное число, чтобы помочь в тестировании новых ботов (и, возможно, ошибок в тестовом драйвере). Только космическое излучение должно вызывать любое отклонение / недетерминизм.
    • Имейте в виду, что хэш-коды рандомизированы в Python 3, поэтому использование hashдля принятия любого решения не допускается. dictЭто нормально даже при использовании порядка итераций для решений, поскольку порядок гарантированно согласован с Python 3.6.
  • Вы не можете обойти тестовый драйвер с помощью ctypesхаков или inspectстека вуду (или любого другого метода). Есть несколько поразительно страшных вещей, которые вы можете сделать с этими модулями. Пожалуйста, не надо.
    • Каждый бот достаточно хорошо изолирован в песочнице с помощью защитных копий и естественной неизменности namedtuples, но есть некоторые неуязвимые лазейки / эксплойты.
    • Другие функциональные возможности inspectи ctypesмогут использоваться, если ни один из них не используется для обхода функциональности контроллера.
    • Любой метод захвата экземпляров других ботов в вашей текущей игре не допускается.
  • Боты должны работать в одиночку и не могут никоим образом координировать свои действия с любыми другими ботами. Это включает в себя создание двух ботов с разными целями, так что один жертвует собой ради успеха другого. Если у вас более 10 соперников, вам не гарантируется наличие двух ботов в одной и той же игре, а имена авантюристов не дают никаких указаний на класс ботов, поэтому эти типы стратегий в любом случае ограничены.
  • В настоящее время нет жестких ограничений по времени выполнения, однако я оставляю за собой право жестко ограничить его в будущем, если турниры начнутся слишком долго. Будьте разумны и старайтесь обрабатывать ходы менее 100 мс , так как я не ожидаю необходимости ограничивать его ниже этого порога. (Турниры начнутся примерно через 2 часа, если на всех ботов потребуется около 100 мс за ход.)
  • Ваш класс ботов должен быть назван однозначно среди всех представленных.
  • Вы можете не помнить ничего между играми. (Тем не менее, вы можете помнить вещи между поворотами )
    • Не редактируйте sys.modules. Все, что находится за пределами переменных экземпляра, должно рассматриваться как константа.
  • Вы не можете изменять код любого бота программно, включая ваш собственный.
    • Это включает в себя удаление и восстановление вашего кода. Это должно сделать отладку и турниры более упорядоченными.
  • Любой код, вызывающий сбой контроллера, будет немедленно дисквалифицирован. В то время как большинство исключений будут обнаружены, некоторые могут проскользнуть, а ошибки могут быть не обнаружены. (Да, вы можете segfault в Python благодаря ctypes)

Материалы

Чтобы упростить проверку ответов, укажите имя вашего бота в верхней части ответа с помощью символа a #Header1и убедитесь, что ваш ответ содержит хотя бы один кодовый блок (будет использоваться только первый в вашем ответе). Вам не нужно включать какие-либо строки импорта или документации, так как они будут добавлены шабером автоматически.

Я буду более склонен давать ответы с подробными и понятными объяснениями. Другие, вероятно, будут вести себя так же.

Грубо говоря, ваш ответ должен быть отформатирован примерно так:

# Name of Bot
Optional blurb

    #imports go here

    class BotName(Adventurer):
        #implementation

Explanation of bot algorithm, credits, etc...

(отображается как)

Имя бота

Опциональная реклама

#imports go here

class BotName(Adventurer):
    #implementation

Объяснение алгоритма бота, кредиты и т.д ...

Запуск тестового драйвера локально

Вам понадобится Python 3.7+, и я рекомендую вам также установить его tabulateчерез pip. Соскреб этой страницы для представления дополнительно требует lxmlи requests. Вам также следует использовать терминал с поддержкой цветовых переходов ANSI для достижения наилучших результатов. Информацию о том, как настроить это в Windows 10, можно найти здесь .

Добавьте своего бота в файл в подкаталоге того же каталога, что и ruins.py( ruins_botsпо умолчанию), и обязательно добавьте from __main__ import Adventurerего в начало модуля. Это добавляется к модулям, когда скребок загружает вашу заявку, и, хотя он определенно хакерский, это самый простой способ убедиться, что у вашего бота правильно есть доступ Adventurer.

Все боты в этом каталоге будут загружаться динамически во время выполнения, поэтому дальнейших изменений не требуется.

Турнир

Окончательный победитель будет определен в серии игр, в каждой из которых будет по 10 ботов. Если общее количество отправленных сообщений превышает 10, лучшие 10 ботов будут определяться путем систематического разделения их на группы по 10 человек, пока каждый бот не сыграет (ровно) 20 игр. Лучшие 10 ботов будут выбраны из этой группы со счетом сброса и будут играть в игры до тех пор, пока бот, занявший первое место, не достигнет 50 баллов по сравнению со ботом, занявшим второе место, или пока не будет сыграно 500 игр.

Пока не будет получено как минимум 10 заявок, пустые слоты будут заполнены «Пьяницами», которые случайным образом бродят по руинам и берут (и иногда отбрасывают) случайные сокровища, пока у них не кончится выносливость, и они должны быть на пути к выходу.

Турниры будут повторно проводиться еженедельно, если появятся новые заявки. Это открытый вызов KOTH без установленной даты окончания.

Leaderboard

С пробега 4 мая 2019 года в 16:25. 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

Обновление - 15 апреля: пара обновлений правил / разъяснений

Обновление - 17 апреля: запрет на несколько заметных крайних случаев гнусных действий, таких как изменение кода других ботов.

Обновление - 4 мая: награда, присуждаемая Sleafar за полное уничтожение Backwards. Поздравляем!


1
Это наконец здесь! Думаю, мне придется начать делать свой бот сейчас.
Belhenix

12
Почему лимит в один бот? У меня есть несколько взаимоисключающих идей, и я бы предпочел не выбрасывать совершенно хорошего бота каждый раз, когда я создаю новый.

@Mnemonic, в основном это для предотвращения смещения новых представлений с помощью нескольких почти идентичных ботов. Другая причина состояла в том, чтобы запретить ботам работать вместе, но это явно запрещено в любом случае. Я подумаю над этим. Те, кто одобряет возможность подачи нескольких заявок, поддерживают комментарий Mnemonic выше.
Бифстер

1
@ Draco18s Если вы pipустановили и включили PATH(что по умолчанию для более новых установок AFAIK), то из окон вы можете запустить pip install modulenameв командной строке. Для других обстоятельств (о которых я не знаю), перейдите в pip , найдите нужный модуль и выберите опцию.
Артемида поддерживает Монику

1
Я предполагаю, что это будет «нет», но разрешено ли нам сохранять информацию через турнир? (например, когда сработала ставка)
Артемида поддерживает Монику

Ответы:


5

бухгалтер

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'

Бухгалтер очень склонен к риску. Ему нравится быть уверенным, что то, что он делает, является лучшим вариантом в данной ситуации. Таким образом, он ставит перед собой цель и только когда собирает сокровища, если его расчеты показывают, что это ставит его на правильный путь к этой цели. Однако он очень бюрократичен и не любит бросать вещи, которые он уже решил, что хотел; любые попытки научить его делать это привели к тому, что бухгалтер бросил предмет, а затем сразу же поднял его.

Возможно продолжение следует.


1
Хорошая работа по определению ценности сокровищ. Я определенно имел в виду написать лучший, «стоит ли это того» код, но пока не получил. Негодяй идет за нижней строкой бухгалтера, хотя ...
Draco18s

«любые попытки научить его делать это привели к тому, что бухгалтер бросил предмет, а затем сразу же поднял его». Вы можете обойти это, сохранив набор названий сокровищ. У меня было ощущение, что названия сокровищ пригодятся (даже если они просто сочтены прямо сейчас)
Beefster

Спасибо, но я уже понял, почему это произошло. Не знаю, исправлю ли я это сразу же, поскольку он редко использует его, когда я проверял, как его
вставить

2

Accomodator

Основано на моем другом боте LightWeight. Где Lightweight бот был прост, этот бот является гораздо более сложным для того , чтобы размещать до взаимодействия с другими ботами: как доброкачественный , так и deliberatly разрушительного.

Этот бот сначала побежит в случайно назначенную комнату, а затем попытается сделать ставку на лучшее сокровище в соотношении цена / вес. Если в комнате есть другие игроки, тогда предположим, что они также сделают ставку, поэтому вместо этого сделайте ставку на второе лучшее сокровище. Если эта ставка не удалась, то на следующем ходу ставка на следующее лучшее сокровище.

Как только ставка была успешной, повторяйте ставку для лучшего / второго лучшего, пока в комнате больше не будет сокровищ, а затем двигайтесь глубже в руины.

Для каждой комнаты входите в повторные торги за лучший / второй луч, пока в комнате больше не будет сокровищ, затем углубитесь в руины или если мы обнаружим, что мы не можем идти глубже, то перейдем в состояние «Выход» и начнем отбрасывать худшее. сокровище, пока мы не сможем гарантировать, что мы можем выйти из руин живыми.

Когда мы находимся в состоянии выхода, мы проверим, можем ли мы добавить 1 кг сокровища и все еще сделать его живым, если это так, тогда мы попытаемся сделать ставку на 1 кг сокровища, если нет, то мы отправимся в предыдущую комнату.

Его производительность довольно разнообразна ... однако, как правило, будет одним из трех лучших ботов.

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'

Впечатляет! Этот доминирует в турнире примерно в 50 раундах.
Бифстер

2

спринтер

Подобно Дайверу, Спринтер углубляется и подбирает лучшие предметы на своем пути назад.

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

Спринтер углубляется, затем вычисляет счет для каждого сокровища и подбирает что-либо выше определенного порога. Этот порог зависит от комнаты, в которой он находится в данный момент, потому что стоит брать предметы из комнат, расположенных глубже в руинах.

У меня все еще есть 2 оптимизма относительно "борьбы за сокровище", которые запланированы на следующие дни.

17.04 .: Негодяй стал слишком умным, Sprinter решил загнать его в ловушку. Сначала я хотел убить любого бота, который пытался вызвать Sprinter, но тест-драйвер, к сожалению, не обрабатывает исключения, которые происходят в init. Так что следующее исправление для негодяя довольно просто ...


Убийство негодяя находится в процессе ...
AKroell

2

План впереди

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

В ответе Артемиды Фаул я использовал подсчет лучших / худших расчетов , но логика выбора полностью принадлежит мне и с тех пор была изменена, чтобы включить в нее несколько дополнительных факторов, таких как сокровища, которые можно было увидеть в более ранних комнатах (чтобы свести к минимуму собирание сокровище, только чтобы отследить, бросить и взять что-то еще).

Бот рискует настолько глубоко, насколько это кажется разумно безопасным для этого (этот расчет эффективно работает для погружения на определенную глубину, но обладает гибкостью обработки других начальных значений выносливости), собирает артефакты (отдавая приоритет высокой стоимости и низкому весу), затем начинает отступать, как только определяет, что больше не может нести.

На выходе он соберет любые дополнительные сокровища, которые он видит, что он может все еще безопасно нести к выходу. Он даже отбросит уже имеющиеся артефакты, если новый будет более выгодным и не приведет к истощению. Если у него есть место в рюкзаке, он поднимет новое сокровище, прежде чем бросить менее качественное, минимизируя борьбу с другими ботами.


Да, когда ты это описываешь, это точно так же, как и я. Я возьмусь за свои цифры ... Примечание: __init__функция уже реализована, вам не нужно ее переопределять.
Артемида поддерживает Монику


2

Artyventurer

Бьет Пьяниц примерно на 1000 долларов! Не могу придумать креативное имя, но вот вы все:

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'

Иногда большая часть кода здесь никогда ничего не делает (по крайней мере, когда я тестирую его на Drunkards), программа просто (пытается) найти лучший ход, включая обработку для ситуаций, в которые она пытается не попасть. Некоторые из них никогда не запускаются, они просто есть, поэтому я могу поиграть с числами, которые, вероятно, еще можно улучшить.

объяснение

  • if state.inventory ... worsti = None
    Найдите «худший» предмет в инвентаре, то есть предмет, который имеет самое низкое соотношение стоимости и веса. Он хранит worsti, который содержит его индекс, его стоимость и вес, как кортеж, или Noneесли в инвентаре нет предметов.

  • if self.drop ... return 'drop', worsti[0]
    Если я велел ему бросить этот ход в последний ход (см. Ниже), и он может отбросить «худший» предмет, как рассчитано выше.

  • extra = ... * state.room
    Подсчитайте, сколько у него осталось бы выносливости, если бы я сказал, чтобы он вернулся прямо сейчас.

  • if extra > CONTINUE_IN:\ return 'next'
    Если это больше, чем CONTINUE_IN, вернитесь 'next'.

  • if extra < 0 and worsti:\ return 'drop', worsti[0]
    Если это меньше 0, отбросьте худший предмет.

  • if extra < state.room:\ return 'previous'
    Если он меньше номера комнаты (не может нести больше сокровищ), вернитесь назад.

  • if state.treasures: ... bestt = None
    Найдите лучшее сокровище, которое нужно взять, как и в худшем предмете из описанного выше. Сохраните это в bestt.

  • if extra > 0 and bestt: ... return 'take', bestt[0], bid
    С текущими числами это выполняется всякий раз, когда мы зашли так далеко, и есть доступное сокровище. Если безопасно взять «лучшее» сокровище, значит, так оно и есть. Это минимальная ставка, или еще одна, если кто-то присутствует.

  • if bestt and worsti: ... return 'take', bestt[0], bid
    С текущими числами этот блок кода никогда не будет выполнен, потому что предыдущий блок кода имеет более широкое условие. Это выполняется, если мы зашли так далеко, и в моем инвентаре и комнате есть и сокровища. Если «лучшее» сокровище в комнате более ценно, чем «худшее» сокровище в моем инвентаре, и было бы безопасно поменять их местами в течение следующих двух ходов, это так и есть.

  • return 'previous'
    Если ничего из этого не произошло, просто вернитесь.

Обновление 16/04/19:

Противозаконные меры. Это станет войной торгов :(

Дальнейшее обновление 16/04/19:

Отменено предыдущее, вместо этого случайным образом переключается каждый второй элемент при поиске лучшего, например. [1, 2, 3, 4, 5, 6] → [2, 1, 3, 4, 6, 5], Должно быть сложнее копировать :).

Обновление 17/04/19:

Отменено предыдущее, вместо этого он стирает свой собственный исходный код . Это делает то, __init__что всегда будет раньше Scoundrel.enter_ruins, и поэтому не позволит Scoundrel загружать его. Он заменяет свой код при get_actionпервом вызове, так что он будет готов к следующему разу. ИСПРАВЛЕНО, негодяй теперь умирает по прибытии.

Дальнейшее обновление 17/04/19:

Отменено предыдущее, вместо этого он заменяет свою sys.modulesзапись математическим модулем, поэтому, когда Scoundrel пытается загрузить его, он загружает математический модуль. :)
Кроме того, я только что понял, что выносливость хода была 10 + вес / 5 , поэтому попытался это исправить.

Дальнейшее обновление 17/04/19:

Теперь включает чеснок из обоих предыдущих обновлений.

Обновление 18/04/19:

Поигравшись с цифрами и расчетами, теперь получаю от 2000 до 3000 долларов.

Дальнейшее обновление 18/04/19:

Удален файл-чеснок, как он был забанен, добавлен новый чеснок, который гарантирует, что 'draco'он не отвечает за его работу, если он просто возвращается previousв свой первый ход. Результатом стало загадочное погружение до 1200-1800 долларов, которое я изучаю.


кажется очень эффективным против Пьяниц, я хотел бы увидеть, как это происходит, когда другие боты присоединяются к рейду :)
Moogie

@Moogie Бьет Дайвера примерно на $ 100, когда присутствуют 8 Пьяниц.
Артемида поддерживает Монику

2

Негодяй

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'

Подлец в первую очередь работает, чтобы мешать другим участникам. В настоящее время он мешает Sprinter, Artyventurer и Accountant (этот список со временем будет расти при условии, что он отвечает интересам негодяя). Он делает это, имитируя других ботов, а затем либо переигрывая, либо урезая, либо иным образом сражаясь за реликвии. Таким образом, маловероятно, что эта запись когда-либо будет доминировать в таблице лидеров и вместо этого будет действовать как побуждающая сила. Текущая редакция на момент публикации публикует 2-е место со средней оценкой около 7.

Негодяй мешает попыткам других ботов изменить себя, чтобы защитить себя от негодяя, непосредственно выполняя код других участников как неотличимую копию клона. Проблемы с импортом, приводившие к дублированию участников, были решены путем создания клонов с помощью Reflection (редактирование войн, связанных с мелкими деталями математического определения, нежелательно с точки зрения обмена стека, но приведет к тому же результату). Проблемы KOTH также имеют историю, позволяющую это делать.

Негодяй заменяет Teamsters, чтобы сохранить Teamsters ради того, чтобы быть интересным. После этого редактирования, команды больше не должны очищаться контроллером.

Обновление от 17.04.2009: дальнейшие встречные меры.

Команды (признаны незаконными)

Но не стесняйтесь бежать на месте, где не более 8 других участников!

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

Эта запись (в то время как теперь явно недействительна), на самом деле, два бота, и контроллер будет счастливо очистит их обоих и добавит их в список участников (потому что ура, Python?)

Фаза 1:

  • TeamsterA возглавляет до уровня 25 (иш) 1 и неоднократно поднимает и опускает самое легкое сокровище, которое он может найти. Это стоит колоссального 1 выносливости за ход до второй фазы.
  • TeamsterB спускается до уровня 55 и собирает все ценные вещи, которые лежат вокруг, а затем возвращается к уровню 25 (иш). 2 Затем начинается фаза 2.

1. Если на полу нет сокровища весом менее 3, он опускается вниз
2. Так как он гарантированно будет последним искателем приключений, вернувшимся на поверхность, все, что ему нужно сделать, - это найти кого-нибудь.

Фаза 2:

  • TeamsterB опустошает свои карманы, прежде чем упасть, чтобы умереть от истощения. Мы знали, что ты можешь сделать это.
  • TeamsterA считает, что это блестящие безделушки, приятель, приятель! и загружает гораздо более ценные сокровища, чем другой мусор в комнате, прежде чем идти к выходу, полные золотых карманов.

Название сокровищ действительно пригодилось для того, чтобы помочь логике не загружаться на мусор на 25 этаже и уходить рано, поскольку между двумя ботами не было возможности общаться (а TeamsterA всегда оказывался в комнате с кем-то еще раньше). TeamsterB вернулся).

Следующий логический вывод: создание армии

Теоретически это можно использовать для определения глубины и приобретения сокровищ из комнаты 98, однако, поскольку для этого потребуется более 2-х ботов, логика, составляющая этих ботов, будет становиться все более сложной, и, как я уверен, это незаконное представление за нарушение неписаного правила, поэтому я не собираюсь беспокоиться.

Эффективно Aждет в 30, Bждет в 50 ... nныряет до 98, подбирает сокровище, перемещается в 97, бросает его (а затем умирает), n-1поднимает его и перемещается в 96 ... Cбросает его (умирает), Bвыбирает его вверх и движется до 30, падает (умирает),A поднимает его и возвращается к выходу.

Я считаю, что это займет 11 ботов.

Однако делать это не стоит, если вы не сможете восстановить около 4 сокровищ с этой глубины, чтобы конкурировать с такими записями, как PlanAhead или Artyventure, из-за масштаба между затратами на выносливость и средней стоимостью сокровищ.

Пример результатов

Редко оценивается в 4000 долларов, иногда достигает 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

1
Я думаю, что, когда на человека приходилось только один бот, не было необходимости в таком явном правиле. Но правило нацеливания на конкретного бота по неосновательным причинам на самом деле не то же самое, что запрещать объединение нескольких ботов. Таким образом, требуется четкое решение от ФП.
Муги

Да, это будет не от меня, черт. Это то, что я имел в виду, когда боты работают вместе.
Говядина

1
@Beefster Это то, что я понял. Мне было весело делать это все же. Я позабочусь о включении «редактировать для предотвращения» этим вечером.
Draco18s

Я подумаю над тем, чтобы разрешить это, когда будет более 11 участников, так как в любом случае его эффективность будет выше. Главным образом потому, что я не хочу создавать код для автоматической отправки.
Beefster

Если вы уже очищаете только первый блок кода, все, что мне нужно сделать, это отредактировать другого бота вверху.
Draco18s

2

задом

Потому что он работает наоборот

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'

Почему это называется Назад?

Потому что я взял «Бухгалтера» и попытался заставить его выполнить свою логику так, чтобы он мог глубоко погрузиться, а затем забрать предпочитаемую добычу на выходе (в обратном направлении от бухгалтера).

В конце концов, он по-прежнему собирает большую часть своих призов (собирая их до того, как это делают традиционные искатели при сборе, действуя задом наперед ко всем остальным), но это гораздо более избирательно. выбирает, какие из них он берет, хотя все еще собирает вещи на обратном пути.

Конечным результатом является то, что выносливость сохраняется на пути, в то же время отдавая приоритет ценным сокровищам, а затем пользуясь преимуществом глубокого поворота вокруг и легкого выбора на пути назад. Известно, что задом наперед собирать сокровища из комнаты 41 (и во время разработки входил, а затем сразу уходил, комната 42).


2

Охотник за головами

Простой метод - лучший. Хватайте ценные и легкие сокровища, проходя как можно глубже. Возьмите менее ценные сокровища на обратном пути.

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'

Похоже, вы получаете награду. Мало того, что это работает лучше, чем Backwards, но даже вызывает Backwards к танку. Отлично сработано.
Бифстер

1

облегченный

Простой бот, который все еще работает довольно хорошо.

Выйдя в руины (в настоящее время 21 комната), он возьмет лучшее сокровище в комнате, которое составляет всего 1 кг (отсюда и название бота) и является более ценным, чем наименее ценное сокровище в инвентаре. Если инвентарь полон, бросьте наименее ценное сокровище. Если никакое другое действие не выбрано, двигайтесь в руины. Если мы находимся на пределе нашей выносливости, чтобы быть в состоянии выйти живым, то отправляйтесь к выходу

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'

Я бы порекомендовал поставить dumpingв enter_ruinsметод. Это будет на самом деле помнить это между играми и не будет работать в игре 2. Технически не допускается, но я добавил правило только сейчас (я забыл это раньше, но это было мое намерение), поэтому я немного ослаблю. : P
Beefster

@Beefster Я убрал флаг состояния сброса, он не нужен, так как теперь бот сбрасывает только одно сокровище. Раньше он сбрасывал половину своих сокровищ. Так что должно быть совместимо с новым правилом.
Муги

1

Memorizer

Я могу отправить ботов в свой собственный KotH, верно?

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'

Этот бот погружается в комнату 30 и запоминает все сокровища, которые он видел. В этот момент он начинает свой путь обратно ко входу, пытаясь взять хорошие сокровища, которые он помнил, видя в более ранних комнатах.

Я надеялся, что это будет лучше. Возможные улучшения могут быть достигнуты благодаря лучшему планированию и большей динамичности в отношении того, в какую комнату он прекращает погружаться, и большей готовности изучать варианты резервного копирования.

Обновление: теперь собирает сокровища весом 1 кг на сумму $ 60 или больше.


Я полагаю, что все эти хорошие сокровища просто исчезают к тому моменту, когда бот возвращается туда ... Возможно, вы можете попробовать комбо, где он перенесет действительно хорошие вещи, имея в виду посредственные сокровища, которые он может собрать на обратном пути?
АрБо

Это может
зайти

К вашему сведению, похоже, он иногда просчитывается, если у него достаточно выносливости, чтобы вернуться: [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
Larkeith

1

Задумавшийся

Я думаю, что он очень похож на Memorizer в том, что он использует знания посещенных комнат, чтобы выбрать, какие комнаты и сокровища собирать на обратном пути к выходу, однако он был получен независимо.

Этот бот бегает до случайной глубокой комнаты, в которой хранятся записи о найденных на этом пути сокровищах. Оказавшись в целевой комнате, он обдумает идеальный выбор сокровищ, чтобы вернуться к выходу. Каждый ход он будет обдумывать снова, чтобы определить наиболее вероятный лучший выбор сокровищ, чтобы взять.

В настоящее время существует простой алгоритм (обратная степень номера комнаты), который выдает предполагаемое количество сокровищ, которые были взяты (или будут взяты при посещении этого бота) для каждой комнаты, и поэтому эти сокровища игнорируются при размышлении о том, какие сокровища / комнаты взять из. У меня есть идеи для других более продвинутых алгоритмов, чтобы смоделировать, какие сокровища остаются. Но мне придется посмотреть, стоит ли это того.

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]

1

накопитель

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

Hoarder остается в комнате, пока не заберет все сокровища в комнате (или не подсчитает, что ему не хватает выносливости, чтобы продолжать брать / двигаться дальше). Когда все сокровища исчезнут, если бот сможет безопасно двигаться дальше, он продолжит процесс получения всех сокровищ.


Это умирает в каждой игре, переполняя свой рюкзак.
Beefster

как я в Minecraft () ° ͜ʖ ͡ °) Этот бот будет добывать, идти глубже, а затем найти ценный добычу. Так что это отбросит то, что он считал хорошей добычей ранее. Вот почему работают стратегии Backwards«s», Sprinter«s» и Memorizer«s»; потому что они знают, каковы относительные ценности каждого сокровища, которое они видят.
В. Куртуа

0

водолаз

(Не могу проверить в данный момент, поэтому дайте мне знать, если это не работает.)

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'

Лучшее сокровище глубже в руинах, так что ныряйте глубже, а затем возьмите все, что можем, на выходе.


Я не очень опытный с питоном, но где это divingопределено?
Воплощение Невежества

1
@EmbodimentofIgnorance In enter_ruins (), которая вызывается до запуска игры и выполнения действий.

Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.Не уверен, что вы сделали не так, но это означает «недействительный возврат» AFAIK.
Артемида поддерживает Монику

@ArtemisFowl, он предложил слишком низкую цену за сокровище. Чтобы забрать его, стоит вес сокровища.
Бифстер

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.