Poszukiwacze przygód w ruinach

27

Kierowca testowyDyskusja o wyzwaniuPrześlij poszukiwacza przygód

Pokój skarbów ( Źródło obrazu )

Kilku rywalizujących poszukiwaczy przygód napada na ruiny w poszukiwaniu skarbu, ale mogą nosić tylko tyle rzeczy naraz i mają swoje granice wytrzymałości. Chcą zdobyć najcenniejszy skarb i wydostać się, zanim staną się zbyt zmęczeni, aby kontynuować. Próbują stać się jak najbogatszymi ze swoich grabieży shenaniganów.

Rozgrywka

Każdy poszukiwacz przygód zaczyna w pierwszym pokoju lochu z 1000 punktami wytrzymałości i 50 kg miejsca w plecaku.

Gra działa w systemie turowym, a wszyscy gracze rozpatrują swoje tury jednocześnie. W każdej turze możesz wykonać jedną z następujących czynności:

  • Przejdź do następnego pokoju.
  • Przejdź do poprzedniego pokoju.
  • Licytuj wytrzymałość, aby zdobyć skarb.
  • Upuść skarb.

Przemieszczanie się między pokojami wymaga 10 punktów wytrzymałości plus 1 za każde 5 kg aktualnie w plecaku, zaokrąglone w górę. Na przykład poszukiwacz przygód niosący 3 kg skarbu wymaga 11 punktów wytrzymałości, a jeden niosący 47 kg wymaga 20 punktów wytrzymałości.

Upuszczenie skarbu wymaga 1 wytrzymałości bez względu na upuszczony skarb.

Po wyjściu z ruin gracz nie będzie podejmował więcej tur.

Jeśli gracz nie może wykonać żadnej z tych akcji (z powodu braku wytrzymałości lub braku skarbów), jego poszukiwacz przygód umiera z wyczerpania, rozlewając posiadany skarb do aktualnie zajmowanego pokoju. Podobnie, jeśli gracz podejmie próbę wykonania nieprawidłowej akcji, jego poszukiwacz przygód zostanie zamiast tego zabity przez pułapkę, co spowoduje takie samo rozlanie skarbu.

Licytacja

Minimalna oferta dla skarbu to 1 wytrzymałość na 1 kg ważonego skarbu. Możesz także licytować dodatkowe punkty wytrzymałości, aby zwiększyć prawdopodobieństwo zdobycia skarbu. Wytrzymałość, która została licytowana, jest zużywana bez względu na wynik.

W przypadku, gdy wielu graczy licytuje ten sam skarb, gracz, który licytuje najwięcej, otrzymuje skarb. Jeśli więcej niż jeden gracz złoży najwyższą ofertę, żaden z nich nie otrzyma skarbu.

Warunek wygranej

Gracz z największą całkowitą wartością skarbów jest zwycięzcą. W mało prawdopodobnym przypadku remisu, krawaty przechodzą do najmniejszej całkowitej wagi, następnie najmniejszej liczby skarbów, a następnie wartości najcenniejszego skarbu, drugiego najcenniejszego, trzeciego ... aż do zerwania remisu. W niemal niemożliwym przypadku, gdy w tym momencie nadal jest remis, kierowca testowy mówi „spieprzyć”, a zwycięzca zostaje w ten sposób ustalony arbitralnie.

W kontekście turnieju gracze zostaną sklasyfikowani z pierwszym miejscem, które otrzyma 10 punktów, drugim miejscem z 9 punktami, trzecim miejscem z 8 punktami itp., Z martwymi graczami i poszukiwaczami przygód bez skarbów, którzy zdobędą 0 punktów.

O ruinach

  • Każdy pokój początkowo zawiera między i skarbów. (Gdzie jest numerem pokoju)r3+3r2+5r
  • Istnieje dowolnie wiele pokoi, ograniczonych jedynie wytrzymałością poszukiwaczy przygód i chęcią eksploracji.
  • Każdy skarb będzie miał wartość pieniężną (w pełnych $) i wagę (w pełnych kg).
    • Skarby wydają się być bardziej cenne i obfite, gdy schodzisz głębiej w ruiny.
  • Szczegółowe formuły generowania skarbów są następujące: (używając notacji dla rzutów kostką) xdy
    • Masa jest najpierw generowana przy użyciu wzoru (minimum 1)2d62
    • Wartość skarbu jest następnie generowana przez (gdzie jest numerem pokoju, a jest wagą)1d[10w]+2d[5r+10]rw

Informacje widoczne dla graczy

W każdej turze gracze otrzymują następujące informacje:

  • Numer pokoju, w którym aktualnie przebywają. Jest to indeks 1, więc koncepcyjnie wyjście znajduje się w „pokoju 0”
  • Lista skarbów znajdujących się obecnie w pokoju
  • Lista innych graczy, którzy również znajdują się obecnie w pokoju.
  • Twój aktualny spis skarbów
  • Twój obecny poziom wytrzymałości

Kodowanie

Sterownik testowy można znaleźć tutaj .

Powinieneś zaimplementować podklasę tej Adventurerklasy:

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

Musisz tylko zastąpić get_actionmetodę. enter_ruinsjest uruchamiany przed rozpoczęciem gry i jest szansą na przygotowanie czegokolwiek, co chcesz mieć w grze. Nie musisz przesłonić __init__i naprawdę nie powinieneś . Jeśli Twoje __init__awarie zostaną zdyskwalifikowane.

get_actionotrzymuje pojedynczy argument namedtuplezawierający następujące pola (w tej kolejności, jeśli wolisz destrukcję):

  • room: numer pokoju, w którym aktualnie się znajdujesz
  • treasures: lista skarbów w pokoju
  • players: lista innych graczy w pokoju. W ten sposób otrzymujesz tylko nazwę gracza, więc nie wiesz, który bot kontroluje go, ani jaki jest jego ekwipunek / wytrzymałość.
  • inventory: lista skarbów w twoim plecaku
  • stamina: twój obecny poziom wytrzymałości

Ten obiekt zapewnia dodatkowo dwie właściwości narzędzia:

  • carry_weight: całkowita waga wszystkich skarbów, które nosisz
  • total_value: całkowita wartość wszystkich skarbów, które nosisz

Te treasuresi inventorywykazy zawierają namedtupleS z tych atrybutów:

  • name: nazwa skarbu (do celów kosmetycznych)
  • value: wartość pieniężna skarbu w $.
  • weight: waga skarbu w kg

get_action powinien zwrócić jedną z następujących wartości / wzorców:

  • 'next'lub 'previous'przejść do następnych / poprzednich pokoi
  • 'take', <treasure index>, <bid>(tak, jako krotka, choć każda sekwencja również technicznie będzie działała), aby licytować skarb pod danym indeksem na liście skarbów w pokoju. Oba argumenty powinny być liczbami całkowitymi. Pływaki będą zaokrąglane w dół.
  • 'drop', <inventory index>upuścić przenoszony skarb znaleziony przy danym indeksie. Indeks powinien (naturalnie) być liczbą całkowitą.

Inne ograniczenia

  • Możesz użyć tylko przypadkowej instancji podanej podczas inicjalizacji dla pseudolosowości.
    • Cokolwiek innego, co mogłoby wprowadzić behawioralny niedeterminizm, jest niedozwolone. Chodzi tutaj o to, aby boty zachowywały się identycznie, gdy otrzymają to samo ziarno, aby pomóc w testowaniu nowych botów (i potencjalnie błędów w sterowniku testowym). Tylko promieniowanie kosmiczne powinno powodować wszelkie odchylenia / niedeterminizm.
    • Należy pamiętać, że kody skrótu są losowe w Pythonie 3, więc używanie hashdo podejmowania jakichkolwiek decyzji jest niedozwolone. dicts są w porządku, nawet jeśli używamy kolejności iteracji dla decyzji, ponieważ kolejność jest gwarantowana spójna od Pythona 3.6.
  • Nie możesz ominąć sterownika testowego za pomocą ctypeshacków lub inspectstosu voodoo (lub innej metody). Istnieje kilka imponująco przerażających rzeczy, które możesz zrobić z tymi modułami. Proszę nie.
    • Każdy bot jest dobrze izolowany w piaskownicy dzięki obronnym kopiom i naturalnej niezmienności namedtuples, ale istnieją pewne nie do naprawienia luki / exploity.
    • Inne funkcje inspecti ctypesmogą być używane, o ile żadne z nich nie jest używane do obchodzenia funkcji kontrolera.
    • Żadna metoda przechwytywania instancji innych botów w bieżącej grze jest niedozwolona.
  • Boty powinny działać solo i nie mogą w żaden sposób koordynować się z innymi botami w jakimkolwiek celu. Obejmuje to tworzenie dwóch botów o różnych celach, z których jeden poświęca się dla sukcesu drugiego. Gdy jest więcej niż 10 konkurentów, tak naprawdę nie ma gwarancji, że oba boty będą w tej samej grze, a nazwy poszukiwaczy przygód nie dadzą żadnych wskazówek co do klasy bota, więc tego typu strategie są ograniczone.
  • Obecnie nie ma ścisłego ograniczenia czasu wykonania, jednak zastrzegam sobie prawo do ograniczenia go w przyszłości, jeśli turnieje zaczną trwać zbyt długo. Bądź rozsądny i staraj się utrzymywać przetwarzanie tury poniżej 100 ms , ponieważ nie przewiduję potrzeby ograniczenia go poniżej tego progu. (Turnieje potrwają około 2 godzin, jeśli wszystkie boty zajmą około 100 ms na turę.)
  • Twoja klasa botów musi mieć unikalną nazwę spośród wszystkich zgłoszeń.
  • Możesz nic nie pamiętać między grami. (Możesz jednak pamiętać rzeczy między turami )
    • Nie edytuj sys.modules. Wszystko poza zmiennymi instancji należy traktować jako stałą.
  • Nie możesz modyfikować kodu żadnego bota programowo, w tym własnego.
    • Obejmuje to usunięcie i przywrócenie kodu. Ma to na celu usprawnienie debugowania i turniejów.
  • Każdy kod powodujący awarię kontrolera zostanie natychmiast zdyskwalifikowany. Chociaż wychwyconych zostanie większość wyjątków, niektóre z nich mogą się prześlizgnąć, a segfaultów nie da się złapać. (Tak, możesz segfault w Pythonie dzięki ctypes)

Zgłoszenia

Aby ułatwić usuwanie odpowiedzi, wskaż nazwę bota u góry odpowiedzi za pomocą a #Header1i upewnij się, że twoja odpowiedź zawiera co najmniej jeden blok kodu (użyty zostanie tylko pierwszy blok w odpowiedzi). Nie musisz dołączać żadnych importów ani dokumentów, ponieważ zostaną one automatycznie dodane przez skrobak.

Będę bardziej skłonny głosować nad odpowiedziami szczegółowymi i zrozumiałymi wyjaśnieniami. Inni mogą zachowywać się tak samo.

Z grubsza mówiąc, twoja odpowiedź powinna być sformatowana mniej więcej tak:

# Name of Bot
Optional blurb

    #imports go here

    class BotName(Adventurer):
        #implementation

Explanation of bot algorithm, credits, etc...

(renderowane jako)

Nazwa bota

Opcjonalny napis

#imports go here

class BotName(Adventurer):
    #implementation

Objaśnienie algorytmu bota, kredytów itp.

Lokalne uruchamianie sterownika testowego

Będziesz potrzebował Pythona w wersji 3.7+ i zalecam także instalację tabulateza pomocą pip. Skrobanie tej strony w celu przesłania dodatkowo wymaga lxmli requests. Aby uzyskać najlepsze wyniki, należy również użyć terminala z obsługą znaków zmiany koloru ANSI. Informacje na temat konfiguracji w systemie Windows 10 można znaleźć tutaj .

Dodaj bota do pliku w podkatalogu w tym samym katalogu co ruins.py( ruins_botsdomyślnie) i pamiętaj o dodaniu from __main__ import Adventurergo na górze modułu. Jest to dodawane do modułów, gdy skrobak pobiera twoje zgłoszenie, i chociaż jest zdecydowanie zhackowane, jest to najprostszy sposób na upewnienie się, że bot ma właściwy dostęp Adventurer.

Wszystkie boty w tym katalogu będą ładowane dynamicznie w czasie wykonywania, więc dalsze zmiany nie są konieczne.

Zawody

Zwycięzca zostanie wyłoniony w serii gier z maksymalnie 10 botami w każdej grze. Jeśli zgłoszeń jest więcej niż 10, 10 najlepszych botów zostanie określonych poprzez systematyczne dzielenie ich na grupy po 10, aż każdy bot rozegra (dokładnie) 20 gier. 10 najlepszych botów zostanie wybranych z tej grupy z wynikami resetowania i będzie grać w gry, dopóki bot z pierwszego miejsca nie osiągnie 50-punktowej przewagi nad botem z drugiego miejsca lub do momentu rozegrania 500 gier.

Dopóki nie będzie co najmniej 10 zgłoszeń, puste miejsca zostaną wypełnione „Pijakami”, które wędrują losowo przez ruiny i zabierają (a czasami upuszczają) losowe skarby, dopóki nie wyczerpią się ich wytrzymałość i nie będą musiały płynąć do wyjścia.

Turnieje będą uruchamiane co tydzień, jeśli pojawią się nowe zgłoszenia. To jest otwarte wyzwanie KOTH bez określonej daty zakończenia.

Tabela liderów

Z biegu 4 maja 2019 o 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

Aktualizacja - 15 kwietnia: kilka aktualizacji / objaśnień do reguł

Aktualizacja - 17 kwietnia: zakaz kilku ważnych przypadków nikczemnych działań, takich jak modyfikowanie kodu innych botów.

Aktualizacja - 4 maja: Nagroda przyznana Sleafarowi za absolutne zniszczenie wstecz. Gratulacje!

Wołowina
źródło
1
Nareszcie tu jest! Chyba będę musiał zacząć robić mojego bota teraz.
Belhenix
12
Dlaczego ograniczenie do jednego bota? Mam kilka wzajemnie wykluczających się pomysłów i wolałbym nie wyrzucać idealnie dobrego bota za każdym razem, gdy wymyślam nowy.
@Mnemoniczny, głównie po to, by zapobiec przemieszczaniu nowych zgłoszeń przez użycie wielu prawie identycznych botów. Innym powodem było uniemożliwienie botom współpracy, ale i tak jest to wyraźnie zakazane. Rozważę na to pozwolenie. Zwolennicy dopuszczania wielu zgłoszeń, głosują za komentarzem Mnemonica powyżej.
Beefster
1
@ Draco18s Jeśli masz pipzainstalowany i włączony PATH(co jest domyślne w przypadku nowszych instalacji AFAIK), to z systemu Windows można uruchomić pip install modulenamew wierszu polecenia. W innych okolicznościach (o których nie wiem) przejdź do pip , wyszukaj potrzebny moduł i wybierz opcję.
Artemis obsługuje Monikę
1
Zgaduję, że będzie to „nie”, ale czy możemy zapisywać informacje za pośrednictwem turnieju? (np. kiedy oferta zadziałała)
Artemis obsługuje Monikę

Odpowiedzi:

5

Księgowa

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'

Księgowy jest osobą bardzo unikającą ryzyka. Lubi mieć pewność, że to, co robi, jest naprawdę najlepszą opcją w danej sytuacji. Stawia sobie więc cel i zbiera skarb tylko wtedy, gdy jego obliczenia pokazują, że ustawia go na właściwej drodze do tego celu. Jest jednak bardzo biurokratyczny i nie lubi upuszczać przedmiotów, o których wcześniej postanowił, że chce; wszelkie próby nauczenia go tego do tej pory powodowały, że księgowy upuścił przedmiot, a następnie natychmiast go podniósł.

Być może będzie kontynuowane.

ArBo
źródło
1
Dobra robota determinująca wartość skarbu. Zdecydowanie miałem na myśli napisanie lepszego kodu „czy warto”, ale jeszcze go nie dostałem. Łotr zbliża się do dolnej linii księgowego, ale ...
Draco18s
„wszelkie próby nauczenia go tego do tej pory skutkowały upuszczeniem przedmiotu przez księgowego, a następnie natychmiastowym podniesieniem go”. Można to obejść, zachowując zestaw upuszczonych nazw skarbów. Miałem przeczucie, że nazwy skarbów
przydadzą się
Dzięki, ale już zorientowałem się, dlaczego tak się stało. Nie wiem, czy natychmiast to naprawię, ponieważ rzadko korzysta z niego, gdy testowałem jego wprowadzenie.
ArBo
2

Accomodator

Oparty luźno na moim innym bocie LightWeight. Gdzie lekki bot był prosty, to bot jest dużo bardziej skomplikowana, aby pomieścić interakcje z innymi botami: zarówno łagodne i deliberatly distruptive.

Ten bot najpierw pobiegnie do losowo przydzielonego pokoju, a następnie spróbuje licytować za skarb o najlepszym stosunku wartości do masy, jeśli w pokoju są inni gracze, to załóżmy, że licytują, więc zamiast tego licytują drugi najlepszy skarb. Jeśli ta oferta nie powiedzie się, w następnej turze licytuj kolejny najlepszy skarb.

Gdy licytacja zakończyła się powodzeniem, powtarzaj licytację za najlepszy / drugi najlepszy wynik, aż w pokoju nie będzie już skarbów, a następnie przejdź głębiej do ruiny

Dla każdego pokoju wejdź w ruinę, powtarzaj licytację o najlepszy / drugi najlepszy wynik, dopóki nie będzie już więcej skarbów w pokoju, a następnie przejdź głębiej do ruiny lub jeśli wykryjemy, że nie możemy zejść głębiej, przełącz się do stanu „Wyjście” i zacznij spadać najgorsze skarb, dopóki nie będziemy w stanie zagwarantować, że możemy opuścić ruinę żywcem.

W stanie wyjścia sprawdzimy, czy możemy dodać 1 kg skarbu i nadal będziemy ożywiać, jeśli tak, to spróbujemy licytować skarb 1 kg, jeśli nie, to przejdziemy do poprzedniego pokoju.

Jego działanie jest dość zmienne ... jednak zwykle będzie jednym z trzech najlepszych botów.

import math

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

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

        worstMyTreasure = None
        worstMyTreasureId = -1

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

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

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

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

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

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

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

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

        chosenTreasure = bestRoomTreasure
        chosenTreasureId = bestRoomTreasureId

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

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

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

        # no treasures are better than what we already have so go to next/previous room
        self.bidValue = -1
        self.bidWeight = -1
        if (self.exiting == False):
          return 'next'
        else:
          return 'previous'
Moogie
źródło
Imponujący! Ten dominuje w turnieju w około 50 rundach.
Beefster
2

Sprinter

Podobnie jak Nurek, Sprinter wchodzi głęboko i zbiera najlepsze przedmioty w drodze powrotnej.

import math


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

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

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

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

    instance = None

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

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

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

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

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

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

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

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

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

Sprinter wchodzi głęboko, a następnie oblicza wynik dla każdego skarbu i podnosi wszystko powyżej pewnego progu. Ten próg zależy od pomieszczenia, w którym aktualnie się znajduje, ponieważ kosztuje więcej, aby zabrać przedmioty z pomieszczeń głębiej w ruinach.

Nadal mam 2 optymalizacje dotyczące „walki o skarb”, które są planowane na następne dni.

17.04 .: Scoundrel stał się zbyt bystry, Sprinter początkowo postanowił wpuścić go w pułapkę. Chciałem zabić dowolnego bota, który próbował wywołać Sprintera, ale testdriver niestety nie radzi sobie z wyjątkami, które zdarzają się przy init. Więc kolejna poprawka dla Scoundrel jest dość łatwa ...

AKroell
źródło
Zabijanie
łotrów
2

Planować naprzód

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

Wykorzystałem najlepsze / najgorsze fragmenty obliczeń z odpowiedzi Artemis Fowl , ale logika wyboru jest całkowicie moim własnym projektem i od tego czasu została zmodyfikowana, aby uwzględnić kilka dodatkowych czynników, takich jak skarby widoczne we wcześniejszych pokojach (w celu zminimalizowania odbioru skarb, tylko do cofnięcia się, upuszczenia i odebrania czegoś innego).

Bot zapuszcza się tak głęboko, jak uważa, że ​​jest to dość bezpieczne (obliczenia te skutecznie sprawdzają się podczas nurkowania na określoną głębokość, ale ma elastyczność w obsłudze innych początkowych wartości wytrzymałości), zbiera artefakty (priorytetem jest wysoki koszt i niska waga), a następnie zaczyna się wycofywać, gdy stwierdzi, że nie może już więcej nieść.

Wychodząc, zbierze dodatkowe skarby, które widzi, że może nadal bezpiecznie przenosić do wyjścia. Zrzuci nawet posiadane artefakty, jeśli nowy jest lepszą ofertą i nie spowoduje wyczerpania. Jeśli ma miejsce w plecaku, zbierze nowy skarb przed upuszczeniem go o niższej jakości, minimalizując walkę z innymi botami.

Draco18s
źródło
Huh, kiedy to opisujesz, to jest to samo co moje. Będę majstrować przy swoich liczbach ... Uwaga: __init__funkcja jest już zaimplementowana, nie musisz jej zastępować.
Artemis obsługuje Monikę
2

Artyventurer

Pokonaj pijaków o około 1000 $! Nie mogłem wymyślić twórczej nazwy, ale tutaj wszyscy:

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'

Czasami większość kodu tutaj nic nie robi (przynajmniej kiedy testuję go przeciwko Drunkards), program po prostu (próbuje) znaleźć najlepszy ruch, w tym przetwarzanie w sytuacjach, w których próbuje się nie poddać. Niektóre z nich nigdy nie mogą działać, po prostu są tam, więc mogę bawić się liczbami, które prawdopodobnie nadal można poprawić.

Wyjaśnienie

  • if state.inventory ... worsti = None
    Znajdź „najgorszy” element w ekwipunku, czyli przedmiot o najniższym stosunku wartości do masy. Przechowuje worsti, który zawiera jego indeks, jego wartość i wagę, jako krotka lub Nonejeśli w ekwipunku nie ma żadnych przedmiotów.

  • if self.drop ... return 'drop', worsti[0]
    Jeśli kazałem mu upuścić tę turę w ostatniej turze (patrz poniżej), i może, upuść „najgorszy” przedmiot, jak obliczono powyżej.

  • extra = ... * state.room
    Oblicz, ile wytrzymałości by pozostałoby, gdybym kazał jej wrócić teraz.

  • if extra > CONTINUE_IN:\ return 'next'
    Jeśli to więcej niż CONTINUE_IN, wróć 'next'.

  • if extra < 0 and worsti:\ return 'drop', worsti[0]
    Jeśli jest mniej niż 0, upuść najgorszy przedmiot.

  • if extra < state.room:\ return 'previous'
    Jeśli jest mniejszy niż numer pokoju (nie może nosić więcej skarbów), wróć.

  • if state.treasures: ... bestt = None
    Wypracuj najlepszy skarb do zabrania, podobny do najgorszego przedmiotu w powyższym ekwipunku. Przechowuj w bestt.

  • if extra > 0 and bestt: ... return 'take', bestt[0], bid
    Przy obecnych liczbach jest to wykonywane za każdym razem, gdy jesteśmy tak daleko i istnieje skarb. Jeśli bezpiecznie jest wziąć „najlepszy” skarb, robi to. Licytacja to minimum, lub jeszcze jedno, jeśli ktoś jest obecny.

  • if bestt and worsti: ... return 'take', bestt[0], bid
    Przy obecnych liczbach ten blok kodu nigdy się nie uruchomi, ponieważ poprzedni blok kodu ma szerszy warunek. Wykonuje się to, jeśli zaszliśmy tak daleko i w moim ekwipunku i pokoju znajdują się zarówno skarby / skarby. Jeśli „najlepszy” skarb w pokoju jest cenniejszy niż „najgorszy” skarb w moim ekwipunku i bezpiecznie byłoby go zamienić w ciągu następnych dwóch tur, robi to.

  • return 'previous'
    Jeśli nic się nie wydarzy, po prostu wróć.

Aktualizacja 16/04/19:

Środki przeciw łajdakowi. To stanie się wojną licytacyjną :(

Dalsza aktualizacja 16/04/19:

Cofnięto poprzedni, zamiast tego losowo przełącza każdy inny element przy znajdowaniu najlepszego, np. [1, 2, 3, 4, 5, 6] → [2, 1, 3, 4, 6, 5]. Powinny być trudniejsze do skopiowania :).

Aktualizacja 17/04/19:

Cofnął poprzedni, zamiast tego czyści swój własny kod źródłowy . Robi to, w __init__czym zawsze będzie wcześniej Scoundrel.enter_ruins, i tym samym powstrzyma Scoundrel przed załadowaniem go. Zastępuje swój kod przy get_actionpierwszym wywołaniu, aby był gotowy na następny raz. NAPRAWIONO, Łotr umiera teraz po przyjeździe.

Dalsza aktualizacja 17.04.19:

Cofnięty poprzedni, zamiast tego zastępuje sys.moduleswpis modułem matematycznym, więc gdy Scoundrel próbuje go załadować, zamiast tego ładuje moduł matematyczny. :)
Poza tym dopiero sobie uświadomiłem, że wytrzymałość na ruch wynosi 10 + waga / 5 , więc próbowałem to naprawić.

Dalsza aktualizacja 17.04.19:

Teraz obejmuje czosnek z obu poprzednich aktualizacji.

Aktualizacja 18/04/19:

Bawiąc się liczbami i obliczeniami, dostaje teraz 2000 USD - 3000 USD.

Dalsza aktualizacja 18/04/19:

Usunięto czosnek do usuwania plików, ponieważ został zbanowany, dodano nowy czosnek, który upewnia się, że 'draco'nie jest odpowiedzialny za jego działanie, jeśli tak, to po prostu wraca previousprzy pierwszej turze. Wyniki skoczyły w tajemniczy sposób do 1200–1800 USD, na co patrzę.

Artemis wspiera Monikę
źródło
wydaje się bardzo skuteczny przeciwko Pijakom, chciałbym zobaczyć, jak mu
idzie,
@Moogie Beats the Diver o około 100 USD, gdy obecnych jest również 8 Pijaków.
Artemis obsługuje Monikę
2

Łotr

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'

Łotr działa przede wszystkim na przeszkadzanie innym zawodnikom. Obecnie ingeruje w Sprintera, Artystera i Księgowego (lista ta będzie rosła w miarę upływu czasu, pod warunkiem, że leży to w najlepszym interesie Łajdaka). Robi to poprzez naśladowanie innych botów, a następnie licytowanie, podcinanie lub w inny sposób walkę o relikwie. W związku z tym jest mało prawdopodobne, aby ten wpis kiedykolwiek zdominował tabelę wyników i zamiast tego działał jako siła psująca. Obecna wersja w momencie publikowania tego artykułu stawia go na 2. miejscu ze średnią oceną wynoszącą około 7.

Scoundrel udaremnia próby innego bota, aby zmodyfikować się w celu obrony przed Scoundrel, wykonując bezpośrednio kod innych uczestników jako nierozróżnialną kopię klonu. Problemy z importem powodujące zduplikowanie uczestników zostały rozwiązane poprzez utworzenie klonów poprzez odbicie (wojny edycyjne z drobnymi szczegółami matematycznego określenia nie są pożądane z punktu widzenia wymiany stosów, ale doprowadziłyby do tego samego rezultatu). Wyzwania KOTH również mają na to swoją historię.

Scoundrel zastępuje Teamsters, aby zachować Teamsters ze względu na ich ciekawość. Po tej edycji kontroler nie powinien już zdrapywać Teamsters.

Aktualizacja 17.04.2019: dalsze środki zaradcze.

Teamsters (uznany za nielegalny)

Nie krępuj się biegać lokalnie tam, gdzie jest nie więcej niż 8 innych zawodników!

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

Ten wpis (choć teraz wyraźnie nieważny) to w rzeczywistości dwa boty, a kontroler chętnie zgarnie je oba i doda do listy uczestników (bo czyż nie, Python?)

Faza 1:

  • TeamsterA kieruje się na poziom 25 (ish) 1 i wielokrotnie podnosi i upuszcza najlżejszy skarb, jaki może znaleźć. To kosztuje ogromną 1 wytrzymałość na turę do drugiej fazy.
  • TeamsterB schodzi do poziomu 55 i podnosi wszystkie kosztowności leżące wokół, a następnie wraca do poziomu 25 (ish). 2 Następnie rozpoczyna się faza 2.

1. Jeśli na podłodze nie ma skarbu ważącego mniej niż 3, porusza się w dół 2. Ponieważ jest prawie pewnym, że
jest ostatnim poszukiwaczem przygód powracającym na powierzchnię, wszystko, co musi zrobić, to znaleźć kogoś.

Faza 2:

  • TeamsterB opróżnia kieszenie, po czym czołga się, by umrzeć z wyczerpania. Wiedzieliśmy, że możesz to zrobić.
  • TeamsterA myśli: „To są jakieś błyszczące świecidełka, dobry przyjacielu stary!” i ładuje o wiele bardziej cenne skarby niż inne śmieci w pokoju przed wyjściem, pełne kieszeni złota.

Nazwa skarbów faktycznie przydała się, aby pomóc logice nie załadować się na śmieci na podłodze 25 i wyjść wcześniej, ponieważ nie było sposobu na komunikację między dwoma botami (a TeamsterA zawsze wcześniej znajdowałby się w pokoju z kimś innym) TeamsterB powrócił).

Kolejny logiczny wniosek: stworzenie armii

Teoretycznie można to wykorzystać do ustalenia głębokości i zdobycia skarbów z głębokości nawet z Pokoju 98, jednak ponieważ wymagałoby to więcej niż 2 botów, logika składająca się z tych botów stawałaby się coraz bardziej złożona, a ponieważ jestem pewien, że tak jest nielegalne zgłoszenie za naruszenie niepisanej reguły, więc nie będę się tym przejmować.

Skutecznie Aczeka na 30, Bczeka na 50 ... nnurkuje do 98, podnosi skarb, przesuwa się do 97, upuszcza (a następnie umiera), n-1podnosi i przesuwa do 96 ... Cupuszcza (umiera), Bpodnosi w górę i przesuwa się do 30, upuszcza (umiera), Apodnosi go i wraca do wyjścia.

Szacuję, że zajęłoby to 11 botów.

Jednak nie warto tego robić, chyba że uda ci się odzyskać z tej głębokości około 4 skarbów, aby konkurować z takimi wejściami jak PlanAhead lub Artyventure, ze względu na skalowanie między kosztami wytrzymałości ruchu i średnią wartością skarbów.

Przykładowe wyniki

Rzadko osiąga wynik poniżej 4000 $, czasami grzebienie 6000 $.

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

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

[Turn 133] Rob the Smuggler (TeamsterA) exited the ruins with 255 stamina
    and 12 treasures, totaling $3527 in value.
[Game End] The game has ended!
[Game End] Eliwood the Forgettable (PlanAhead) won the game
Draco18s
źródło
1
Myślę, że jeśli na osobę przypadałby tylko jeden bot, nie było potrzeby stosowania tak wyraźnej reguły. Ale zasada celowania w konkretnego bota z nieistotnych powodów nie jest tak naprawdę taka sama, jak odrzucanie wielu botów połączonych razem. Potrzebne jest zatem wyraźne orzeczenie PO.
Moogie
Tak, to będzie ode mnie nie, skarbie. To właśnie miałem na myśli, gdy boty pracowały razem.
Beefster,
1
@ Beefster Tak właśnie wymyśliłem. Ale dobrze się bawiłem. Zajmę się tym wieczorem, aby zapobiec włączeniu edycji.
Draco18s
Rozważę na to pozwolenie, gdy będzie więcej niż 11 konkurentów, ponieważ jego skuteczność i tak się zwiększy. Głównie dlatego, że nie chcę tworzyć kodu do automatycznych zgłoszeń.
Beefster
Jeśli już skrobasz tylko pierwszy blok kodu, wszystko, co muszę zrobić, to edytować w innym bocie na górze.
Draco18s
2

Wstecz

Ponieważ działa odwrotnie

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'

Dlaczego nazywa się Backwards?

Ponieważ wziąłem Księgowego i próbowałem zmusić go do działania zgodnie z jego logiką, aby zanurkował głęboko, a następnie podniósł preferowane łupy w drodze do wyjścia (na tyłach Księgowego).

W końcu wciąż zbiera większość swoich nagród po drodze (zgarniając je, zanim zrobią to tradycyjni poszukiwacze zbierania, działając wstecz do wszystkich innych), ale jest znacznie bardziej selektywny w kwestii tego, które z nich bierze, chociaż nadal odbiera rzeczy w drodze powrotnej.

Efektem końcowym jest zachowanie wytrzymałości po drodze, przy jednoczesnym priorytetowym traktowaniu skarbów o wysokiej wartości, a następnie skorzystanie z głębokiego zwrotu i łatwego zbierania w drodze powrotnej. Od dawna wiadomo, że zbiera skarby aż z pokoju 41 (a podczas rozwoju wchodził, a następnie natychmiast opuszczał pokój 42).

Draco18s
źródło
2

Łowca nagród

Ta prosta metoda jest najlepsza. Chwyć cenne i lekkie skarby, wchodząc tak głęboko, jak to możliwe. W drodze powrotnej zdobądź mniej cenne skarby.

import math

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

    def get_action(self, state):
        can_go_deeper = state.stamina > (state.room + 2) * self.move_cost(state, 0)
        if state.treasures:
            best_ratio = 0
            best_index = 0
            best_weight = 0
            for i, treasure in enumerate(state.treasures):
                ratio = treasure.value / treasure.weight
                if ratio > best_ratio:
                    best_ratio = ratio
                    best_index = i
                    best_weight = treasure.weight
            limit = 160 if can_go_deeper else 60
            bid = best_weight + 2 if len(state.players) >= 1 else best_weight
            if state.carry_weight + best_weight <= 50 and best_ratio >= limit and state.stamina >= bid + state.room * self.move_cost(state, best_weight):
                return 'take', best_index, bid
        if can_go_deeper:
            return 'next'
        else:
            return 'previous'
Sleafar
źródło
Wygląda na to, że dostajesz nagrodę. Nie tylko działa to lepiej niż Wstecz, ale nawet powoduje Wstecz do czołgu. Dobra robota.
Beefster
1

Waga lekka

Prosty bot, który nadal działa całkiem dobrze.

Po zapuszczeniu się do ruin (obecnie 21 pokoi) zdobędzie najlepszy skarb w pokoju, który ma tylko 1 kg (stąd nazwa bota) i jest cenniejszy niż najmniej cenny skarb w ekwipunku. Jeśli ekwipunek jest pełny, zrzuć najmniej wartościowy skarb. Jeśli nie zostanie wybrana żadna inna akcja, przejdź do ruin. Jeśli jesteśmy na granicy naszej wytrzymałości, aby móc wyjść żywo, skieruj się do wyjścia

import math

class LightWeight(Adventurer):

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

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

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

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

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

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

            # dump the worst treasure
            return 'drop',worstMyTreasureId

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

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

        # no treasures are better than what we already have so go to next room
        return 'next'
Moogie
źródło
Polecam wprowadzenie dumpingw enter_ruinsmetody. To faktycznie zapamięta to między grami i nie będzie działać w grze 2. Technicznie niedozwolone, ale właśnie dodałem regułę (wcześniej o tym zapomniałem, ale było to moim zamiarem), więc zmniejszyłem trochę luzu. : P
Beefster
@ Beefster Usunąłem flagę stanu zrzutu, nie jest to konieczne, ponieważ bot zrzuca teraz tylko jeden skarb. Kiedyś zrzucał połowę swojego skarbu. Powinien więc być zgodny z nową regułą.
Moogie
1

Memorizer

Mogę przesyłać boty do własnego KotH, prawda?

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'

Ten robot nurkuje w pokoju 30 i pamięta wszystkie skarby, które widział. W tym momencie rozpoczyna wędrówkę z powrotem do wejścia, próbując zdobyć dobre skarby, które pamiętał z wcześniejszych pokoi.

Miałem nadzieję, że będzie lepiej. Możliwe ulepszenia mogą wynikać z lepszego planowania i bycia bardziej dynamicznym w zakresie tego, w którym pokoju przestaje nurkować, oraz z chęci eksploracji opcji tworzenia kopii zapasowych.

Aktualizacja: teraz po drodze zgarnia 1 kg skarbów o wartości 60 USD lub więcej.

Wołowina
źródło
Wyobrażam sobie, że cały ten dobry skarb zniknął do momentu, w którym bot się tam wraca ... Być może możesz wypróbować kombinację, w której zabierze on naprawdę dobre rzeczy po drodze, mając na uwadze mierny skarb, który mógłby zebrać w drodze powrotnej?
ArBo
Może być za daleko
Beefster
Do twojej wiadomości, wygląda na to, że czasami przeliczy się, jeśli ma wystarczającą wytrzymałość, aby odzyskać: [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
Larkeith
1

Myśliciel

Myślę, że jest bardzo podobny do Memorizera, ponieważ wykorzystuje wiedzę o odwiedzonych pokojach, aby wybrać, które pokoje i skarby zebrać w drodze powrotnej do wyjścia, jednak zostało ono niezależnie uzyskane.

Ten bot biegnie do przypadkowego głębokiego pokoju, w którym zapisuje skarby znalezione po drodze. W pokoju docelowym zastanawia się nad wyborem skarbów, które można zabrać z powrotem do wyjścia. Przy każdej turze zastanawia się ponownie, aby ustalić najbardziej prawdopodobny wybór skarbów do zabrania.

Obecnie istnieje prosty algorytm (odwrotna moc liczby pokojów), który daje założoną liczbę zabranych skarbów (lub zostanie zabranych podczas wizyty przez tego bota) dla każdego pokoju, więc te skarby są ignorowane przy rozważaniu, które skarby / pokoje wziąć od. Mam pomysły na inne bardziej zaawansowane algorytmy do modelowania, które skarby pozostały. Ale będę musiał sprawdzić, czy korzyść jest tego warta.

import math

class Ponderer(Adventurer):

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

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

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

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

    roomValueCache = valueCache.get(roomId)

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

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

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

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

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

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

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

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

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

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

      roomValueCache[carry_weight] = value
    return value

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

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

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

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

    self.rooms[state.room] = room

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

    self.exiting = True

    action = []
    valueCache = {}

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

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

    return action[0][0]
Moogie
źródło
1

Hoarder

import math

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

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

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

Hoarder pozostaje w pokoju, dopóki nie zabierze wszystkich skarbów w pokoju (lub obliczy, że nie ma wystarczającej wytrzymałości, aby kontynuować przyjmowanie / poruszanie się). Kiedy wszystkie skarby znikną, jeśli bot będzie mógł bezpiecznie przejść dalej, zrobi to i będzie kontynuował proces zbierania całego skarbu.

lolad
źródło
To umiera w każdej grze, przepełniając plecak.
Beefster
jak ja w Minecraft (͡ ° ͜ʖ ͡ °) Ten bot będzie grał, wchodził głębiej, a następnie znajdował cenne łupy. Więc porzuci to, co wcześniej uważał za dobry łup. Dlatego Backwards„s, Sprinter” s i Memorizer„s praca strategia; ponieważ wiedzą, jakie są względne wartości każdego skarbu, który widzą.
V. Courtois,
0

Nurek

(W tej chwili nie można przetestować, więc daj mi znać, czy to jest zepsute).

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'

Najlepszy skarb znajduje się głębiej w ruinach, więc zanurkuj głęboko, a następnie weź to, co możemy, wychodząc.


źródło
Nie mam zbyt dużego doświadczenia z pythonem, ale gdzie to jest divingzdefiniowane?
Wcielenie nieznajomości
1
@EmbodimentofIgnorance W enter_ruins (), która jest wywoływana przed uruchomieniem gry i wykonaniem akcji.
Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.Nie jestem pewien, co zrobiłeś źle, ale to oznacza AFAIK „nieprawidłowy zwrot”.
Artemis obsługuje Monikę
@ArtemisFowl on licytował zbyt nisko dla skarbu. Podniesienie go kosztuje wagę skarbu.
Beefster
@Beefster O tak.
Artemis obsługuje Monikę