Avventurieri nelle Rovine


27

Test driverDiscussione sulla sfidaInvia avventuriero

Sala del tesoro ( Fonte immagine )

Diversi avventurieri rivali stanno razziando le rovine per il tesoro, ma possono trasportare così tanto alla volta e avere i loro limiti di resistenza. Vogliono ottenere il tesoro più prezioso ed uscire prima di diventare troppo stanchi per continuare. Stanno cercando di diventare il più ricchi possibile dai loro shenanigani saccheggiatori.

gameplay

Ogni avventuriero inizia nella prima stanza della prigione con 1000 punti di resistenza e 50 kg di spazio nel proprio zaino.

Il gioco funziona a turni, con tutti i giocatori che risolvono i loro turni contemporaneamente. Ad ogni turno, puoi effettuare una delle seguenti azioni:

  • Passa alla stanza successiva.
  • Passa alla stanza precedente.
  • Offri resistenza per prendere un tesoro.
  • Rilascia un tesoro.

Lo spostamento tra le stanze richiede 10 resistenza, più 1 per ogni 5 kg attualmente nello zaino, arrotondato per eccesso. Ad esempio, un avventuriero che trasporta 3 kg di tesoro richiede 11 resistenza per muoversi e uno che porta 47 kg richiede 20 resistenza per muoversi.

La caduta del tesoro richiede 1 resistenza indipendentemente dal tesoro lasciato cadere.

All'uscita dalle rovine, il giocatore non effettuerà più turni.

Se un giocatore non può compiere nessuna di queste azioni (a causa della mancanza di resistenza o dell'assenza di tesori), il suo avventuriero muore di sfinimento, riversando il tesoro detenuto nella stanza attualmente occupata. Allo stesso modo, se un giocatore tenta di compiere un'azione non valida, il suo avventuriero verrà invece ucciso da una trappola, causando la stessa fuoriuscita di tesori.

offerta

L'offerta minima per un tesoro è di 1 resistenza per 1 kg che pesa il tesoro. Puoi anche offrire punti di resistenza aggiuntivi per avere maggiori probabilità di ottenere il tesoro. La resistenza che è stata offerta viene consumata indipendentemente dal risultato.

Nel caso in cui più giocatori abbiano offerto di prendere lo stesso tesoro, il giocatore che punta più in alto ottiene il tesoro. Se più di un giocatore ha fatto l'offerta più alta, nessuno di loro riceverà il tesoro.

Condizioni di vittoria

Il giocatore con il maggior valore totale di tesori è il vincitore. Nell'improbabile caso di un pareggio, i legami vanno al minor peso totale, quindi al minor numero di tesori, quindi al valore del tesoro più prezioso, il secondo più prezioso, il terzo ... fino a quando il pareggio non viene rotto. Nel caso quasi impossibile che ci sia ancora un pareggio a questo punto, il collaudatore dice "fanculo" e il vincitore viene quindi determinato arbitrariamente.

Nel contesto del torneo, i giocatori saranno classificati con il primo posto che riceve 10 punti, il secondo posto con 9 punti, il terzo posto con 8 punti, ecc ..., con giocatori morti e avventurieri senza tesori che segnano 0 punti.

A proposito delle rovine

  • Ogni stanza inizialmente contiene tra e tesori. (Dove è il numero della stanza)r3+3r2+5r
  • Ci sono arbitrariamente molte stanze, limitate solo dalla resistenza degli avventurieri e dalla volontà di esplorare.
  • Ogni tesoro avrà un valore monetario (in $ interi) e un peso (in kg interi).
    • I tesori tendono ad essere più preziosi e abbondanti man mano che approfondisci le rovine.
  • Le formule specifiche per generare tesori sono le seguenti: (usando la notazione per i tiri di dado) Xdy
    • Il peso viene generato per primo utilizzando la formula (minimo 1)2d6-2
    • Il valore del tesoro viene quindi generato tramite (dove è il numero della stanza e è il peso)1d[10*w]+2d[5*r+10]rw

Informazioni visibili ai giocatori

Ad ogni turno, i giocatori ottengono le seguenti informazioni:

  • Il numero della stanza in cui si trovano attualmente. Questo è 1 indicizzato, quindi concettualmente l'uscita è alla "stanza 0"
  • Un elenco di tesori attualmente presenti nella stanza
  • Un elenco di altri giocatori che sono anche attualmente nella stanza.
  • Il tuo attuale inventario di tesori
  • Il tuo attuale livello di resistenza

Coding

Il driver di test può essere trovato qui .

È necessario implementare una sottoclasse di questa Adventurerclasse:

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

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

    def enter_ruins(self):
        pass

Hai solo bisogno di sovrascrivere il get_actionmetodo. enter_ruinsviene eseguito prima dell'inizio di una partita ed è la tua occasione per preparare tutto ciò che desideri avere pronto per la partita. Non è necessario eseguire l'override __init__e non è necessario . In caso di __init__crash, verrai squalificato.

get_actionriceve un singolo argomento che è a namedtuplecon i seguenti campi (in questo ordine, se si preferisce la destrutturazione):

  • room: il numero della stanza in cui ci si trova attualmente
  • treasures: l'elenco dei tesori nella stanza
  • players: l'elenco degli altri giocatori nella stanza. Ottieni solo il nome del giocatore in questo modo, quindi non sai quale bot li sta controllando o il loro inventario / resistenza.
  • inventory: l'elenco dei tesori nel tuo zaino
  • stamina: il tuo attuale livello di resistenza

Questo oggetto fornisce inoltre due proprietà di utilità:

  • carry_weight: il peso totale di tutti i tesori che stai trasportando
  • total_value: il valore totale di tutti i tesori che stai portando

Gli elenchi treasurese inventorycontengono namedtuples con questi attributi:

  • name: il nome del tesoro (per scopi cosmetici)
  • value: il valore monetario del tesoro in $.
  • weight: il peso del tesoro in kg

get_action dovrebbe restituire uno dei seguenti valori / modelli:

  • 'next'o 'previous'per passare alle stanze successive / precedenti
  • 'take', <treasure index>, <bid>(sì, come una tupla, anche se tecnicamente funzionerà anche una sequenza) per fare offerte sul tesoro all'indice indicato nella lista dei tesori della stanza. Entrambi gli argomenti dovrebbero essere numeri interi. I galleggianti verranno arrotondati per difetto.
  • 'drop', <inventory index>per rilasciare il tesoro trasportato trovato nell'indice indicato. L'indice dovrebbe (naturalmente) essere un numero intero.

Altre restrizioni

  • È possibile utilizzare solo l'istanza casuale fornita durante l'inizializzazione per pseudo casualità.
    • Qualsiasi altra cosa che potrebbe introdurre il non determinismo comportamentale non è consentita. L'intento qui è di fare in modo che i robot si comportino in modo identico quando viene dato lo stesso seme per aiutare a testare nuovi robot (e potenzialmente bug nel driver di test). Solo la radiazione cosmica dovrebbe causare qualsiasi deviazione / non determinismo.
    • Tieni presente che i codici hash sono randomizzati in Python 3, quindi hashnon è consentito l' utilizzo per qualsiasi processo decisionale. dicts vanno bene anche quando si utilizza l'ordine di iterazione per le decisioni poiché l'ordine è stato garantito coerente da Python 3.6.
  • Non è possibile aggirare il test driver usando ctypeshack o inspectstack voodoo (o qualsiasi altro metodo). Ci sono alcune cose straordinariamente spaventose che puoi fare con quei moduli. Per favore, no.
    • Ogni bot è sottoposto a sandbox ragionevolmente bene tramite copie difensive e l'immutabilità naturale di namedtuples, ma ci sono alcune lacune / exploit non percorribili.
    • Altre funzionalità da inspecte ctypespossono essere utilizzate purché non vengano utilizzate per eludere la funzionalità del controller.
    • Non è consentito alcun metodo per catturare istanze di altri robot nel tuo gioco attuale.
  • I robot dovrebbero operare da soli e non possono coordinarsi con altri robot in alcun modo per alcuno scopo. Ciò include la creazione di due robot con obiettivi diversi in modo tale che uno si sacrifichi per il successo dell'altro. Una volta che ci sono più di 10 concorrenti, in realtà non avrai la garanzia di avere i due robot nello stesso gioco e i nomi degli avventurieri non daranno alcuna indicazione sulla classe dei bot, quindi questi tipi di strategie sono comunque limitati.
  • Al momento non ci sono restrizioni rigide sui tempi di esecuzione, tuttavia mi riservo il diritto di limitarlo duramente in futuro se i tornei iniziano a richiedere troppo tempo. Sii ragionevole e cerca di continuare a girare l'elaborazione a meno di 100 ms , poiché non prevedo la necessità di limitarla al di sotto di tale soglia. (I tornei dureranno circa 2 ore se tutti i robot impiegano circa 100 ms per turno.)
  • La tua classe bot deve essere nominata in modo univoco tra tutti gli invii.
  • Potresti non ricordare nulla tra i giochi. (Tuttavia, puoi ricordare le cose tra i turni )
    • Non modificare sys.modules. Qualsiasi elemento esterno alle variabili di istanza deve essere trattato come una costante.
  • Non è possibile modificare il codice di alcun bot a livello di codice, incluso il proprio.
    • Ciò include l'eliminazione e il ripristino del codice. Questo per semplificare il debug e i tornei.
  • Qualsiasi codice che causa l'arresto anomalo del controller verrà immediatamente squalificato. Sebbene vengano rilevate la maggior parte delle eccezioni, alcune potrebbero scivolare e le segfault non sono raggiungibili. (Sì, puoi segfault in Python grazie a ctypes)

Inseriti

Per facilitare lo scraping delle risposte, indica il nome del tuo bot nella parte superiore della risposta con a #Header1e assicurati che la tua risposta includa almeno un blocco di codice (verrà utilizzato solo il primo nella tua risposta). Non è necessario includere importazioni o documenti, poiché verranno aggiunti automaticamente dal raschietto.

Sarò più propenso a valutare le risposte con spiegazioni dettagliate e comprensibili. Altri probabilmente si comporteranno allo stesso modo.

In parole povere, la tua risposta dovrebbe essere formattata in questo modo:

# Name of Bot
Optional blurb

    #imports go here

    class BotName(Adventurer):
        #implementation

Explanation of bot algorithm, credits, etc...

(reso come)

Nome del Bot

Blurb opzionale

#imports go here

class BotName(Adventurer):
    #implementation

Spiegazione di algoritmo bot, crediti, ecc ...

Esecuzione del test driver localmente

Avrai bisogno di Python 3.7+ e ti consiglio di installarlo anche tabulatetramite pip. La rimozione di questa pagina per gli invii richiede inoltre lxmle requests. Dovresti anche usare un terminale con supporto per le fughe di colore ANSI per i migliori risultati. Informazioni su come configurarlo in Windows 10 sono disponibili qui .

Aggiungi il tuo bot a un file in una sottodirectory nella stessa directory di ruins.py( ruins_botsper impostazione predefinita) e assicurati di aggiungere from __main__ import Adventurernella parte superiore del modulo. Questo viene aggiunto ai moduli quando lo scraper scarica la tua presentazione e, sebbene sia decisamente confuso, questo è il modo più semplice per assicurarsi che il tuo bot abbia correttamente accesso Adventurer.

Tutti i robot in quella directory verranno caricati dinamicamente in fase di esecuzione, quindi non sono necessarie ulteriori modifiche.

Torneo

Il vincitore finale sarà determinato in una serie di partite con un massimo di 10 robot in ogni partita. Se ci sono più di 10 invii totali, i primi 10 robot saranno determinati partizionandoli sistematicamente in gruppi di 10 fino a quando ogni bot avrà giocato (esattamente) 20 partite. I primi 10 robot verranno selezionati da questo gruppo con i punteggi di reset e giocheranno fino a quando il primo posto non avrà raggiunto un vantaggio di 50 punti sul secondo posto o fino a quando non saranno state giocate 500 partite.

Fino a quando non ci saranno almeno 10 invii, gli spazi vuoti saranno riempiti con "Drunkards" che vagano casualmente tra le rovine e prendono (e occasionalmente lasciano cadere) tesori casuali fino a quando non si esauriscono la resistenza e devono dirigersi verso l'uscita.

I tornei verranno ripetuti settimanalmente in caso di nuovi invii. Questa è una sfida KOTH aperta senza una data di fine prestabilita.

Classifica

Dalla corsa del 4 maggio 2019 alle 16:25 MDT: (2019-05-04 16: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

Aggiornamento - 15 aprile: un paio di aggiornamenti / chiarimenti

Aggiornamento - 17 aprile: vietare un paio di casi limite notevoli di azioni nefaste come la modifica del codice di altri robot.

Aggiornamento - 4 maggio: ricompensa assegnata a Sleafar per aver distrutto definitivamente Backwards. Congratulazioni!


1
È finalmente arrivato! Immagino che dovrò iniziare a creare il mio bot ora.
Belhenix,

12
Perché il limite di un bot? Ho diverse idee che si escludono a vicenda e preferirei non dover lanciare un robot perfettamente buono ogni volta che ne trovo uno nuovo.

@Mnemonic, principalmente per evitare di spostare nuovi invii utilizzando più robot quasi identici. L'altro motivo era impedire ai robot di lavorare insieme, ma ciò è esplicitamente vietato comunque. Prenderò in considerazione il permesso. Coloro che sono a favore di più invii, hanno commentato il commento di Mnemonic sopra.
Beefster,

1
@ Draco18s Se è stato pipinstallato e attivato PATH(impostazione predefinita per le nuove installazioni AFAIK), da Windows è possibile eseguire pip install modulenamedal prompt dei comandi. Per altre circostanze (che non conosco), vai su pip , cerca il modulo necessario e scegli un'opzione.
Artemis supporta Monica il

1
Immagino che questo sarà un "no", ma ci è permesso salvare informazioni durante il torneo? (ad es. quando un'offerta ha funzionato)
Artemis supporta Monica il

Risposte:


5

Contabile

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'

Il contabile è una persona molto avversa al rischio. Gli piace essere sicuro che ciò che fa è davvero l'opzione migliore in una determinata situazione. Quindi, si pone un obiettivo e raccoglie tesori solo se i suoi calcoli mostrano che questo lo porta sulla strada giusta verso quell'obiettivo. Tuttavia, è molto burocratico e non gli piace lasciare oggetti che aveva già deciso di desiderare; qualsiasi tentativo di insegnargli a farlo finora ha portato il Ragioniere a lasciare un oggetto e poi a prenderlo di nuovo immediatamente dopo.

Forse da continuare.


1
Bel lavoro che determina il valore del tesoro. Avevo sicuramente pensato di scrivere un codice "ne vale la pena" migliore, ma non ci ero ancora arrivato. Il mascalzone sta arrivando per la linea di fondo del ragioniere, però ...
Draco18s

"qualsiasi tentativo di insegnargli a farlo finora ha portato il Ragioniere a lasciare un oggetto e poi a ritirarlo immediatamente dopo.". Puoi aggirare questo mantenendo una serie di nomi di tesori lasciati cadere. Ho avuto la sensazione che i nomi dei tesori sarebbero tornati utili (anche se sono solo numerati proprio ora)
Beefster

Grazie, ma ho già capito perché è successo. Non so se lo risolverò immediatamente, poiché lo usa raramente quando ho provato a inserirlo.
ArBo,

2

Accomodator

Basato vagamente sul mio altro robot LightWeight. Laddove il robot LightWeight era semplice, questo bot è molto più complesso al fine di soddisfare le interazioni con altri robot: sia benigni che deliberatamente distruttivi.

Questo bot prima salterà su una stanza assegnata casualmente e quindi proverà a fare un'offerta per il miglior tesoro in rapporto valore / peso, se ci sono altri giocatori nella stanza, allora supponiamo che anche loro facciano offerte, quindi fanno invece un'offerta per il secondo miglior tesoro. Se quell'offerta fallisce, al prossimo turno l'offerta per il prossimo tesoro migliore.

Una volta che un'offerta è andata a buon fine, ripeti l'offerta per il migliore / secondobest fino a quando non ci sono più tesori nella stanza, quindi vai più in profondità nella rovina

Per ogni stanza entra nella rovina, ripeti l'offerta per il migliore / secondobest fino a quando non ci sono più tesori nella stanza, quindi vai più in profondità nella rovina o se rileviamo che non possiamo andare più in profondità, passa allo stato "Uscita" e inizia a rilasciare il peggio tesoro finché non possiamo garantire che possiamo uscire vivi dalla rovina.

Quando usciremo controlleremo se possiamo aggiungere un tesoro da 1 kg e comunque uscirne vivo, in tal caso tenteremo di fare un'offerta per un tesoro da 1 kg, altrimenti andremo nella stanza precedente.

Le sue prestazioni sono piuttosto variabili ... tuttavia normalmente sarà uno dei primi tre robot.

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'

Impressionante! Questo sta dominando il torneo in circa 50 round.
Beefster

2

Velocista

Simile al Sub, Sprinter va in profondità e raccoglie i migliori oggetti sulla via del ritorno.

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 va in profondità, quindi calcola un punteggio per ogni tesoro e raccoglie qualsiasi cosa al di sopra di una certa soglia. Questa soglia dipende dalla stanza in cui si trova attualmente perché costa di più prendere oggetti dalle stanze più in profondità nelle rovine.

Ho ancora 2 ottimizzazioni riguardo a "combattere per il tesoro" che sono previste per i prossimi giorni.

17.04 .: Scoundrel è diventato troppo intelligente, Sprinter ha deciso di spingerlo in una trappola inizialmente volevo uccidere qualsiasi bot che cercasse di invocare Sprinter ma il testdriver purtroppo non gestisce le eccezioni che si verificano su init. Quindi la prossima correzione per Scoundrel è abbastanza semplice ...


L'uccisione di Scoundrel è in corso di
elaborazione

2

Pianificare in anticipo

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

Ho utilizzato gli hunk di calcolo migliori / peggiori dalla risposta di Artemis Fowl , ma la logica della scelta è interamente del mio progetto e da allora è stata modificata per includere alcuni fattori aggiuntivi, come i tesori visti nelle stanze precedenti (al fine di minimizzare la raccolta di un tesoro, solo per tornare indietro, lasciarlo cadere e raccogliere qualcos'altro).

Il bot avventurarsi in profondità come pensa sia ragionevolmente sicuro farlo (questo calcolo risolve efficacemente l'immersione ad una profondità specifica, ma ha la flessibilità di gestire altri valori iniziali di resistenza), raccoglie artefatti (dando priorità a costi elevati e peso ridotto), quindi inizia a ritirarsi una volta determinato che non può più trasportare.

All'uscita raccoglierà altri tesori che vede che determina che può ancora portare in sicurezza all'uscita. Scenderà anche manufatti già conservati se il nuovo è un affare migliore e non si tradurrà in esaurimento. Se ha spazio nel suo zaino, raccoglierà il nuovo tesoro prima di far cadere quello di qualità inferiore, minimizzando il combattimento con altri robot.


Eh, quando lo descrivi è uguale al mio. Giocherò con i miei numeri ... Nota: la __init__funzione è già implementata, non è necessario sostituirla.
Artemis supporta Monica il


2

Artyventurer

Beats the Drunkards di circa $ 1000! Non riuscivo a pensare a un nome creativo, ma qui sono tutti:

import math, sys, inspect, ntpath, importlib


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


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

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

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

A volte la maggior parte del codice qui non fa mai nulla (almeno quando lo collaudo contro Drunkards), il programma cerca (solo di) trovare la mossa migliore, inclusa l'elaborazione per le situazioni in cui cerca di non inserirsi. Alcuni di essi non possono mai funzionare, è solo lì, così posso armeggiare con i numeri, che probabilmente possono ancora essere migliorati.

Spiegazione

  • if state.inventory ... worsti = None
    Trova l'articolo "peggiore" nell'inventario, ovvero l'articolo che ha il rapporto più basso tra valore e peso. Memorizza worsti, che ne contiene l'indice, il suo valore e il suo peso, come tupla, o Nonese non ci sono articoli nell'inventario.

  • if self.drop ... return 'drop', worsti[0]
    Se gli ho detto di abbandonare questo turno nell'ultima curva (vedi sotto), e può, rilasciare l'elemento "peggiore" come calcolato sopra.

  • extra = ... * state.room
    Calcola quanta resistenza avrebbe avuta se gli avessi detto di tornare subito indietro.

  • if extra > CONTINUE_IN:\ return 'next'
    Se è superiore a CONTINUE_IN, ritorna 'next'.

  • if extra < 0 and worsti:\ return 'drop', worsti[0]
    Se è inferiore 0, elimina l'elemento peggiore.

  • if extra < state.room:\ return 'previous'
    Se è inferiore al numero della stanza (non è possibile trasportare altri tesori) torna indietro.

  • if state.treasures: ... bestt = None
    Elabora il miglior tesoro da prendere, simile al peggior oggetto nell'inventario sopra. Conservalo in bestt.

  • if extra > 0 and bestt: ... return 'take', bestt[0], bid
    Con i numeri attuali, questo viene eseguito ogni volta che siamo arrivati ​​così lontano e ci sono tesori disponibili. Se è sicuro prendere il tesoro "migliore", lo fa. La sua offerta è il minimo, o uno in più se qualcuno è presente.

  • if bestt and worsti: ... return 'take', bestt[0], bid
    Con i numeri correnti, questo blocco di codice non verrà mai eseguito, poiché il blocco di codice precedente presenta una condizione più ampia. Questo viene eseguito se siamo arrivati ​​così lontano e ci sono entrambi tesori nel mio inventario e nella stanza. Se il tesoro "migliore" nella stanza è più prezioso del tesoro "peggiore" nel mio inventario, e sarebbe sicuro scambiarli nei due turni successivi, lo fa.

  • return 'previous'
    Se non accade nulla di tutto ciò, torna indietro.

Aggiornamento 16/04/19:

Misure anti-furfanti. Questa diventerà una guerra di offerte :(

Ulteriore aggiornamento 16/04/19:

Ripristinato in precedenza, invece cambia casualmente ogni altro elemento quando trova il migliore, ad es. [1, 2, 3, 4, 5, 6] → [2, 1, 3, 4, 6, 5]. Dovrebbe essere più difficile da copiare :).

Aggiornamento 17/04/19:

Ripristinato in precedenza, invece cancella il proprio codice sorgente . Fa ciò in __init__cui sarà sempre prima Scoundrel.enter_ruins, e così impedirà a Scoundrel di caricarlo. Sostituisce il suo codice quando get_actionviene chiamato per la prima volta, in modo che sia pronto per la prossima volta. RISOLTO, Scoundrel ora muore all'arrivo.

Ulteriore aggiornamento 17/04/19:

Ripristinato in precedenza, invece sostituisce la sua sys.modulesvoce con il modulo matematico, in modo che quando Scoundrel tenta di caricarlo, carica invece il modulo matematico. :)
Inoltre, mi sono appena reso conto che la resistenza alla mossa era 10 + peso / 5 , quindi ho cercato di risolverlo.

Ulteriore aggiornamento 17/04/19:

Ora include l'aglio da entrambi gli aggiornamenti precedenti.

Aggiornamento 18/04/19:

Giocherellando con numeri e calcoli, ora guadagna $ 2000 - $ 3000.

Ulteriore aggiornamento 18/04/19:

Rimosso il file-wipe aglio come è stato vietato, aggiunto nuovo aglio che si assicura che 'draco'non sia responsabile del suo funzionamento, se lo fa ritorna previousal suo primo turno. I risultati hanno fatto un tuffo misterioso a $ 1200- $ 1800, che sto esaminando.


sembra molto efficace contro gli ubriaconi, mi piacerebbe vedere come va quando altri robot partecipano al raid :)
Moogie

@Moogie Beats the Diver di circa $ 100 quando sono presenti anche 8 ubriaconi.
Artemis supporta Monica il

2

The Scoundrel

import math, importlib

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

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

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

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

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

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

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

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

Lo Scoundrel lavora principalmente per interferire con altri concorrenti. Attualmente interferisce con Sprinter, Artyventurer e Accountant (questo elenco aumenterà nel tempo a condizione che rientri nell'interesse dello Scoundrel). Lo fa imitando gli altri robot e quindi o fuori offerta, sottotaglio o combattendo in altro modo sulle reliquie. Come tale, è improbabile che questa voce dominerà mai la classifica e opererà invece come una forza viziata. L'attuale revisione al momento di questo post lo mette al 2 ° posto con un punteggio medio di circa 7.

Scoundrel ostacola i tentativi di altri bot di modificarsi per difendersi dallo Scoundrel eseguendo direttamente il codice degli altri concorrenti come una copia clone indistinguibile. I problemi con le importazioni risultanti in duplicati dei partecipanti sono stati risolti creando i cloni tramite Reflection (le guerre di modifica che comportano dettagli precisi sulla determinazione matematica non sono desiderabili dal punto di vista dello scambio di stack, ma porterebbero allo stesso risultato). Anche le sfide di KOTH hanno permesso questo.

Scoundrel sostituisce i Teamsters al fine di mantenere i Teamsters per il gusto di essere stati interessanti. Dopo questa modifica, i Teamsters non dovrebbero più essere eliminati dal controller.

Aggiornamento del 17/04/2019: ulteriori contromisure.

The Teamsters (reso illegale)

Ma sentiti libero di correre localmente dove non ci sono più di 8 altri concorrenti!

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

Questa voce (sebbene ora esplicitamente non valida) è, in effetti, due robot, e il controller li cancellerà felicemente entrambi e li aggiungerà all'elenco dei concorrenti (perché evviva Python?)

Fase 1:

  • TeamsterA scende al livello 25 (ish) 1 e raccoglie e rilascia ripetutamente il tesoro più leggero che riesce a trovare. Questo costa 1 enorme resistenza al turno fino alla seconda fase.
  • TeamsterB scende al livello 55 e raccoglie tutti gli oggetti di valore che giacciono intorno, quindi torna al livello 25 (ish). 2 Quindi inizia la fase 2.

1. Se non c'è un tesoro che pesa meno di 3 su un piano, si sposta verso il basso
2. Poiché è praticamente garantito di essere l' ultimo avventuriero che ritorna in superficie, tutto quello che deve fare è trovare qualcuno.

Fase 2:

  • TeamsterB si svuota le tasche prima di strisciare per morire per la stanchezza. Sapevamo che potevi farlo.
  • TeamsterA pensa "quelli sono dei ciondoli luccicanti, buon amico o amico!" e carica i tesori molto più preziosi dell'altra robaccia nella stanza prima di procedere verso l'uscita, tasche piene d'oro.

Il nome dei tesori in realtà è stato utile per aiutare la logica a non caricarsi sulla spazzatura del piano 25 e andarsene presto poiché non c'era modo di comunicare tra i due robot (e TeamsterA si sarebbe sempre trovato in una stanza con qualcun altro prima TeamsterB era tornato).

La prossima conclusione logica: creare un esercito

In teoria, questo potrebbe essere usato per sondare le profondità e acquisire tesori dalla profondità della Stanza 98, tuttavia, poiché ciò richiederebbe più di 2 robot, la logica che comprende tali robot diventerebbe sempre più complessa, e poiché sono certo che questo è una richiesta illegale per violazione di una regola non scritta, quindi non mi preoccuperò.

AAttende efficacemente a 30, Baspetta a 50 ... si ntuffa a 98, prende un tesoro, si sposta a 97, lo rilascia (e poi muore), lo n-1raccoglie e si sposta a 96 ... lo Crilascia (muore), lo Braccoglie su e si sposta su 30, lo rilascia (muore), lo Araccoglie e ritorna all'uscita.

Stimo che ciò richiederebbe 11 robot.

Tuttavia, non vale la pena farlo a meno che non sia possibile recuperare circa 4 tesori da quella profondità per competere con voci come PlanAhead o Artyventure, a causa del ridimensionamento tra i costi di resistenza da spostare e il valore medio dei tesori.

Risultati del campione

Raramente segna meno di $ 4000, occasionalmente cresta di $ 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
Penso che quando ci sarebbe stato solo un bot per persona non c'era bisogno di una regola così esplicita. Ma la regola del targeting di un particolare bot per motivi neutrali non è in realtà la stessa di dissuadere più robot che fanno squadra insieme. Pertanto è necessaria una decisione esplicita dall'OP.
Moogie,

Sì, questo sarà un no da parte mia, amico. Questo è il tipo di cosa che avevo in mente con i robot che lavoravano insieme.
Beefster,

1
@Beefster Questo è quello che ho pensato. Mi sono divertito a farlo però. Stasera gestirò la modifica per impedire l'inclusione.
Draco18s

Prenderò in considerazione la possibilità di concederlo una volta che ci saranno più di 11 concorrenti poiché la sua efficacia aumenterà comunque. Soprattutto perché non voglio creare codice per invii autoban.
Beefster,

Se stai già raschiando solo il primo blocco di codice, tutto quello che devo fare è modificare in un altro bot in alto.
Draco18s

2

Indietro

Perché funziona al contrario

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'

Perché si chiama Backwards?

Perché ho preso The Accountant e ho cercato di far funzionare la sua logica in modo che si immergesse in profondità, quindi raccoglievo il bottino preferito all'uscita (all'indietro del Accountant).

Alla fine raccoglie ancora gran parte dei suoi premi sulla strada (raccogliendoli prima che facciano i tradizionali cercatori di raccolta, operando a ritroso verso tutti gli altri), ma è molto più selettivo su quali prende, anche se ancora raccoglie le cose sulla via del ritorno.

Il risultato finale è che la resistenza viene mantenuta sulla strada mentre si privilegiano ancora tesori di alto valore, sfruttando quindi una svolta profonda e facili prelievi sulla via del ritorno. È noto che Backwards raccoglie tesori fino alla Room 41 (e durante lo sviluppo entrerebbe, quindi lascerebbe immediatamente la Room 42).


2

Cacciatore di taglie

Il metodo semplice è il migliore. Prendi tesori preziosi e leggeri mentre vai più in profondità possibile. Prendi tesori meno preziosi sulla via del ritorno.

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'

Sembra che tu stia ottenendo la taglia. Non solo funziona meglio di Backwards, ma provoca anche il tank di Backwards. Molto bene.
Beefster

1

LightWeight

Un semplice bot che funziona ancora abbastanza bene.

Dopo essersi avventurato nelle rovine (attualmente 21 stanze), afferrerà il miglior tesoro nella stanza che è solo 1 kg (da cui il nome del robot) ed è più prezioso del tesoro meno prezioso nell'inventario. Se l'inventario è pieno, rilascia il tesoro meno prezioso. Se non viene selezionata alcuna altra azione, lo spostamento nelle rovine. Se siamo al limite della nostra resistenza per essere in grado di uscire vivi, dirigetevi verso l'uscita

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'

Consiglierei di inserire dumpingil enter_ruinsmetodo. Questo in realtà lo ricorderà tra i giochi e non funzionerà sul gioco 2. Tecnicamente non è permesso, ma ho aggiunto la regola proprio ora (l'ho dimenticato prima ma era il mio intento), quindi taglierò un po 'di gioco. : P
Beefster,

@Beefster Ho rimosso la bandiera dello stato di dumping, non è necessario poiché il bot scarica solo un tesoro ora. Era solito scaricare metà del suo tesoro. Quindi dovrebbe essere compatibile con la nuova regola.
Moogie,

1

Memorizer

Posso inviare robot al mio KotH, giusto?

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'

Questo robot si tuffa nella stanza 30 e ricorda tutti i tesori che ha visto. A quel punto, inizia il suo viaggio di ritorno all'ingresso, cercando di prendere buoni tesori che ricordava di aver visto nelle stanze precedenti.

Speravo potesse fare di meglio. I possibili miglioramenti possono derivare da una migliore pianificazione ed essere più dinamici su quale stanza smette di immergersi ed essere più disposti a esplorare le opzioni di backup.

Aggiornamento: ora raccoglie tesori da 1 kg del valore di $ 60 o più durante il viaggio.


Immagino che tutto quel buon tesoro sia appena passato dal punto in cui il robot ritorna lì ... Forse puoi provare una combo dove prenderà la roba davvero buona sulla sua strada, tenendo presente il tesoro mediocre che potrebbe raccogliere sulla via del ritorno?
ArBo,

Potrebbe andare troppo lontano
Beefster,

Cordiali saluti, sembra che a volte calcoli male se ha abbastanza resistenza per tornare indietro: [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
Larkeith

1

ponderer

Penso che sia abbastanza simile a Memorizer in quanto utilizza la conoscenza delle stanze visitate per scegliere quali stanze e tesori collezionare sulla via del ritorno verso l'uscita, tuttavia è stata derivata in modo indipendente.

Questo robot scatta fino a quando una stanza profonda e casuale prende una registrazione dei tesori trovati lungo la strada. Una volta nella stanza target, rifletterà sulla selezione ideale di tesori per tornare all'uscita. Ogni turno rifletterà di nuovo per determinare la migliore selezione di tesori da prendere.

Attualmente esiste un semplice algoritmo (potenza inversa del numero della stanza) che fornisce il numero presunto di tesori presi (o saranno stati presi quando visitati da questo bot) per ogni stanza e quindi questi tesori vengono ignorati quando si medita su quali tesori / stanze da cui prendere. Ho idee per altri algoritmi più avanzati per modellare quali tesori rimangono. Ma dovrò vedere se ne vale la pena.

import math

class Ponderer(Adventurer):

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

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

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

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

    roomValueCache = valueCache.get(roomId)

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

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

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

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

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

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

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

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

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

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

      roomValueCache[carry_weight] = value
    return value

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

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

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

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

    self.rooms[state.room] = room

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

    self.exiting = True

    action = []
    valueCache = {}

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

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

    return action[0][0]

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

L'accaparratore rimane in una stanza fino a quando non ha preso tutti i tesori nella stanza (o calcola che non ha abbastanza resistenza per continuare a prendere / andare avanti). Quando tutti i tesori sono spariti, se il robot può andare avanti in modo sicuro, continuerà il processo di acquisizione di tutti i tesori.


Questo muore ogni gioco riempiendo troppo il suo zaino.
Beefster

come me in Minecraft (͡ ° ͜ʖ ͡ °) Questo bot saccheggerà, approfondirà e poi troverà prezioso bottino. Quindi lascerà cadere quello che pensava fosse un bel bottino in precedenza. Ecco perché il lavoro di strategia di " Backwards, Sprinter" e Memorizer"; perché sanno quali sono i valori relativi di ogni tesoro che vedono.
V. Courtois,

0

tuffatore

(Impossibile eseguire il test al momento, quindi fammi sapere se questo è rotto.)

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'

Il miglior tesoro è più in profondità tra le rovine, quindi tuffati in profondità, quindi prendi ciò che possiamo uscire.


Non ho molta esperienza con Python, ma dove è divingdefinito?
Incarnazione dell'ignoranza il

1
@EmbodimentofIgnorance In enter_ruins (), che viene chiamato prima che il gioco venga eseguito e le azioni vengano eseguite.

Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.Non sono sicuro di ciò che hai fatto di sbagliato, ma questo significa 'AFAIK' ritorno non valido.
Artemis supporta Monica il

@ArtemisFowl ha fatto un'offerta troppo bassa per il tesoro. Costa il peso del tesoro raccoglierlo.
Beefster

@Beefster Oh yup.
Artemis supporta Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.