Пригоди в Руїнах


27

Тест-драйверОбговорення викликівНадіслати авантюриста

Кімната скарбів ( Джерело зображення )

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

Ігровий процес

Кожен авантюрист починає в першій кімнаті підземелля з 1000 очками витривалості та 50 кг місця у своєму рюкзаку.

Гра діє по черзі, і всі гравці вирішують свої повороти одночасно. Кожен поворот можна виконати одну з наступних дій:

  • Перехід до сусідньої кімнати.
  • Переїзд до попередньої кімнати.
  • Пропонуйте витримати взяти скарб.
  • Киньте скарб.

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

Для викидання скарбу потрібна 1 витримка незалежно від скинутого скарбу.

Після виходу з руїн гравець більше не обійдеться.

Якщо гравець не може вчинити жодної з цих дій (через дефіцит витривалості чи відсутність скарбів), їхній авантюрист помирає від виснаження, перекидаючи свій затриманий скарб у зайняту зараз кімнату. Аналогічно, якщо гравець намагається здійснити недійсну дію, їх авантюрист буде вбитий замість пастки, що призведе до того ж розсипання скарбів.

Торги

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

У випадку, якщо кілька гравців подають заявку на отримання одного і того ж скарбу, гравець, який зробив ставку найвищий, отримує скарб. Якщо більшість гравців зробили найвищу ставку, жоден із них не отримає скарб.

Умова виграшу

Гравець з найбільшою загальною вартістю скарбів є переможцем. У випадку навряд чи краватки, краватки йдуть до найменшої загальної ваги, потім до найменшої кількості скарбів, потім до вартості найціннішого скарбу, другого найціннішого, третього ... до тих пір, поки краватка не буде розірвана. У майже неможливому випадку, коли в цій точці все ще є нічия, тестовий драйвер каже "накрутити", і переможець тим самим визначається довільно.

У контексті турніру гравці посідають перше місце, отримуючи 10 балів, друге місце з 9 очками, третє місце з 8 балами тощо тощо, а мертві гравці та авантюристи без скарбів набирають 0 балів.

Про Руїни

  • Кожна кімната спочатку містить між r3+3іr2+5скарбів. (Деrномер номера)
  • Є довільно багато кімнат, обмежених лише витривалістю авантюристів та готовністю досліджувати.
  • Кожен скарб матиме грошову вартість (у цілому $) та вагу (у цілому кг).
    • Скарби, як правило, є більш цінними та багатими, коли ви заглиблюєтесь у руїни.
  • Конкретні формули для отримання скарбів такі: (використовуючи позначення хгу для рулонів з кістки)
    • Вага формується спочатку за допомогою формули 2г6-2 (мінімум 1)
    • Значення скарбу тоді генерується через 1г[10ш]+2г[5r+10] (де r - номер приміщення, а ш - вага)

Інформація, доступна для гравців

На кожному кроці гравці отримують таку інформацію:

  • Кількість номерів, в яких вони зараз перебувають. Це 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отримує єдиний аргумент, який представляє собою 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для будь-якого прийняття рішень заборонено. dicts добре, навіть якщо використовується порядок ітерації для прийняття рішень, оскільки порядок гарантується послідовним з Python 3.6.
  • Ви не можете обійти тестовий драйвер, використовуючи ctypesхаки абоinspect стек вуду (або будь-який інший метод). Є кілька вражаючих страшних речей, які ви можете зробити з цими модулями. Будь ласка, не варто.
    • Кожен бот розміщений з піском у захисних копіях та природній незмінності namedtuple s, але є деякі незмінні лазівки / подвиги.
    • Інша функціональність від inspectі ctypesможе використовуватися до тих пір, поки жодна з них не використовується для обходу функцій контролера.
    • Будь-який метод захоплення екземплярів інших ботів у вашій поточній грі не дозволений.
  • Боти повинні діяти соло і не можуть жодним чином координуватись з будь-якими іншими ботами. Це включає створення двох ботів з різними цілями, так що одна жертвує собою заради успіху іншої. Коли буде більше 10 конкурентів, ви фактично не будете гарантовано мати двох ботів в одній грі, а назви авантюристів не вказують на клас бота, тому такі стратегії все одно обмежені.
  • В даний час немає жорсткого обмеження часу виконання, однак я залишаю за собою право жорстко обмежити його в майбутньому, якщо турніри почнуть тривати занадто довго. Будьте розумні і намагайтеся тримати обробку повороту менше 100 мс , оскільки я не передбачаю, що потрібно обмежувати її нижче цього порогового значення. (Турніри триватимуть приблизно через 2 години, якщо всі боти займають близько 100 мс на обороту.)
  • Ваш клас бота повинен бути названий однозначно серед усіх матеріалів.
  • Ви можете нічого не пам’ятати між іграми. (Однак ви можете запам'ятати речі між поворотами )
    • Не редагуйте sys.modules. Все, що знаходиться за межами змінних екземплярів, слід розглядати як константу.
  • Ви не можете змінювати код будь-якого бота програмно, включаючи свій власний.
    • Сюди входить видалення та відновлення коду. Це робить налагодження та турніри більш спрощеними.
  • Будь-який код, який викликає збій контролера, буде негайно дискваліфікований. Незважаючи на те, що буде вилучено більшість винятків, деякі можуть проскочити, і segfault не можна побачити. (Так, ви можете сегментувати в Python завдяки ctypes)

Подання

Для того, щоб допомогти зібрати відповіді, вкажіть ім’я вашого бота вгорі відповіді з а #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 без встановленої дати закінчення.

Таблиця лідерів

З запуску 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 за те, що він абсолютно знищив назад. Вітаємо!


1
Це нарешті тут! Здогадуюсь, мені зараз доведеться почати робити свого бота.
Belhenix

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

@Mnemonic, здебільшого, це запобігти витісненню нових матеріалів, використовуючи кілька близьких ідентичних ботів. Інша причина полягала в тому, щоб боти не могли працювати разом, але це явно заборонено. Я розглядаю, як це дозволити. Ті, хто виступає за те, щоб дозволити кілька заявок, підтверджують коментар Mnemonic вище.
Beefster

1
@ Draco18s Якщо ви pipвстановили і ввімкнено PATH(що за замовчуванням для нових установок AFAIK), тоді з Windows ви можете запустити pip install modulenameкомандний рядок. Для інших обставин (про які я не знаю), перейдіть на піп , шукайте потрібний модуль і виберіть варіант.
Артеміда підтримує Моніку

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
Хороша робота, що визначає вартість скарбу. Я, безумовно, мав на увазі написати якийсь кращий код "чи варто", але ще не потрапив туди. Негідник іде за
підсумком

"будь-які спроби навчити його робити це до цього часу призводять до того, що Бухгалтер скидає предмет, а потім відразу після цього забирає його." Ви могли обійти це, зберігаючи набір скинутих імен скарбів. У мене було відчуття, що назви скарбів стануть у нагоді (хоча вони зараз просто пронумеровані)
Beefster

Дякую, але я вже зрозумів, чому це сталося. Не знаю, чи негайно я це
виправлю

2

Проживання

Базуючись на моєму іншому боті LightWeight. Де Lightweight бот був простий, цей бот є набагато більш складним для того , щоб розміщувати до взаємодії з іншими ботами: як доброякісний , так і deliberatly руйнівного.

Цей бот спочатку спринтується до довільно виділеної кімнати, а потім спробує подати заявку на найкращий скарб співвідношення співвідношення ціна / вага, якщо в кімнаті є інші гравці, то припустимо, що вони також будуть робити ставки, а натомість подавати заявку на другий найкращий скарб. Якщо ця пропозиція не вдасться, тоді наступна черга на наступний кращий скарб.

Після того, як пропозиція була успішною, повторіть торги за кращий / secondbest, поки в кімнаті не буде більше скарбів, а потім перемістіться глибше в руїну

Для кожної кімнати введіть у руїну повторні торги за кращий / secondbest, поки в ньому немає більше скарбів, потім перемістіться глибше в руїну або якщо ми виявимо, що ми не можемо піти глибше, тоді поміняйтеся на стан "Exiting" і починайте скидати найгірше скарб, поки не зможемо гарантувати, що ми можемо вийти з руїни живими.

Коли у вихідному стані ми перевіримо, чи зможемо ми додати скарб на 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 раундів.
Beefster

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

Спринтер заглиблюється, потім обчислює бал за кожен скарб і підбирає що-небудь вище певного порогу. Цей поріг залежить від кімнати, в якій він зараз перебуває, тому що коштувати дорожче, щоб забрати предмети з приміщень глибше руїн.

У мене ще є дві оптимізації щодо "боротьби за скарб", які заплановані на наступні дні.

17.04 .: Scoundrel став занадто розумним, Sprinter вирішив підштовхнути його в пастку. Спочатку я хотів убити будь-якого бота, який намагався викликати Sprinter, але testdriver, на жаль, не обробляє винятків, які трапляються на 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'

Іноді більша частина коду тут ніколи нічого не робить (принаймні, коли я перевіряю його проти "П'яниць"), програма просто (намагається) знайти найкращий крок, включаючи обробку для ситуацій, у які вона намагається не ставити себе. Деякі з них ніколи не можуть працювати, вони просто є, щоб я міг поспішати з цифрами, які, можливо, ще можна вдосконалити.

Пояснення

  • 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 та Бухгалтеру (цей перелік буде зростати з часом, за умови, що це в інтересах Скандала). Це робиться, наслідуючи інших ботів, а потім або проводячи торги, недорізаючи або іншим чином воюючи за мощі. Таким чином, малоймовірно, що цей запис коли-небудь домінуватиме в таблиці лідерів і замість цього функціонує як руйнівна сила. Поточна редакція на момент публікації цієї публікації ставить її на 2 місце із середньою оцінкою близько 7.

Скандал переслідує спроби інших ботів змінити себе, щоб захищатись від Негідника, безпосередньо виконуючи код інших учасників як нерозрізну копію клону. Проблеми з імпортом, що призводять до дублювання учасників, вирішувались шляхом створення клонів за допомогою Reflection (редагувати війни, що передбачають дрібні деталі математичного визначення, з точки зору Stack Exchange не бажано, але це призведе до того ж результату). Виклики KOTH також мають історію, що дозволяє це зробити.

Скандал замінює Командирів, щоб зберегти Команди заради того, щоб було цікаво. Після цього редагування, Командистів більше не слід скреблювати контролером.

Оновлення 17.04.2019: подальші заходи протидії.

Команди (нелегально)

Але сміливо бігайте на місцях, де не більше 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

Цей запис (поки він явно недійсний) насправді є двома ботами, і контролер із задоволенням зішкребить їх обох і додасть їх до списку учасників (адже hooray Python?)

Фаза 1:

  • TeamsterA прямує до рівня 25 (ish) 1 і кілька разів підбирає і скидає найлегший скарб, який він може знайти. Це коштує колосальних 1 витривалості обороту до другої фази.
  • TeamsterB рухається вниз до рівня 55 і збирає всі цінності, що лежать навколо, а потім направляється назад до рівня 25 (ish). 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
Я думаю, що коли тільки один бот на людину збирався, не було потреби в такому явному правилі. Але правило щодо націлювання на конкретного бота з непристойних причин насправді не те саме, що забороняти спільну роботу декількох ботів. Тому потрібна чітка ухвала від ОП.
Мугі

Так, це буде ні від мене, дауг. Це та річ, яку я мав на увазі, коли боти працюють разом.
Beefster

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'

Схоже, ти отримуєш щедрості. Це не тільки краще, ніж назад, але навіть спричиняє резервування назад. Молодці.
Beefster

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 Я видалив державний прапор демпінгу, він не потрібен, оскільки бот зараз скидає лише один скарб. Раніше скидав половину скарбу. Тому має бути сумісним з новим правилом.
Moogie

1

Запам'ятовувач

Я можу подати ботів у свій власний 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 доларів або більше.


Я уявляю, що весь цей гарний скарб просто минув, коли бот повертається туди ... Можливо, ви можете спробувати комбо, де він візьме дійсно хороші речі на своєму шляху, маючи на увазі посередній скарб, який він може забрати. на шляху назад?
ArBo

Це може зайти занадто далеко
Beefster

FYI, схоже, іноді прорахується, якщо у нього є достатня витримка, щоб повернутися: [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
Ларкіт,

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

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


Це гине кожну гру, переповнюючи її рюкзак.
Beefster

як я в Minecraft (͡ ° ͜ʖ ͡ °) Цей бот розграбує, піде глибше, а потім знайде цінне бабло. Тож він упустить те, що раніше вважав гарним грабунку. Ось чому стратегія Backwards", Sprinter" і Memorizer"працює"; бо вони знають, які відносні значення кожного скарбу вони бачать.
В. Куртуа

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'

Найкращий скарб знаходиться глибше в руїнах, тому зануримося вглиб, тоді захопимо все, що можемо, на виході.


Я не дуже досвідчений з python, але де це divingвизначено?
Втілення невігластва

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

Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.Не впевнений, що ви зробили не так, але це означає "недійсне повернення" AFAIK.
Артеміда підтримує Моніку

@ArtemisFowl він запропонував занадто низьку кількість скарбу. Вага скарбу коштує, щоб забрати його.
Beefster

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.