Aventuriers dans les ruines

27

Pilote d'essaiDiscussion sur le défiSoumettre un aventurier

Salle au trésor ( Source de l'image )

Plusieurs aventuriers rivaux attaquent les ruines pour trouver des trésors, mais ils ne peuvent en transporter que beaucoup à la fois et ont leurs limites d'endurance. Ils veulent obtenir le trésor le plus précieux et sortir avant de devenir trop fatigués pour continuer. Ils essaient de devenir aussi riches que possible de leurs manigances de pillage.

Gameplay

Chaque aventurier commence dans la première salle du donjon avec 1000 points d'endurance et 50 kg d'espace dans son sac à dos.

Le jeu fonctionne au tour par tour, tous les joueurs résolvant leurs tours simultanément. À chaque tour, vous pouvez effectuer l'une des actions suivantes:

  • Passez à la pièce suivante.
  • Passez à la pièce précédente.
  • Offrez de l'endurance pour prendre un trésor.
  • Lâchez un trésor.

Se déplacer entre les pièces nécessite 10 points d'endurance, plus 1 pour chaque 5 kg actuellement dans votre sac à dos, arrondi. Par exemple, un aventurier portant 3 kg de trésors a besoin de 11 points d'endurance pour se déplacer et un porteur de 47 kg a besoin de 20 points d'endurance pour se déplacer.

Lâcher un trésor nécessite 1 endurance quel que soit le trésor lâché.

À la sortie des ruines, le joueur ne prendra plus de tours.

Si un joueur ne peut effectuer aucune de ces actions (en raison du manque d'endurance ou de l'absence de trésors), son aventurier meurt d'épuisement, déversant son trésor détenu dans la pièce actuellement occupée. De même, si un joueur tente de faire une action invalide, son aventurier sera tué par un piège à la place, entraînant le même déversement de trésors.

Enchère

L'enchère minimum pour un trésor est de 1 endurance par 1 kg que le trésor pèse. Vous pouvez également offrir des points d'endurance supplémentaires pour être plus susceptible d'obtenir le trésor. L'endurance qui a été mise est consommée quel que soit le résultat.

Dans le cas où plusieurs joueurs ont misé pour prendre le même trésor, le joueur qui a misé le plus haut obtient le trésor. Si plusieurs joueurs ont fait l'enchère la plus élevée, aucun d'eux ne recevra le trésor.

Condition de victoire

Le joueur avec la plus grande valeur totale de trésors est le gagnant. Dans le cas peu probable d'une égalité, les égalités vont au plus petit poids total, puis au plus petit nombre de trésors, puis à la valeur du trésor le plus précieux, au deuxième plus précieux, au troisième ... jusqu'à ce que l'égalité soit brisée. Dans le cas presque impossible où il y a encore égalité à ce stade, le pilote d'essai dit "vissez" et le vainqueur est ainsi déterminé arbitrairement.

Dans le cadre du tournoi, les joueurs seront classés avec la première place recevant 10 points, la deuxième place avec 9 points, la troisième place avec 8 points, etc ..., avec les joueurs morts et les aventuriers sans trésors marquant 0 point.

À propos des ruines

  • Chaque pièce contient initialement entre r3+3etr2+5trésors. (Oùrest le numéro de la chambre)
  • Il y a arbitrairement de nombreuses pièces, limitées uniquement par l'endurance des aventuriers et leur volonté d'explorer.
  • Chaque trésor aura une valeur monétaire (en $) et un poids (en kg).
    • Les trésors ont tendance à être plus précieux et abondants lorsque vous vous enfoncez plus profondément dans les ruines.
  • Les formules spécifiques pour générer des trésors sont les suivantes: (en utilisant la notation xdy pour les lancers de dés)
    • Le poids est généré en premier en utilisant la formule 2d62 (minimum 1)
    • La valeur du trésor est alors générée via 1d[10w]+2d[5r+10] (où r est le numéro de la pièce et w est le poids)

Informations visibles pour les joueurs

À chaque tour, les joueurs obtiennent les informations suivantes:

  • Le numéro de la pièce dans laquelle ils se trouvent actuellement. Il est indexé sur 1, donc conceptuellement, la sortie est à la "pièce 0"
  • Une liste des trésors actuellement dans la salle
  • Une liste d'autres joueurs qui sont également actuellement dans la salle.
  • Votre inventaire actuel de trésors
  • Votre niveau d'endurance actuel

Codage

Le pilote de test peut être trouvé ici .

Vous devez implémenter une sous-classe de cette 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

Il vous suffit de remplacer la get_actionméthode. enter_ruinsest exécuté avant le début d'un jeu et est votre chance de préparer tout ce que vous souhaitez préparer pour le jeu. Vous n'avez pas besoin de passer outre __init__et vous ne devriez vraiment pas . En cas de __init__plantage, vous serez disqualifié.

get_actionreçoit un seul argument qui est un namedtupleavec les champs suivants (dans cet ordre, si vous préférez la déstructuration):

  • room: le numéro de la pièce dans laquelle vous vous trouvez actuellement
  • treasures: la liste des trésors de la salle
  • players: la liste des autres joueurs dans la salle. Vous n'obtenez que le nom du joueur de cette façon, donc vous ne savez pas quel bot les contrôle ou leur inventaire / endurance.
  • inventory: la liste des trésors dans votre sac à dos
  • stamina: votre niveau d'endurance actuel

Cet objet fournit en outre deux propriétés utilitaires:

  • carry_weight: le poids total de tous les trésors que vous portez
  • total_value: la valeur totale de tous les trésors que vous portez

Les listes treasureset inventorycontiennent des namedtuples avec ces attributs:

  • name: le nom du trésor (à des fins esthétiques)
  • value: la valeur monétaire du trésor en $.
  • weight: le poids du trésor en kg

get_action doit renvoyer l'une des valeurs / modèles suivants:

  • 'next'ou 'previous'pour passer aux salles suivantes / précédentes
  • 'take', <treasure index>, <bid>(oui, en tant que tuple, bien que n'importe quelle séquence fonctionne également techniquement) pour miser sur le trésor à l'index donné dans la liste des trésors de la salle. Les deux arguments doivent être des entiers. Les flotteurs seront arrondis.
  • 'drop', <inventory index>pour laisser tomber le trésor transporté trouvé à l'indice donné. L'index doit (naturellement) être un entier.

Autres restrictions

  • Vous ne pouvez utiliser l'instance aléatoire qui vous a été fournie lors de l'initialisation pour pseudo-aléatoire.
    • Tout ce qui pourrait introduire un non-déterminisme comportemental n'est pas autorisé. L'intention ici est de faire en sorte que les bots se comportent de manière identique lorsqu'ils reçoivent la même graine pour aider à tester de nouveaux bots (et potentiellement des bogues dans le pilote de test). Seul le rayonnement cosmique devrait provoquer une déviation / non-déterminisme.
    • Gardez à l'esprit que les codes de hachage sont aléatoires en Python 3, donc l'utilisation hashpour toute prise de décision n'est pas autorisée. dictles s sont corrects même lors de l'utilisation de l'ordre d'itération pour les décisions car l'ordre est garanti cohérent depuis Python 3.6.
  • Vous ne pouvez pas contourner le pilote de test à l'aide de ctypeshacks ou de inspectstack vaudou (ou toute autre méthode). Il y a des choses effrayantes que vous pouvez faire avec ces modules. S'il vous plait, ne le faites pas.
    • Chaque bot est raisonnablement bien mis en bac à sable via des copies défensives et l'immuabilité naturelle de namedtuples, mais il y a des failles / exploits non détectables.
    • D'autres fonctionnalités de inspectet ctypespeuvent être utilisées tant qu'aucune n'est utilisée pour contourner la fonctionnalité du contrôleur.
    • Toute méthode pour récupérer des instances des autres robots dans votre jeu actuel n'est pas autorisée.
  • Les bots doivent fonctionner en solo et ne peuvent en aucun cas se coordonner avec d'autres bots à quelque fin que ce soit. Cela comprend la création de deux bots avec des objectifs différents tels que l'un se sacrifie pour le succès de l'autre. Une fois qu'il y a plus de 10 concurrents, vous ne serez pas réellement assuré d'avoir les deux bots dans le même jeu et les noms des aventuriers ne donnent aucune indication sur la classe de bot, donc ces types de stratégies sont de toute façon limités.
  • Il n'y a actuellement aucune restriction stricte sur le temps d'exécution, mais je me réserve le droit de le limiter à l'avenir si les tournois commencent trop longtemps. Soyez raisonnable et essayez de garder le traitement des virages sous 100 ms , car je ne prévois pas avoir besoin de le limiter en dessous de ce seuil. (Les tournois se dérouleront dans environ 2 heures si tous les robots prennent environ 100 ms par tour.)
  • Votre classe de bot doit être nommée de manière unique parmi toutes les soumissions.
  • Vous ne vous souvenez peut-être de rien entre les jeux. (Cependant, vous pouvez vous souvenir des choses entre les tours )
    • Ne modifiez pas sys.modules. Tout ce qui est en dehors des variables d'instance doit être traité comme une constante.
  • Vous ne pouvez pas modifier le code d'un bot par programme, y compris le vôtre.
    • Cela comprend la suppression et la restauration de votre code. C'est pour simplifier le débogage et les tournois.
  • Tout code provoquant le plantage du contrôleur sera immédiatement disqualifié. Bien que la plupart des exceptions soient interceptées, certaines peuvent passer à travers et les erreurs de segmentation sont inaccessibles. (Oui, vous pouvez vous séparer en Python grâce à ctypes)

Soumissions

Afin de faciliter le grattage des réponses, indiquez le nom de votre bot en haut de la réponse par un #Header1et assurez-vous que votre réponse comprend au moins un bloc de code (seul le premier de votre réponse sera utilisé). Vous n'avez pas besoin d'inclure d'importations ou de docstrings, car ils seront ajoutés automatiquement par le grattoir.

Je serai plus enclin à voter pour des réponses avec des explications détaillées et compréhensibles. D'autres se comporteront probablement de la même manière.

En gros, votre réponse doit être formatée comme ceci:

# Name of Bot
Optional blurb

    #imports go here

    class BotName(Adventurer):
        #implementation

Explanation of bot algorithm, credits, etc...

(rendu comme)

Nom du Bot

Texte de présentation en option

#imports go here

class BotName(Adventurer):
    #implementation

Explication de l'algorithme du bot, des crédits, etc ...

Exécution locale du pilote de test

Vous aurez besoin de Python 3.7+ et je vous recommande également d'installer tabulatevia pip. Le grattage de cette page pour les soumissions nécessite en outre lxmlet requests. Vous devez également utiliser un terminal prenant en charge les sorties de couleur ANSI pour de meilleurs résultats. Des informations sur la façon de configurer cela dans Windows 10 peuvent être trouvées ici .

Ajoutez votre bot à un fichier dans un sous-répertoire du même répertoire que ruins.py( ruins_botspar défaut) et assurez-vous de l'ajouter from __main__ import Adventureren haut du module. Ceci est ajouté aux modules lorsque le scraper télécharge votre soumission, et bien qu'il soit définitivement hacky, c'est le moyen le plus simple de vous assurer que votre bot a correctement accès Adventurer.

Tous les bots de ce répertoire seront chargés dynamiquement lors de l'exécution, donc aucune autre modification n'est nécessaire.

Tournoi

Le vainqueur final sera déterminé dans une série de jeux avec jusqu'à 10 bots dans chaque jeu. S'il y a plus de 10 soumissions au total, les 10 meilleurs bots seront déterminés en les partitionnant systématiquement en groupes de 10 jusqu'à ce que chaque bot ait joué (exactement) 20 parties. Les 10 meilleurs bots seront sélectionnés dans ce groupe avec des scores réinitialisés et joueront des jeux jusqu'à ce que le bot de première place ait atteint une avance de 50 points sur le bot de deuxième place ou jusqu'à ce que 500 jeux aient été joués.

Jusqu'à ce qu'il y ait au moins 10 soumissions, les emplacements vides seront remplis de "ivrognes" qui errent au hasard à travers les ruines et prennent (et parfois déposent) des trésors aléatoires jusqu'à ce qu'ils soient à court d'endurance et doivent se précipiter vers la sortie.

Les tournois seront renouvelés chaque semaine s'il y a de nouvelles soumissions. Il s'agit d'un défi ouvert KOTH sans date de fin définie.

Classement

À partir du 4 mai 2019 à 16 h 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

Mise à jour - 15 avril: quelques mises à jour / clarifications de règles

Mise à jour - 17 avril: interdiction de quelques cas marginaux notables d'actions néfastes telles que la modification du code d'autres bots.

Mise à jour - 4 mai: prime accordée à Sleafar pour avoir absolument détruit Backwards. Toutes nos félicitations!

Beefster
la source
1
C'est enfin là! Je suppose que je vais devoir commencer à faire mon bot maintenant.
Belhenix
12
Pourquoi la limite à un bot? J'ai plusieurs idées mutuellement exclusives, et je préfère ne pas avoir à jeter un bon bot chaque fois que j'en trouve une nouvelle.
@Mnemonic, c'est principalement pour empêcher le déplacement de nouvelles soumissions en utilisant plusieurs robots presque identiques. L'autre raison était d'empêcher les robots de travailler ensemble, mais cela est explicitement interdit de toute façon. J'envisagerai de l'autoriser. Ceux qui sont en faveur de permettre plusieurs soumissions, votent le commentaire de Mnemonic ci-dessus.
Beefster
1
@ Draco18s Si vous avez pipinstallé et activé PATH(ce qui est par défaut pour les installations plus récentes AFAIK), vous pouvez exécuter pip install modulenameà partir de Windows dans l'invite de commande. Pour d'autres circonstances (que je ne connais pas), allez dans pip , recherchez le module nécessaire et choisissez une option.
Artemis prend en charge Monica le
1
Je suppose que ce sera un «non», mais sommes-nous autorisés à enregistrer des informations via le tournoi? (par exemple lorsqu'une offre a fonctionné)
Artemis soutient Monica le

Réponses:

5

Comptable

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'

Le comptable est une personne très opposée au risque. Il aime être sûr que ce qu'il fait est vraiment la meilleure option dans la situation donnée. Ainsi, il se fixe un objectif et ne recueille un trésor que si ses calculs montrent que cela le place sur la bonne voie vers cet objectif. Cependant, il est très bureaucratique et n'aime pas laisser tomber des articles qu'il avait déjà décidé qu'il voulait; jusqu'à présent, toute tentative de lui apprendre à le faire a conduit le comptable à déposer un article, puis à le reprendre immédiatement après.

Peut-être à poursuivre.

ArBo
la source
1
Beau travail déterminant la valeur d'un trésor. J'avais certainement en tête de rédiger un meilleur code "ça vaut le coup", mais je n'y étais pas encore arrivé. Le scélérat arrive pour la ligne de fond du comptable, cependant ...
Draco18s
"toute tentative de lui apprendre à le faire a jusqu'à présent conduit le comptable à déposer un article, puis à le reprendre immédiatement après.". Vous pouvez contourner cela en conservant un ensemble de noms de trésors abandonnés. J'avais le sentiment que les noms de trésors seraient utiles (même s'ils sont simplement numérotés en ce moment)
Beefster
Merci, mais j'ai déjà compris pourquoi cela s'est produit. Je ne sais pas si je vais le réparer immédiatement, car il l'utilise rarement lorsque je l'ai testé.
ArBo
2

Accomodateur

Basé vaguement sur mon autre bot LightWeight. Là où le bot LightWeight était simple, ce bot est beaucoup plus complexe afin de s'adapter aux interactions avec d'autres bots: à la fois bénins et délibérément perturbateurs.

Ce bot sprintera d'abord vers une pièce assignée au hasard, puis tentera d'enchérir pour le meilleur trésor de rapport valeur / poids, s'il y a d'autres joueurs dans la salle, supposez qu'ils enchériront également à la place pour le deuxième meilleur trésor. Si cette enchère échoue, alors au tour suivant, enchérissez pour le prochain meilleur trésor.

Une fois que l'enchère a réussi, répétez l'enchère pour le meilleur / second meilleur jusqu'à ce qu'il n'y ait plus de trésors dans la salle, puis allez plus loin dans la ruine

Pour chaque pièce, entrez dans la ruine, répétez les enchères pour le meilleur / deuxième meilleur jusqu'à ce qu'il n'y ait plus de trésors dans la pièce, puis approfondissez la ruine ou si nous détectons que nous ne pouvons pas aller plus loin, passez à l'état `` Quitter '' et commencez à laisser tomber le pire trésor jusqu'à ce que nous pouvons garantir que nous pouvons sortir de la ruine en vie.

Lorsque nous sortirons, nous vérifierons si nous pouvons ajouter un trésor de 1 kg et le faire vivre, si c'est le cas, nous tenterons d'enchérir sur un trésor de 1 kg, sinon nous nous dirigerons vers la pièce précédente.

Ses performances sont assez variables ... mais sera normalement l'un des trois premiers bots.

import math

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

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

        worstMyTreasure = None
        worstMyTreasureId = -1

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

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

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

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

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

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

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

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

        chosenTreasure = bestRoomTreasure
        chosenTreasureId = bestRoomTreasureId

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

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

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

        # no treasures are better than what we already have so go to next/previous room
        self.bidValue = -1
        self.bidWeight = -1
        if (self.exiting == False):
          return 'next'
        else:
          return 'previous'
Moogie
la source
Impressionnant! Celui-ci domine le tournoi en environ 50 rounds.
Beefster
2

Sprinter

Semblable au plongeur, Sprinter va en profondeur et ramasse les meilleurs articles sur le chemin du retour.

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 en profondeur puis calcule un score pour chaque trésor et ramasse tout ce qui dépasse un certain seuil. Ce seuil dépend de la pièce dans laquelle il se trouve actuellement car cela coûte plus cher de prendre des objets dans des pièces plus profondes dans les ruines.

J'ai encore 2 optimisations concernant "la lutte pour le trésor" qui sont prévues pour les prochains jours.

17.04 .: Scoundrel est devenu trop intelligent, Sprinter a décidé de le pousser dans un piège au départ, je voulais tuer tout bot qui essayait d'invoquer Sprinter mais le testeur ne gère malheureusement pas les exceptions qui se produisent lors de l'init. Le prochain correctif pour Scoundrel est donc assez facile ...

AKroell
la source
Le meurtre de scélérats est en cours ...
AKroell
2

Planifier à l'avance

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

J'ai utilisé les meilleurs / les pires calculs de la réponse d' Artemis Fowl , mais la logique de choix est entièrement de ma propre conception et a depuis été modifiée pour inclure quelques facteurs supplémentaires, tels que les trésors vus dans les salles précédentes (afin de minimiser la collecte d'un trésor, seulement pour revenir en arrière, le laisser tomber et ramasser autre chose).

Le robot s'aventure aussi profondément qu'il le juge raisonnablement sûr (ce calcul équivaut à plonger à une profondeur spécifique, mais a la flexibilité de gérer d'autres valeurs d'endurance initiales), recueille des artefacts (priorisant les coûts élevés et le faible poids), puis commence à battre en retraite une fois qu'il détermine qu'il ne peut plus porter.

À la sortie, il ramassera tous les trésors supplémentaires qu'il verra déterminer qu'il peut toujours transporter en toute sécurité jusqu'à la sortie. Il laissera même tomber les artefacts déjà détenus si le nouveau est meilleur marché et n'entraînera pas l'épuisement. S'il a de la place dans son sac à dos, il ramassera le nouveau trésor avant de laisser tomber celui de qualité inférieure, minimisant les combats avec d'autres robots.

Draco18s
la source
Huh, quand tu le décris, c'est pareil que le mien. Je vais jouer avec mes chiffres ... Remarque: la __init__fonction est déjà implémentée, vous n'avez pas besoin de la remplacer.
Artemis soutient Monica le
Continuons cette discussion dans le chat .
Draco18s
2

Artyventurer

Beats the Drunkards d'environ 1000 $! Impossible de penser à un nom créatif, mais vous êtes tous ici:

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'

Parfois, la plupart du code ici ne fait jamais rien (du moins lorsque je le teste contre Drunkards), le programme essaie juste de trouver le meilleur coup, y compris le traitement des situations dans lesquelles il essaie de ne pas se mettre. Certains ne peuvent jamais fonctionner, c'est juste là, donc je peux jouer avec les chiffres, qui peuvent probablement encore être améliorés.

Explication

  • if state.inventory ... worsti = None
    Trouvez le «pire» article de l'inventaire, c'est-à-dire celui qui présente le rapport valeur / poids le plus faible. Il stocke worsti, qui contient son index, sa valeur et son poids, en tant que tuple, ou Nones'il n'y a pas d'articles dans l'inventaire.

  • if self.drop ... return 'drop', worsti[0]
    Si je lui ai dit d'abandonner ce tour au dernier tour (voir ci-dessous), et qu'il le peut, laissez tomber le «pire» élément calculé ci-dessus.

  • extra = ... * state.room
    Calculez combien d'endurance il lui resterait si je lui disais de retourner directement maintenant.

  • if extra > CONTINUE_IN:\ return 'next'
    Si c'est plus que CONTINUE_IN, revenez 'next'.

  • if extra < 0 and worsti:\ return 'drop', worsti[0]
    S'il est inférieur à 0, supprimez le pire élément.

  • if extra < state.room:\ return 'previous'
    S'il est inférieur au nombre de chambres (ne peut plus transporter de trésor), revenez en arrière.

  • if state.treasures: ... bestt = None
    Trouvez le meilleur trésor à prendre, semblable au pire article de l'inventaire ci-dessus. Conservez-le bestt.

  • if extra > 0 and bestt: ... return 'take', bestt[0], bid
    Avec les chiffres actuels, cela s'exécute chaque fois que nous sommes arrivés jusqu'ici et qu'il y a des trésors disponibles. S'il est sûr de prendre le «meilleur» trésor, il le fait. Son offre est le minimum, ou un de plus si quelqu'un est présent.

  • if bestt and worsti: ... return 'take', bestt[0], bid
    Avec les nombres actuels, ce bloc de code ne s'exécutera jamais, car le bloc de code précédent a une condition plus large. Cela s'exécute si nous sommes arrivés jusqu'ici et qu'il y a des trésors dans mon inventaire et dans la chambre. Si le «meilleur» trésor de la pièce a plus de valeur que le «pire» trésor de mon inventaire, et qu'il serait prudent de les échanger au cours des deux tours suivants, il le fait.

  • return 'previous'
    Si rien de tout cela ne se produit, revenez en arrière.

Mise à jour 16/04/19:

Mesures anti-scélérats. Cela deviendra une guerre d'enchères :(

Nouvelle mise à jour 16/04/19:

Inversé précédent, au lieu de cela, il commute au hasard tous les autres éléments lors de la recherche du meilleur, par exemple. [1, 2, 3, 4, 5, 6] → [2, 1, 3, 4, 6, 5]. Cela devrait être plus difficile à copier :).

Mise à jour 17/04/19:

Revenu précédent, il efface plutôt son propre code source . Il fait cela dans __init__lequel sera toujours avant Scoundrel.enter_ruins, et empêchera donc Scoundrel de le charger. Il remplace son code lors de get_actionson premier appel, afin qu'il soit prêt pour la prochaine fois. CORRIGÉ, Scoundrel meurt maintenant à son arrivée.

Nouvelle mise à jour 17/04/19:

Revenu précédent, il remplace à la place son sys.modulesentrée par le module mathématique, de sorte que lorsque Scoundrel tente de le charger, il charge le module mathématique à la place. :)
De plus, je viens juste de réaliser que l'endurance du mouvement était de 10 + poids / 5 , j'ai donc essayé de résoudre ce problème.

Nouvelle mise à jour 17/04/19:

Inclut désormais l'ail des deux mises à jour précédentes.

Mise à jour 18/04/19:

Jouant avec des chiffres et des calculs, obtient maintenant 2000 $ - 3000 $.

Nouvelle mise à jour 18/04/19:

Suppression de l'ail effacé des fichiers car il a été interdit, ajout d'un nouvel ail qui garantit qu'il 'draco'n'est pas responsable de son fonctionnement, s'il l'est, il revient juste previousà son premier tour. Les résultats ont pris une plongée mystérieuse à 1200 $ - 1800 $, que j'examine.

Artemis soutient Monica
la source
semble très efficace contre les ivrognes, j'aimerais voir comment ça se passe quand d'autres bots rejoignent le raid :)
Moogie
@Moogie bat le plongeur d'environ 100 $ lorsque 8 ivrognes sont également présents.
Artemis soutient Monica le
2

Le scélérat

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'

Le Scoundrel travaille principalement pour interférer avec les autres concurrents. Actuellement, il interfère avec Sprinter, Artyventurer et Accountant (cette liste s'allongera avec le temps à condition qu'elle soit dans le meilleur intérêt du Scoundrel). Il le fait en imitant les autres robots, puis en surenchérissant, en sous-coupant ou en se battant pour des reliques. En tant que tel, il est peu probable que cette entrée domine jamais le classement et opère plutôt comme une force de détérioration. La révision en cours au moment de cette publication le place à la 2e place avec un score moyen d'environ 7.

Scoundrel déjoue les tentatives des autres bots de se modifier pour se défendre contre le Scoundrel en exécutant directement le code des autres participants comme une copie de clone indiscernable. Les problèmes d'importations entraînant la duplication des entrants ont été résolus en créant les clones via Reflection (les guerres d'édition impliquant des détails fins de la détermination mathématique ne sont pas souhaitables du point de vue de l'échange de pile, mais aboutiraient au même résultat). Les défis KOTH ont également permis cela.

Scoundrel remplace les Teamsters afin de garder les Teamsters pour avoir été intéressants. Après cette modification, les Teamsters ne devraient plus être grattés par le contrôleur.

Mise à jour 17/04/2019: nouvelles contre-mesures.

Les Teamsters (rendus illégaux)

Mais n'hésitez pas à courir localement où il n'y a pas plus de 8 autres concurrents!

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

Cette entrée (bien que maintenant explicitement invalide) est, en fait, deux bots, et le contrôleur se fera un plaisir de les gratter tous les deux et de les ajouter à la liste des candidats (car hourra Python?)

La phase 1:

  • TeamsterA descend au niveau 25 (ish) 1 et ramasse et laisse tomber à plusieurs reprises le trésor le plus léger qu'il puisse trouver. Cela coûte 1 endurance énorme par tour jusqu'à la deuxième phase.
  • TeamsterB descend au niveau 55 et ramasse tous les objets de valeur qui traînent, puis revient au niveau 25 (ish). 2 Commence alors la phase 2.

1. S'il n'y a pas de trésor pesant moins de 3 sur un étage, il descend
2. Comme il est à peu près assuré d'être le dernier aventurier à revenir à la surface, tout ce qu'il a à faire est de trouver quelqu'un.

Phase 2:

  • TeamsterB vide ses poches avant de ramper pour mourir d'épuisement. Nous savions que vous pourriez le faire.
  • TeamsterA pense "ce sont des babioles brillantes, bon copain vieux copain!" et se charge sur les trésors beaucoup plus précieux que les autres ordures dans la salle avant de procéder à la sortie, les poches pleines d'or.

Le nom des trésors a été utile pour aider la logique à ne pas se charger sur les ordures du 25e étage et à partir tôt car il n'y avait aucun moyen de communiquer entre les deux robots (et TeamsterA se retrouverait toujours dans une pièce avec quelqu'un d'autre avant TeamsterB était revenu).

La prochaine conclusion logique: créer une armée

En théorie, cela pourrait être utilisé pour sonder les profondeurs et acquérir des trésors aussi profonds que la salle 98, cependant, car cela nécessiterait plus de 2 bots, la logique comprenant ces bots deviendrait de plus en plus complexe, et comme je suis certain que c'est une soumission illégale pour violation d'une règle non écrite, donc je ne vais pas m'embêter.

AAttend effectivement à 30, Battend à 50 ... nplonge à 98, ramasse un trésor, se déplace à 97, le dépose (puis meurt), le n-1ramasse et se déplace à 96 ... le Cdépose (meurt), le Bramasse en haut et passe à 30, le laisse tomber (meurt), le Aramasse et revient à la sortie.

J'estime que cela prendrait 11 bots.

Cependant, cela ne vaut la peine que si vous pouvez récupérer environ 4 trésors de cette profondeur afin de rivaliser avec des entrées comme PlanAhead ou Artyventure, en raison de l'échelle entre les coûts d'endurance à déplacer et la valeur moyenne des trésors.

Exemples de résultats

Score rarement moins de 4000 $, parfois des emblèmes de 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
la source
1
Je pense que lorsqu'il n'y avait qu'un seul bot par personne, il n'était pas nécessaire d'avoir une règle aussi explicite. Mais la règle concernant le ciblage d'un bot particulier pour des raisons néfastes n'est pas vraiment la même que de interdire plusieurs bots de s'associer. Une décision explicite du PO est donc nécessaire.
Moogie
Ouais, ça va être un non de ma part, dawg. C'est le genre de chose que j'avais en tête avec les robots travaillant ensemble.
Beefster
1
@Beefster C'est ce que je pensais. Mais je me suis amusé à le faire. Je m'occuperai de la modification pour empêcher l'inclusion ce soir.
Draco18s
J'envisagerai d'autoriser cela une fois qu'il y aura plus de 11 concurrents puisque son efficacité sera de toute façon réservée. Principalement parce que je ne veux pas créer de code pour les soumissions d'autoban.
Beefster
Si vous ne supprimez déjà que le premier bloc de code, tout ce que j'ai à faire est de modifier dans un bot différent en haut.
Draco18s
2

En arrière

Parce qu'il fonctionne à l'envers

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'

Pourquoi est-il appelé Backwards?

Parce que j'ai pris le comptable et essayé de lui faire exécuter sa logique de telle sorte qu'il plongerait profondément, puis ramasser son butin préféré à la sortie (à l'arrière du comptable).

En fin de compte, il recueille encore une grande partie de ses prix en cours de route (les ramassant avant les chercheurs traditionnels en collecte, fonctionnant à l'envers pour tout le monde), mais il est beaucoup plus sélectif quant à ceux qu'il prend, bien qu'il reste ramasse des choses sur son chemin de retour.

Le résultat final est que l'endurance est conservée en cours de route tout en priorisant les trésors de grande valeur, puis en profitant d'un virage en profondeur et de sélections faciles sur le chemin du retour. Backwards est connu pour collecter des trésors aussi loin que la salle 41 (et pendant le développement entrerait, puis quitterait immédiatement la salle 42).

Draco18s
la source
2

Chasseur de primes

La méthode simple est la meilleure. Saisissez des trésors précieux et légers tout en allant le plus loin possible. Récupérez des trésors moins précieux au retour.

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
la source
On dirait que vous obtenez la prime. Non seulement cela fonctionne mieux que Backwards, mais cela fait même reculer Backwards. Bien joué.
Beefster
1

Poids léger

Un bot simple qui fonctionne encore assez bien.

Après s'être aventuré dans les ruines (actuellement 21 chambres), il récupérera le meilleur trésor de la pièce qui ne fait que 1 kg (d'où le nom du bot) et qui a plus de valeur que le trésor le moins précieux de l'inventaire. Si l'inventaire est plein, laissez tomber le trésor le moins précieux. Si aucune autre action n'est sélectionnée, déplacez-vous dans les ruines. Si nous sommes à la limite de notre endurance pour pouvoir sortir vivants alors dirigez-vous vers la sortie

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
la source
Je recommanderais de mettre dumpingla enter_ruinsméthode. Cela se souviendra en fait entre les jeux et ne fonctionnera pas sur le jeu 2. Techniquement non autorisé, mais j'ai ajouté la règle tout à l'heure (je l'ai oubliée auparavant mais c'était mon intention), donc je vais perdre du temps. : P
Beefster
@Beefster J'ai supprimé le drapeau de l'état de dumping, ce n'est pas nécessaire car le bot ne dépose qu'un seul trésor maintenant. Il déversait la moitié de son trésor. Il devrait donc être compatible avec la nouvelle règle.
Moogie
1

Memorizer

Je peux soumettre des bots à mon propre KotH, non?

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'

Ce robot plonge dans la salle 30 et se souvient de tous les trésors qu'il a vus. À ce moment-là, il commence son voyage vers l'entrée, essayant de prendre de bons trésors dont il se souvenait avoir vu dans les salles précédentes.

J'espérais que ça ferait mieux. Les améliorations possibles peuvent provenir d'une meilleure planification et d'une plus grande dynamique dans quelle pièce il arrête de plonger et d'être plus disposé à explorer les options de sauvegarde.

Mise à jour: récupère désormais 1 kg de trésors d'une valeur de 60 $ ou plus en chemin.

Beefster
la source
J'imagine que tout ce bon trésor a disparu au moment où le bot y retourne ... Peut-être pouvez-vous essayer un combo où il emmènera les très bonnes choses sur son chemin, en gardant à l'esprit le trésor médiocre qu'il pourrait ramasser sur le chemin du retour?
ArBo
Ça pourrait aller trop loin
Beefster
Pour info, on dirait qu'il se trompe parfois s'il a assez d'endurance pour revenir: [Turn 072] Ryu Ridley (Memorizer) collapsed in the doorway to room #1 and died of exhaustion
Larkeith
1

Méditer

Je pense qu'il est assez similaire à Memorizer en ce qu'il utilise la connaissance des pièces visitées pour choisir les pièces et les trésors à collecter sur le chemin du retour à la sortie, mais il a été dérivé indépendamment.

Ce bot sprinte jusqu'à une pièce profonde aléatoire prenant un record des trésors trouvés le long du chemin. Une fois dans la salle cible, il réfléchira ensuite à la sélection idéale de trésors à ramener à la sortie. À chaque tour, il réfléchira nouveau pour déterminer la meilleure sélection de trésors à prendre.

Actuellement, il existe un algorithme simple (puissance inverse du numéro de la pièce) qui donne le nombre supposé de trésors pris (ou aura été pris lors de la visite de ce bot) pour chaque pièce et donc ces trésors sont ignorés lors de la réflexion sur les trésors / chambres à prendre. J'ai des idées pour d'autres algorithmes plus avancés pour modéliser les trésors qui restent. Mais je devrai voir si l'avantage en vaut la peine.

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
la source
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

Le thésauriseur reste dans une pièce jusqu'à ce qu'il ait pris tous les trésors de la pièce (ou calcule qu'il n'a pas assez d'endurance pour continuer à prendre / bouger). Lorsque tous les trésors ont disparu, si le bot peut continuer à avancer en toute sécurité, il le fera et continuera le processus de prise de tout le trésor.

lolade
la source
Cela meurt à chaque match en remplissant son sac à dos.
Beefster
comme moi dans Minecraft (͡ ° ͜ʖ ͡ °) Ce bot va piller, aller plus loin, puis trouver un butin précieux. Donc, cela laissera tomber ce qu'il pensait être un bon butin plus tôt. C'est pourquoi Backwardsla stratégie de Sprinter«s» , «et Memorizer» fonctionne; parce qu'ils savent quelles sont les valeurs relatives de chaque trésor qu'ils voient.
V. Courtois
0

Plongeur

(Je ne peux pas tester pour le moment, alors faites-moi savoir si cela est cassé.)

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'

Le meilleur trésor est plus profond dans les ruines, alors plongez profondément, puis prenez ce que nous pouvons en sortant.


la source
Je ne suis pas très expérimenté avec python, mais où est-il divingdéfini?
Incarnation de l'ignorance
1
@EmbodimentofIgnorance Dans enter_ruins (), qui est appelé avant que le jeu ne soit exécuté et que des actions soient effectuées.
Jacob the Orphan (Diver) was sliced in half by a swinging blade trap.Je ne sais pas ce que vous avez fait de mal, mais cela signifie «retour invalide» AFAIK.
Artemis prend en charge Monica
@ArtemisFowl, il a offert trop bas pour le trésor. Il en coûte le poids du trésor pour le ramasser.
Beefster
@Beefster Oh ouais.
Artemis prend en charge Monica le