La bureaucratie céleste KoTH


14

Dans la Chine impériale, les rangs dans la société n'étaient pas déterminés par la naissance ou la richesse, mais par la capacité d'une personne à exceller dans les examens impériaux. L'empereur de jade, souverain divin des cieux, a demandé que tous ses sujets soient examinés pour déterminer leur valeur et à qui ensuite confier le mandat divin pour gouverner la Chine.

Règles de la bureaucratie:

  • La bureaucratie divine se compose de rangs non négatifs à valeur entière, commençant par 0. Chaque membre (bot) de la bureaucratie appartient à un rang. Chaque rang peut contenir un nombre arbitraire de membres, mais ne peut être vide que si tous les rangs ci-dessus sont vides
  • Au début du jeu, tous les membres ont le rang 0
  • Chaque tour, chaque membre de la bureaucratie doit répondre à un examen. L'examen consiste à deviner correctement les valeurs booléennes d'une liste. La longueur de la liste est le numéro du rang au-dessus du membre.
  • Les questions d'examen sont préparées par un membre aléatoire du rang supérieur. Les membres du rang le plus élevé obtiennent leurs questions directement du JadeEmperor(voir ci-dessous)
  • Un membre ayant obtenu au moins 50% à son examen est éligible à la promotion. Un membre ayant obtenu moins de 50% à son examen est admissible à la rétrogradation.
  • Un membre éligible à la rétrogradation voit son rang diminué de un uniquement s'il y a un membre éligible à la promotion au grade ci-dessous pour prendre sa place.
  • Tous les membres éligibles à la promotion voient leur rang augmenté de un tant que cela ne laisse aucun rang vide.
  • Si tous les membres éligibles ne peuvent pas être rétrogradés ou promus, la préférence va à ceux des plus bas (pour rétrogradation) resp. score le plus élevé (pour la promotion). Les liens sont rompus au hasard.
  • Le rang d'un membre ne peut changer que d'au plus 1 à chaque tour.

Règles du jeu:

  • Chaque bot se verra attribuer un ID au hasard au début du jeu, qui ne changera pas au cours de son déroulement. Le JadeEmperora l'ID -1, tous les autres ont des ID non négatifs consécutifs, en commençant par 0.
  • Tous les bots s'affrontent en même temps
  • Le jeu se déroule sur 100 tours, le score du bot est son rang moyen possédé au cours de cette période.
  • Le score total est acquis en exécutant 1000 matchs et en faisant la moyenne des résultats.
  • Chaque Bot est une classe Python 3 implémentant les quatre fonctions suivantes:
    • ask(self,n,ID), ce qui fait un examen en renvoyant un listdes booléens de longueur n. ID est l'ID du bot qui doit deviner cette liste. ask()peut être appelé plusieurs fois au cours d'une même manche pour n'importe quel bot, mais pas du tout.
    • answer(self,n,ID), qui est une tentative de réponse à un examen en renvoyant un listdes booléens de longueur n. ID est l'ID du bot qui a ask()généré l'examen. answer()est appelé exactement une fois par tour pour chaque bot.
    • update(self,rankList,ownExam,otherExams)est appelé une fois que le contrôleur a effectué toutes les rétrogradations et rétrogradations. Ses arguments sont: Une liste d'entiers, listant tous les rangs par ID de tous les bots; un tuple, composé de deux listes, d'abord les questions d'examen, puis les réponses du bot (au cas où il aurait oublié); puis une liste de tuples, composée également de paires examen-réponse, cette fois pour tous les examens distribués par le bot.
    • __init__(self, ID, n) transmet au bot son propre identifiant et le nombre de bots concurrents.
  • Les classes sont autorisées à implémenter d'autres fonctions pour un usage privé
  • Il est explicitement permis de définir d'autres variables et de les utiliser pour stocker des données sur les examens passés.
  • La programmation des méta-effets est interdite, ce qui signifie que toute tentative d'accéder directement au code d'autres bots, au code du contrôleur, provoquant des exceptions ou similaires. Il s'agit d'un concours de stratégies pour les examens, pas de piratage de code.
  • Les bots essayant de s'entraider sont explicitement autorisés, tant qu'ils ne le font pas via des méta-effets, mais uniquement par les informations transmises update()
  • D'autres langues ne sont autorisées que si elles peuvent être facilement converties en Python 3.
  • La bibliothèque numpy sera importée en tant que np. La version est 1.6.5, ce qui signifie qu'elle utilise l'ancienne bibliothèque aléatoire. Si vous avez numpy 1.7, les anciennes fonctions sont disponibles sous numpy.random.mtrandpour les tests. N'oubliez pas de retirer le mtrand pour soumission.
  • Si un bot provoque une exception pendant l'exécution, il est disqualifié. Tout bot dont le code est tellement obscurci qu'il est impossible de dire s'il génère une liste de longueur n quand ask()ou answer()est appelé sera également disqualifié de manière préventive. Un bot me forçant à copier en profondeur les sorties obtient -1 sur le score.
  • Les noms de classe doivent être uniques
  • Plusieurs bots par personne sont autorisés, mais seule la dernière version sera prise des bots mis à jour de manière itérative.
  • Puisqu'il semble y avoir une certaine confusion sur la similitude des bots:
    • Vous n'êtes pas autorisé à publier une copie d'un autre bot. C'est la seule échappatoire standard qui s'applique vraiment à ce défi.
    • Vous êtes autorisé à partager du code avec d'autres robots, y compris des robots d'autres personnes.
    • Vous n'êtes pas autorisé à soumettre un bot qui ne diffère d'un autre que par un changement trivial de la stratégie (comme un changement dans la graine pour la génération de la question), sauf si vous pouvez prouver que le nombre de ces bots de copie carbone est le minimum requis pour réussir mise en œuvre de leur stratégie (Ce sera généralement deux bots pour une coopération).

Exemples de bots:

Le JadeEmperorfait toujours partie du jeu, mais ne participe pas à la compétition; il sert de générateur pour les examens des bots de rang le plus élevé. Ses examens sont aléatoires, mais pas uniformément, pour permettre aux robots intelligents de progresser.

class JadeEmperor:
    def __init__(self):
        pass

    def ask(self,n,ID):
        num=min(np.random.exponential(scale=np.sqrt(np.power(2,n))),np.power(2,n)-1)
        bi=list(np.binary_repr(int(num),width=n))
        return [x=='0' for x in bi]

L' ivrogne produit des examens et des réponses de manière complètement aléatoire. Il fera partie du jeu.

class Drunkard:
    def __init__(self,ID,n):
        pass

    def ask(self,n,ID):
        return list(np.random.choice([True,False],size=n,replace=True))

    def answer(self,n,ID):
        return list(np.random.choice([True,False],size=n,replace=True))

    def update(self,rankList,ownExam,otherExams):
        pass #out

Le plagiaire copie simplement les examens précédents. Il fera également partie du jeu.

class Plagiarist:
    def __init__(self,ID,n):
        self.exam=[True]

    def ask(self,n,ID):
        return (self.exam*n)[0:n]

    def answer(self,n,ID):
        return (self.exam*n)[0:n]

    def update(self,rankList,ownExam,otherExams):
        self.exam=ownExam[0]

Code contrôleur disponible ici . Pour les tests, vous pouvez placer votre propre classe dans un fichier Contestants.py dans le même dossier, et ils seront importés.

Chatroom se trouve ici .

Les examens commencent!

Score actuel, en plus haute précision (10000 runs) pour le 20 octobre:

ParticipantAuteurButAlphaSleafar9.669691GammaSleafar9.301362BêtaSleafar9.164597WiQeLuViolet P7.870821StudiousBotDignissimus - Spammy7.538537SantayanaSara J7.095528Plagiaire6.522047CountOracularIFcoltransG5.881175ThomasAlien @ System5.880041ContraireDraco18s5.529652MarxSugarfi5.433808Ivrogne5.328178YinYangViolet P5.102519ÉgaliseurMnémonique4.820996TitForTatAnonyme3,35801

Des concours seront organisés avec chaque nouvelle inscription dans un avenir prévisible.


1
Les copies de bots sont une échappatoire standard, donc non. Si vous essayez d'abuser des règles de plusieurs robots par auteur en soumettant des copies presque mais pas tout à fait, je les supprimerai.
AlienAtSystem

1
@AlienAtSystem Pourquoi autorisez-vous les robots à s'entraider? Cela semble être plus de chaos et d'aléatoire à gérer.
Don Thousand

2
Pourquoi les arguments du constructeur ID, nmais les autres arguments de la méthode n, ID?
Purple P

1
@DonThousand parce que je crois que sous les contraintes données, c'est tout un exploit de faire deux bots que A) réussisse la poignée de main (notez que le plagiaire pourrait accidentellement jouer l'homme au milieu) et B) puis adopter une stratégie qui aide de manière fiable ce bot mais pas d'autre à se lever.
AlienAtSystem

1
@someone rangs comptent vers le haut. Vous commencez à 0 et vous travaillez vers des nombres plus élevés
AlienAtSystem

Réponses:


4

Santayana

Ceux qui ne se souviennent pas du passé sont condamnés à le répéter. Nous prenons donc nos décisions en fonction de la façon dont les autres ont agi dans le passé, en répondant en fonction de la réponse que le demandeur attend habituellement de nous à un indice donné, et en demandant la réponse qu'ils nous ont donnée le moins souvent à un indice donné .

import numpy as np

class Santayana:
    """
    Those who cannot remember the past are condemned to repeat it
    """
    def __init__(self, ID, num_competitors):
        self.ID = ID
        self.exams_taken = {}
        self.exams_issued = {}
        self.last_exam_asker = None
        self.recent_exam_takers = []

        for i in range(num_competitors):
            self.exams_taken[i] = []
            self.exams_issued[i] = []

    def ask(self, length, taker_ID):
        # Remember who asked
        self.recent_exam_takers.append(taker_ID)
        new_exam = []

        # At every index, expect the answer they've given the least often (default to False if equal)
        for i in range(length):
            trues = 0
            falses = 0
            for exam in self.exams_issued[taker_ID]:
                if len(exam) <= i: continue
                if exam[i]:
                    trues += 1
                else:
                    falses += 1
            new_exam.append(trues < falses)
        return new_exam

    def answer(self, num_answers, asker_ID):
        self.last_exam_asker = asker_ID
        if asker_ID == -1:
            # Copy emperor's process to hopefully get a similar exam
            num = min(np.random.exponential(scale=np.sqrt(np.power(2,num_answers))),np.power(2,num_answers)-1)
            as_bin = list(np.binary_repr(int(num),width=num_answers))
            return [x=='0' for x in as_bin]
        else:
            new_answer = []

            # At every index, give the answer that's been correct the greatest number of times (default to True if equal)
            for i in range(num_answers):
                trues = 0;
                falses = 0;
                for exam in self.exams_taken[asker_ID]:
                    if len(exam) <= i: continue
                    if exam[i]:
                        trues += 1
                    else:
                        falses += 1
                new_answer.append(trues >= falses)
            return new_answer

        return [True for i in range(num_answers)]

    def update(self, rank_list, own_exam, other_exams):
        if self.last_exam_asker > -1:
            # Save the exam we took, unless it was from the Emperor - we already know how he operates
            self.exams_taken[self.last_exam_asker].append(own_exam[0])
        for i in range(len(self.recent_exam_takers)):
            # Save the responses we got
            self.exams_issued[i].append(other_exams[i][1])

        self.recent_exam_takers = []

3

Studious Bot

Ce bot étudie pour les tests! Il essaie de trouver des modèles dans les tests donnés par divers bots et agit en conséquence.

Pour ma part, jusqu'à présent, ce bot surpasse tous les autres bots que je pourrais faire fonctionner sur mon ordinateur, à l'exception d'Alpha, Beta et Gamma (qui ont été programmés pour fonctionner ensemble). Le bot n'utilise pas le fait que faire équipe est autorisé parce que je sentais que c'était un peu comme tricher et un peu sale. Cependant, en regardant par-dessus, le travail en équipe semble être assez efficace.

Le bot tente de reconnaître quand les réponses aux tests sont aléatoires et en réponse correspond à ce que l'on espère en moyenne 50% sur les tests.

Le bot tente également de reconnaître quand un bot a simplement retourné ses réponses pour rejeter d'autres bots qui tentent de prédire leur comportement, mais je ne l'ai pas encore programmé pour agir spécifiquement sur cela.

J'ai annoté le code avec quelques commentaires afin d'en faciliter la lecture

import random
import numpy as np


class StudiousBot:
    GRAM_SIZE = 5
    def __init__(self, identifier, n):
        self.id = identifier
        self.ranks = {i: 0 for i in range(n)} # Stores ranks
        self.study_material = {i: [] for i in range(n)} # Stores previous exam data
        self.distribution = {i: [] for i in range(n)} # Stores the percentage of answers that were `True` on a Bot's tests over time
        self.last_examiner = None

    def ask(self, n, identifier):
        # This bot gives random tests, it doesn't bother making them difficult based on answers to them
        # The reason for this is that I can't personalise the tests for each bot
        return [random.choice([True, False]) for i in range(n)] 

    def answer(self, n, examiner_id):
        self.last_examiner = examiner_id
        if examiner_id == -1:
            return StudiousBot.answer_emperor(n) # Easy win, I know the distribution of answers for the Emperor's tests

        bother_predicting = True # Whether or not the Bot will attempt to predict the answers to the exam
        study_material = self.study_material[examiner_id]
        distribution = self.distribution[examiner_id]
        if len(distribution) > 0: # If there is actually data to analyse
            sd = StudiousBot.calculate_standard_deviation(distribution)
            normalised_sd = StudiousBot.calculate_normalised_standard_deviation(distribution)

            if abs(30 - sd) < 4: # 30 is the expected s.d for a random distribution
                bother_predicting = False # So I won't bother predicting the test 

            if abs(sd - normalised_sd * 2) > 4: # The bot is merely inverting answers to evade being predicted
                pass # However, at this time, I'm not certain how I should deal with this. I'll continue to attempt to predict the test 


        if bother_predicting and len(study_material) >= StudiousBot.GRAM_SIZE:
            return StudiousBot.predict(study_material, n)

        return [random.choice([True, False]) for i in range(n)]

    def predict(study_material, n): # Predicts the answers to tests with `n` questions
        grams = StudiousBot.generate_ngrams(study_material, StudiousBot.GRAM_SIZE) # Generate all n-grams for the study material
        last_few = study_material[-(StudiousBot.GRAM_SIZE - 1):] # Get the last 9 test answers
        prediction = None
        probability = -1
        for answer in [True, False]: # Finds the probabiility of the next answer being True or False, picks the one with the highest probability
            new_prediction = last_few + [answer]
            new_probability = grams.count(new_prediction)         

            if new_probability > probability:
                prediction = answer
                probability = new_probability

        if n == 1:
            return [prediction]

        return [prediction] + StudiousBot.predict(study_material + [prediction], n-1)          


    @staticmethod
    def calculate_standard_deviation(distribution):
        return np.std(distribution)

    def calculate_normalised_standard_deviation(distribution): # If the answers happen to be inverted at some point, this function will return the same value for answers that occured both before and after this point  
        distribution = list(map(lambda x: 50 + abs(50-x), distribution))
        return StudiousBot.calculate_standard_deviation(distribution)   

    @staticmethod
    def generate_ngrams(study_material, n):
        assert len(study_material) >= n
        ngrams = []
        for i in range(len(study_material) - n + 1):
            ngrams.append(study_material[i:i+n])

        return ngrams

    def update(self, ranks, own_exam, other_exams):
        self.ranks = dict(enumerate(ranks))
        if self.last_examiner != -1:
            self.study_material[self.last_examiner] += own_exam[0]
            self.distribution[self.last_examiner].append(own_exam[0].count(True) / len(own_exam[0]) * 100) # Stores the percentage of the answers which were True

    @staticmethod
    def answer_emperor(n): # Algorithm to reproduce Emperor's distribution of test answers  
        exp = np.random.exponential(scale=np.sqrt(np.power(2,n)))
        power = np.power(2,n) - 1        
        num = min(exp, power)
        bi = list(np.binary_repr(int(num), width=n))
        return [x == '0' for x in bi]

À en juger par nos performances, vous avez le meilleur algorithme pour répondre et Wi Qe Lu a le meilleur algorithme pour demander. Je propose que nous combinions nos bots en un seul bot, appelé Xuézhě (chinois pour "érudit"), qui sonne comme un "switcher".
Purple P

Je l'ai piraté et j'ai passé les examens sur ma machine. Curieusement, il a dépassé Studious Bot, mais pas Wi Qe Lu.
Purple P

@PurpleP Haha! Cela semble très intéressant, je ne pense pas qu'il me reste suffisamment de temps pour améliorer mon bot, mais vous pouvez le poster en tant que soumission ici
Dignissimus - Spammy

3

Count Oracular

Ce bot utilise un algorithme qui fait la moyenne des examens de tous les autres bots qui fonctionnent (étant donné le nombre de tours et quelques heuristiques terribles) pour décider ce que chaque autre bot définira comme examen.
Le comte demande ses examens à l'aide d'un hachage md5. Ses questions et ses réponses sont donc déterministes. Il ignore la plupart des entrées, demandant et répondant exactement aux mêmes séquences de booléens, de pluie ou de soleil, y compris contre Jade Emporer.

import numpy as np
import hashlib

class CountOracular:
    '''Uses very little external data to make heuristical statistical
    deterministic predictions about the average exam.
    (Assonance not intended.)
    To generate its own exams, uses a deterministic hash.'''
    def __init__(self, id, number_of_bots):
        self.last_round = []
        #functions for calculating what other bots will likely do.
        self.bots_calculators = [
            self._jad, #Jade Emporer
            self._alp, #Alpha
            self._bet, #Beta
            self._gam, #Gamma
            self._wiq, #Wi Qe Lu
            self._stu, #StudiousBot
            self._pla, #Plagiarist
            self._san, #Santayana
            self._tho, #Thomas
            self._dru, #Drunkard
            self._yin, #YinYang
            self._con, #Contrary
            self._tit, #TitForTat
            self._equ, #Equalizer
            self._mar, #Marx
        ]
        self.bot_types = len(self.bots_calculators)
    def ask(self, n, id):
        #if we can, show that hardcoding is no match for the power of heuristics:
        if n == 2:
            return [False, True]
        #otherwise, refer to the wisdom of Mayor Prentiss in order to command The Ask
        #i.e. hashes a quote, and uses that as the exam.
        salt = b"I AM THE CIRCLE AND THE CIRCLE IS ME " * n
        return self._md5_from(salt, n)
    def answer(self, n, id):
        #uses the power of heuristics to predict what the average bot will do
        #ignores all inputs except the length of the output
        #very approximate, and deterministic
        #i.e. every game, Count Oracular will send the same lists of answers, in the same order
        best_guess_totals = [0.5] * n #halfway between T and F
        for bot in self.bots_calculators:
            exam, confidence = bot(n)
            if not exam:
                continue
            while len(exam) < n:
                #ensure exam is long enough
                exam += exam[:1]
            exam = exam[:n] #ensure exam is short enough
            #map T and F to floats [0,1] based on confidence
            weighted_exam = [0.5+confidence*(0.5 if q else -0.5) for q in exam]
            best_guess_totals = [current+new for current,new in zip(best_guess_totals, weighted_exam)]
        best_guess_averages = [total/self.bot_types
            for total
            in best_guess_totals
        ]
        best_guess = [avg > 0.5 for avg in best_guess_averages]
        self.last_round = best_guess
        return best_guess
    def update(self, ranks, own, others):
        pass
    def _md5_from(self, data, n):
        md5 = hashlib.md5(data)
        for i in range(n):
            md5.update(data)
        exam = []
        while len(exam) < n:
            exam += [x == "0"
                for x
                in bin(int(md5.hexdigest(), 16))[2:].zfill(128)
            ]
            md5.update(data)
        return exam[:n]
    def _invert(self, exam):
        return [not val for val in exam]
    def _digits_to_bools(self, iterable):
        return [char=="1" for char in iterable]
    def _plagiarise(self, n):
        copy = (self.last_round * n)[:n]
        return copy

    '''functions to calculate expected exams for each other bot:
       (these values, weighted with corresponding confidence ratings,
       are summed to calculate the most likely exam.)'''
    def _jad(self, n):
        '''Calculate the mean of _jad's distribution, then
        use that as the guess'''
        mean = max(int(np.sqrt(np.power(2,n))), (2<<n)-1)
        string_mean = f"{mean}".zfill(n)
        exam = self._invert(self._digits_to_bools(string_mean))
        return exam, 0.5
    def _alp(self, n):
        '''Alpha uses a predictable hash,
        until it figures out we aren't Beta,
        modelled by the probability of giving or solving
        Alpha's exam'''
        #probability that Alpha thinks we're Beta
        #assuming we fail to pretend to be Beta if we meet Alpha
        chance_beta = ((1 - 1/self.bot_types) ** n) ** 2
        return self._md5_from(b"Beta", n), chance_beta
    def _gam(self, n):
        '''Gamma is like Beta, except after realising,
        switches to 50-50 random choice of inverse
        either Beta or Alpha's hash'''
        #probability that Gamma thinks we're Alpha still
        #(Unlikely that Gamma will think we're Beta;
        #we'd need to fail Alpha but pass Beta,
        #therefore, not accounted for)
        chance_unknown = ((1 - 1/self.bot_types) ** n) ** 2
        #default exam that assumes that Gamma thinks we're Alpha
        exam = self._md5_from(b"Beta", n)
        if chance_unknown > 0.5:#there exists a better heuristic here
            #assume Gamma will consider us Alpha
            confidence = chance_unknown
        else:
            #assume Gamma considers us neither Alpha nor Beta
            alpha = self._invert(self._md5_from(b"Beta", n))
            beta = self._invert(self._md5_from(b"Alpha", n))
            #check for bools where both possible exams match
            and_comp = [a and b for a, b in zip(alpha, beta)]
            nor_comp = [not (a or b) for a, b in zip(alpha, beta)]
            #count up matches vs times when fell back on default
            #to calculate ratio of default
            #to bools where hashes agree
            confidence_vs_default = (sum(and_comp)+sum(nor_comp)) / n
            confidence = confidence_vs_default * chance_unknown + (1 - confidence_vs_default) * (1 - chance_unknown)
            for i in range(n):
                if and_comp[i]:
                    exam[i] = True
                if nor_comp[i]:
                    exam[i] = False
        return exam, confidence
    def _bet(self, n):
        '''Beta is like Alpha, but with a different hash'''
        #probability we haven't matched with Beta yet
        #i.e. probability that Beta still thinks we're Alpha
        chance_alpha = ((1 - 1/self.bot_types) ** n) ** 2
        return self._md5_from(b"Alpha", n), chance_alpha
    def _wiq(self, n):
        '''Wi Qe Lu is hard to model, so we pretend
        that it mimicks Plagiarist for the most part'''
        if n == 1:
            #first round is random
            return [False], 0
        #other rounds are based on exams it met
        #leaning towards same as the previous exam
        return self._plagiarise(n), 0.1
    def _stu(self, n):
        '''StudiousBot is random'''
        return [False] * n, 0
    def _pla(self, n):
        '''Plagiarist copies the exams it received,
        which can be modelled with the standard prediction
        calculated for the previous round, padded with its first
        element.'''
        if n == 1:
            return [True], 1
        return self._plagiarise(n), 0.3
    def _san(self, n):
        '''Santayana is based on answers, which we don't predict.
        Modelled as random.'''
        #mostly random, slight leaning towards default False
        return [False] * n, 0.1
    def _tho(self, n):
        '''Thomas has an unpredictable threshold.'''
        #for all intents, random
        return [False] * n, 0
    def _dru(self, n):
        '''Drunkard is utterly random.'''
        return [False] * n, 0
    def _yin(self, n):
        '''YinYang inverts itself randomly, but not unpredictably.
        We can model it to find the probability. Also notably,
        one index is inverted, which factors into the confidence
        especially for lower n.'''
        if n == 1:
            #one element is inverted, so whole list must be False
            return [False], 1
        if n == 2:
            #split half and half randomly; can't predict
            return [True] * n, 0
        #cumulative chance of mostly ones or mostly zeros
        truthy = 1
        for _ in range(n):
            #simulate repeated flipping
            truthy = truthy * 0.44 + (1-truthy) * 0.56
        falsey = 1 - truthy
        if falsey > truthy:
            return [False] * n, falsey - 1/n
        return [True] * n, truthy - 1/n
    def _con(self, n):
        '''Contrary is like Jade Emporer, but inverts itself
        so much that modelling the probability of inversion
        is not worth the effort.'''
        #there are some clever ways you could do statistics on this,
        #but I'm content to call it uniform for now
        return [False] * n, 0
    def _tit(self, n):
        '''TitForTat is most likely to give us False
        but the confidence drops as the chance of having
        met TitForTat increases.
        The square root of the probability we calculate for
        Alpha, Beta and Gamma, because those also care about what
        we answer, whereas TitForTat only cares about what we ask'''
        #probability that we've not given TitForTat an exam
        chance_friends = (1 - 1/self.bot_types) ** n
        return [False] * n, chance_friends
    def _equ(self, n):
        '''Equalizer always asks True'''
        #certain that Equalizer's exam is all True
        return [True] * n, 1
    def _mar(self, n):
        '''Marx returns mostly True, randomised based on our rank.
        We don't predict our rank.
        There's ~50% chance an answer is random'''
        #75% chance we guess right (= 50% + 50%*50%)
        return [True] * n, 0.75

Une excellente idée en théorie, mais dans son premier concours, le comte Oracular a fait moins bien que YinYang, malgré ses efforts pour simuler YinYang.
Purple P

1
@PurpleP Oui, ce n'est pas très bon. La raison en est qu'il essaie de choisir une stratégie «généralement optimale» en faisant la moyenne de toutes les stratégies spécifiques ensemble. Par exemple, il n'utilise pas une stratégie adaptée pour battre YinYang lorsqu'il rencontre YinYang. Il n'utilise même pas de stratégie spécifique sur Jade Emporer: il ajoute simplement la stratégie Jade Emporer à la moyenne. Ce sera mieux que aléatoire, mais pas beaucoup.
IFcoltransG

Marx a été corrigé. Vous devez mettre à jour Count Oracular pour le prévoir.
Purple P

@PurpleP Marx devrait être pris en charge maintenant. C'est comme si c'était encore 1917.
IFcoltransG

2

YinYang

Répond à toutes Trueou à toutes False, sauf pour un index choisi au hasard pour être le contraire. Demande le contraire de ce qu'il répond. Échange au hasard pour repousser les adversaires.

import random

class YinYang:
    def __init__(self, ID, n):
        self.exam = True

    def update(self, rankList, ownExam, otherExams):
        if random.random() < 0.56:
            self.exam = not self.exam

    def answer(self, n, ID):
        a = [not self.exam] * n
        a[random.randint(0, n-1)] = self.exam
        return a

    def ask(self, n, ID):
        e = [self.exam] * n
        e[random.randint(0, n-1)] = not self.exam
        return e

Wi Qe Lu (Switcheroo)

Répond et demande au hasard au premier tour. Par la suite, il utilise les réponses de l'examen précédent et modifie une question si un nombre supérieur à la moyenne de concurrents a réussi.

class WiQeLu:
    def __init__(self, ID, n):
        self.rounds = 1
        self.firstexam = True
        self.firstanswer = True
        self.lastexaminer = -1
        self.exam = []
        self.pastanswers = {}

    def update(self, rankList, ownExam, otherExams):
        questions, lastanswers = ownExam
        self.pastanswers[self.lastexaminer] = questions

        if len(otherExams) == 0:
            return
        correctCounts = [0 for i in otherExams[0][0]]
        for ourExam, response in otherExams:
            for i in range(len(response)):
                if ourExam[i] == response[i]:
                    correctCounts[i] += 1

        newExam = otherExams[0][0]
        meanWhoAnsweredCorrectly = sum(correctCounts) / len(correctCounts)
        for i in range(len(correctCounts)):
            if correctCounts[i] > meanWhoAnsweredCorrectly:
                newExam[i] = not newExam[i]
        self.exam = newExam

    def answer(self, n, ID):
        self.lastexaminer = ID
        if ID not in self.pastanswers:
            randomanswer = [random.randint(0, 1) == 1] * n
            self.pastanswers[ID] = randomanswer
            return randomanswer
        return (self.pastanswers[ID] * n)[:n]

    def ask(self, n, ID):
        if self.firstexam:
            self.firstexam = False
            self.exam = [random.randint(0, 1) == 1] * n
        return (self.exam * n)[:n]

5
Selon Google Translate, "wi qe lu" est à peu près traduit par "Je suis une route des pingouins".
Purple P

2

Un bot à moi:

Thomas

Un voyageur d'un pays lointain a des idées dangereuses sur les résultats passés qui indiquent les performances futures. Il les utilise pour garder les autres bots à moins que cela n'entrave sa propre progression.

class Thomas:
    def __init__(self,ID,n):
        N=10
        self.ID=ID
        self.myrank=n
        self.lowerank=0
        #The highest number of questions is equal to the number of participants, so we can do this:
        self.probs=[{i:1.0/N for i in np.linspace(0,1,num=N)} for i in np.arange(n)]
        self.output=[0.5]*n

    def ask(self,n,ID):
        if self.myrank==1 and self.lowerrank > 1: #I can't advance without promoting somebody first
            return [self.output[i]>np.random.rand() for i in np.arange(n)]
        #Otherwise, try to step on their fingers by going against the expected probability
        return [self.output[i]<np.random.rand() for i in np.arange(n)]


    def answer(self,n,ID):
        return [self.output[i]>np.random.rand() for i in np.arange(n)]

    def update(self,rankList,ownExam,otherExams):
        #Update our ranks
        self.myrank=len([i for i in rankList if i==rankList[self.ID]])
        self.lowerrank=len([i for i in rankList if i==rankList[self.ID]-1])
        #Update our expectations for each input we've been given
        self.bayesianupdate(ownExam[0])
        for ex in otherExams:
            self.bayesianupdate(ex[1])
        #Compress into output variable
        self.output=[np.sum([l[entry]*entry for entry in l]) for l in self.probs]

    def bayesianupdate(self,data):
        for i in np.arange(len(data)):
            if data[i]: #Got a True
                self.probs[i].update({entry:self.probs[i][entry]*entry for entry in self.probs[i]})
            else: #Got a False
                self.probs[i].update({entry:self.probs[i][entry]*(1-entry) for entry in self.probs[i]})
            s=np.sum([self.probs[i][entry] for entry in self.probs[i]]) #Renormalize
            self.probs[i].update({entry:self.probs[i][entry]/s for entry in self.probs[i]})
```

Avez-vous oublié d'indenter votre code après l'instruction de classe?
pppery

C'est juste le formatage SE qui me surprend. Je vais le réparer avec tout ce qui a causé une erreur dans le test de quelqu'un lors de l'utilisation de ce bot
AlienAtSystem

2

Alpha

Lisez le chat avant de voter. Ces robots ne violent aucune règle. Le PO encourage même les robots coopérants.

Alpha forme une équipe avec Beta. Les deux utilisent un ensemble d'examens prédéfinis pour s'aider mutuellement à gravir les échelons. Les deux profitent également des bots utilisant les mêmes examens encore et encore.

import numpy as np
import hashlib

class Alpha:
    def __init__(self, ID, n):
        self.alpha = hashlib.md5(b"Alpha")
        self.beta = hashlib.md5(b"Beta")
        self.asker = -1
        self.betas = set(range(n)).difference([ID])
        self.fixed = set(range(n)).difference([ID])
        self.fixedExams = [[]] * n

    def ask(self,n,ID):
        if ID in self.betas:
            return self.md5ToExam(self.alpha, n)
        else:
            return list(np.random.choice([True, False], n))

    def answer(self,n,ID):
        self.asker = ID
        if self.asker == -1:
            return [True] * n
        elif self.asker in self.fixed and len(self.fixedExams[self.asker]) > 0:
            return (self.fixedExams[self.asker] * n)[:n]
        elif self.asker in self.betas:
            return self.md5ToExam(self.beta, n)
        else:
            return list(np.random.choice([True, False], n))

    def update(self,rankList,ownExam,otherExams):
        if self.asker >= 0:
            if self.asker in self.betas and ownExam[0] != self.md5ToExam(self.beta, len(ownExam[0])):
                    self.betas.remove(self.asker)
            if self.asker in self.fixed:
                l = min(len(ownExam[0]), len(self.fixedExams[self.asker]))
                if ownExam[0][:l] != self.fixedExams[self.asker][:l]:
                    self.fixed.remove(self.asker)
                    self.fixedExams[self.asker] = []
                elif len(ownExam[0]) > len(self.fixedExams[self.asker]):
                    self.fixedExams[self.asker] = ownExam[0]
        self.alpha.update(b"Alpha")
        self.beta.update(b"Beta")

    def md5ToExam(self, md5, n):
        return [x == "0" for x in bin(int(md5.hexdigest(), 16))[2:].zfill(128)][:n]

Je pense que ces trois robots violent les règles du PO, comme indiqué dans l'invite et les commentaires.
Don Thousand

@DonThousand Si vous lisez la discussion dans le chat, vous verrez qu'ils ne violent pas les règles. chat.stackexchange.com/rooms/98905/imperial-exams-office
Sleafar

C'est suffisant. Ma faute.
Don Thousand

@DonThousand Alors, à quoi bon tous les voter?
Sleafar

J'ai seulement dévalorisé Alpha. Je ne peux cependant pas revenir en arrière. Faites un montage superflu et je le corrigerai.
Don Thousand

1

Égaliseur

Tout le monde devrait être égal (sans aucune de ces sottises stupides d'empereur), donc fournissez autant de mobilité sociale que possible. Rendez les questions vraiment faciles (la réponse est toujours vraie) afin que les gens puissent réussir.

class Equalizer:
    def __init__(self, ID, n):
        self.previousAnswers = [[0, 0] for _ in range(n)]
        self.previousAsker = -1

    def ask(self, n, ID):
        return [True] * n

    def answer(self, n, ID):
        if ID == -1:
            return [True] * n

        # Assume that questions from the same bot will usually have the same answer.
        t, f = self.previousAnswers[ID]
        return [t >= f] * n

    def update(self, rankList, ownExam, otherExams):
        if self.previousAsker == -1:
            return

        # Keep track of what answer each bot prefers.
        counts = self.previousAnswers[self.previousAsker]
        counts[0] += ownExam[0].count(True)
        counts[1] += ownExam[0].count(False)

1

Bêta

Lisez le chat avant de voter. Ces robots ne violent aucune règle. Le PO encourage même les robots coopérants.

Beta forme une équipe avec Alpha. Les deux utilisent un ensemble d'examens prédéfinis pour s'aider mutuellement à gravir les échelons. Les deux profitent également des bots utilisant les mêmes examens encore et encore.

import numpy as np
import hashlib

class Beta:
    def __init__(self,ID,n):
        self.alpha = hashlib.md5(b"Alpha")
        self.beta = hashlib.md5(b"Beta")
        self.asker = -1
        self.alphas = set(range(n)).difference([ID])
        self.fixed = set(range(n)).difference([ID])
        self.fixedExams = [[]] * n

    def ask(self,n,ID):
        if ID in self.alphas:
            return self.md5ToExam(self.beta, n)
        else:
            return list(np.random.choice([True, False], n))

    def answer(self,n,ID):
        self.asker = ID
        if self.asker == -1:
            return [True] * n
        elif self.asker in self.fixed and len(self.fixedExams[self.asker]) > 0:
            return (self.fixedExams[self.asker] * n)[:n]
        elif self.asker in self.alphas:
            return self.md5ToExam(self.alpha, n)
        else:
            return list(np.random.choice([True, False], n))

    def update(self,rankList,ownExam,otherExams):
        if self.asker >= 0:
            if self.asker in self.alphas and ownExam[0] != self.md5ToExam(self.alpha, len(ownExam[0])):
                    self.alphas.remove(self.asker)
            if self.asker in self.fixed:
                l = min(len(ownExam[0]), len(self.fixedExams[self.asker]))
                if ownExam[0][:l] != self.fixedExams[self.asker][:l]:
                    self.fixed.remove(self.asker)
                    self.fixedExams[self.asker] = []
                elif len(ownExam[0]) > len(self.fixedExams[self.asker]):
                    self.fixedExams[self.asker] = ownExam[0]
        self.alpha.update(b"Alpha")
        self.beta.update(b"Beta")

    def md5ToExam(self, md5, n):
        return [x == "0" for x in bin(int(md5.hexdigest(), 16))[2:].zfill(128)][:n]

1

Gamma

Lisez le chat avant de voter. Ces robots ne violent aucune règle. Le PO encourage même les robots coopérants.

Gamma a découvert les plans d'Alpha et Beta et essaie de profiter des deux en se déguisant en l'un d'eux.

import numpy as np
import hashlib

class Gamma:
    def __init__(self, ID, n):
        self.alpha = hashlib.md5(b"Alpha")
        self.beta = hashlib.md5(b"Beta")
        self.asker = -1
        self.alphas = set(range(n)).difference([ID])
        self.betas = set(range(n)).difference([ID])
        self.fixed = set(range(n)).difference([ID])
        self.fixedExams = [[]] * n

    def ask(self,n,ID):
        if ID in self.alphas:
            return self.md5ToExam(self.beta, n)
        elif ID in self.betas:
            return self.md5ToExam(self.alpha, n)
        else:
            return self.md5ToWrongExam(np.random.choice([self.alpha, self.beta], 1)[0], n)

    def answer(self,n,ID):
        self.asker = ID
        if self.asker == -1:
            return [True] * n
        elif self.asker in self.fixed and len(self.fixedExams[self.asker]) > 0:
            return (self.fixedExams[self.asker] * n)[:n]
        elif self.asker in self.alphas:
            return self.md5ToExam(self.alpha, n)
        elif self.asker in self.betas:
            return self.md5ToExam(self.beta, n)
        else:
            return list(np.random.choice([True, False], n))

    def update(self,rankList,ownExam,otherExams):
        if self.asker >= 0:
            if self.asker in self.alphas and ownExam[0] != self.md5ToExam(self.alpha, len(ownExam[0])):
                    self.alphas.remove(self.asker)
            if self.asker in self.betas and ownExam[0] != self.md5ToExam(self.beta, len(ownExam[0])):
                    self.betas.remove(self.asker)
            if self.asker in self.fixed:
                l = min(len(ownExam[0]), len(self.fixedExams[self.asker]))
                if ownExam[0][:l] != self.fixedExams[self.asker][:l]:
                    self.fixed.remove(self.asker)
                    self.fixedExams[self.asker] = []
                elif len(ownExam[0]) > len(self.fixedExams[self.asker]):
                    self.fixedExams[self.asker] = ownExam[0]
        self.alpha.update(b"Alpha")
        self.beta.update(b"Beta")

    def md5ToExam(self, md5, n):
        return [x == "0" for x in bin(int(md5.hexdigest(), 16))[2:].zfill(128)][:n]

    def md5ToWrongExam(self, md5, n):
        return [x == "1" for x in bin(int(md5.hexdigest(), 16))[2:].zfill(128)][:n]

1

TitForTat

Vous pose des questions faciles si vous lui avez posé des questions faciles dans le passé. Si vous ne lui avez jamais passé d'examen, il est par défaut des questions faciles.

De plus, ne fait confiance à personne qui pose des questions difficiles et leur donnera des réponses imprévisibles.

import numpy as np

class TitForTat:
    def __init__(self, ID, n):
        self.friendly = [True] * n
        self.asker = -1

    def make_answers(self, n, ID):
        if ID == -1 or self.friendly[ID]:
            return [False] * n
        else:
            return list(np.random.choice([True, False], n))

    def ask(self, n, ID):
        return self.make_answers(n, ID)

    def answer(self, n, ID):
        self.asker = ID
        return self.make_answers(n, ID)

    def update(self, rankList, ownExam, otherExams):
        if self.asker != -1:
            # You are friendly if and only if you gave me a simple exam
            self.friendly[self.asker] = all(ownExam[0])

Ce bot fonctionne bien si d'autres bots coopèrent avec lui. Actuellement, seul Equalizer coopère, mais cela devrait être suffisant.


Pour le moment, le bot ne peut pas rivaliser car il ne respecte pas les spécifications. Assurez-vous qu'il renvoie des listobjets à tout moment. De plus, selon les règles anciennes et mises à jour, les copies parfaites d'un bot ne sont pas des soumissions valides, donc le nombre autorisé d'instances de ce bot en cours d'exécution est de 1.
AlienAtSystem

Je l'ai édité pour renvoyer des listes. En ce qui concerne les copies parfaites, il n'y a pas de bot actuel qui coopère correctement avec lui, donc le nombre de bots de copie carbone - le minimum requis pour la réussite de la mise en œuvre de la stratégie - est d'au moins 1 (ce bot et 1 copie de celui-ci sont nécessaires ).
Anonyme

Vous faites valoir que vous êtes admissible à une exception en vertu de l'article 3 tout en essayant de soumettre quelque chose qui relève de l'article 1: des copies parfaites d'un bot ne sont jamais valides, sans exception. Et pour bénéficier de l'exception de l'article 3, vous devez prouver que votre stratégie exige strictement que tous ces partenaires y réagissent, comme par exemple un signal de poignée de main, qui est en effet inutile sans que quelqu'un ne l'écoute. Pas le vôtre. Equalizer vous remettra des examens pour déclencher la clause "amicale", prouvant ainsi qu'une copie de votre bot est nécessaire.
AlienAtSystem

Bien alors. Je ferai quelques derniers ajustements.
Anonyme

0

Contraire

L'empereur de jade a toujours raison, il implémente donc la fonction de demande de l'empereur de jade comme sa propre fonction de réponse lorsqu'il a besoin de plus de 2 réponses. Pour seulement 1 réponse, il répond true(chances décentes d'être correctes) et pour 2, il répond true,false(cette réponse passe "au moins la moitié" des questions trois quiz sur quatre possibles, mieux que de choisir au hasard).

Utilise une logique similaire dans sa mise à jour en ce qui concerne la façon dont il modifie son modèle de demande, mais sa logique de demande est similaire à celle de l'empereur de jade, juste avec un poids différent. Fluctue entre des valeurs plus élevées trueet des valeurs plus élevées falselorsque trop de candidats obtiennent un score suffisamment élevé pour réussir.

class Contrary:
    def __init__(self,ID,n):
        self.rank = 0
        self.ID = ID
        self.competitors = {}
        self.weight = -2
        pass

    def ask(self,n,ID):
        if self.weight > 0:
            num=min(np.random.exponential(scale=np.sqrt(np.power(self.weight,n))),np.power(2,n)-1)
            bi=list(np.binary_repr(int(num),width=n))
            return [x=='0' for x in bi]
        else:
            num=min(np.random.exponential(scale=np.sqrt(np.power(-self.weight,n))),np.power(2,n)-1)
            bi=list(np.binary_repr(int(num),width=n))
            return [x=='1' for x in bi]

    def answer(self,n,ID):
        if n == 1:
            return [True]
        if n == 2:
            return [True,False]
        num=min(np.random.exponential(scale=np.sqrt(np.power(2,n))),np.power(2,n)-1)
        bi=list(np.binary_repr(int(num),width=n))
        return [x=='0' for x in bi]

    def update(self,rankList,ownExam,otherExams):
        self.rank = rankList[self.ID];
        if len(otherExams) == 0:
            return
        correctCounts = [0 for i in otherExams[0][0]]
        for ourExam, response in otherExams:
            for i in range(len(response)):
                if ourExam[i] == response[i]:
                    correctCounts[i] += 1

        meanWhoAnsweredCorrectly = sum(correctCounts) / len(correctCounts)
        for i in range(len(correctCounts)):
            if correctCounts[i]+1 > meanWhoAnsweredCorrectly:
                self.weight = np.copysign(np.random.uniform(1,3),-self.weight)

1
N'échoue pas true, falsesi l'examen l'est false, true?
pppery

Les premières lignes answercontiennent des erreurs de syntaxe et de nom - trueet falsedevraient être Trueet False, et les ifs manquent :à la fin
Sara J

Merci à vous deux; Je n'avais pas configuré Python sur ma machine car je ne l'utilise pas souvent, donc je gâche régulièrement la syntaxe.
Draco18 ne font plus confiance au SE

newExam est défini mais jamais lu update. passest une commande NOP, vous pouvez la supprimer. (Le commentaire derrière c'est juste un jeu de mots pour l'ivrogne sur lequel vous avez copié.) De plus, vous utilisez implicitement les modules mathet randommais ne déclarez pas que vous les avez importés. Je l'ai réécrit dans mon dossier de concours avec np.copysignet np.random.uniformcela devrait faire la même chose.
AlienAtSystem

@AlienAtSystem devrait être corrigé maintenant.
Draco18s ne fait plus confiance au SE

0

Marx

Voici le robot Marx. Il pense qu'au lieu d'une bureaucratie, nous devrions avoir un système communiste. Pour aider à atteindre cet objectif, il donne des quiz plus difficiles aux bots de rang supérieur. Il donne également plus de réponses aléatoires aux quiz des bots plus élevés, car ils sont probablement plus intelligents, car ils sont plus élevés.

import numpy as np

class Marx():
    def __init__(self, ID, n):
        self.ID = ID
        self.n = n
        self.ranks = [] # The bot rankings
        self.e = [] # Our quiz
        self.rank = 0 # Our rank
    def ask(self, n, ID):
        test = [True] * n
        # Get the rank of the bot being quizzed
        if self.ranks:
            rank = self.ranks[ID]
        else:
            rank = 0
        for i in range(len(test)):
            item = test[i]
            if np.random.uniform(0, rank / self.n) > 0.5:
                # If the bot is higher ranking, make the quiz harder
                item = np.random.choice([True, False], 1)[0]
            test[i] = item
        # IF the test is not long enough, add Falses to the end
        while len(test) < n - 1:
            test.append(False)
        return test
    def answer(self, n, ID):
        # Get the rank of the asking bot
        if self.ranks:
            rank = self.ranks[ID]
        else:
            rank = 0
        if self.e:
            # Pad our quiz with Falses so it will not throw IndexError
            while len(self.e) < n:
                self.e.append(False)
            for i in range(len(self.e)):
                item = self.e[i]
                if np.random.uniform(0, rank / self.n) > 0.5:
                    # Assume that higher ranking bots are cleverer, so add more random answers
                    item = np.random.choice([True, False], 1)[0]
                self.e[i] = item
            if len(self.e) > self.rank + 1:
                self.e = self.e[:self.rank + 1]
            return self.e
        else:
            # If it is the first round, return all Trues
            return [True] * n
    def update(self, rankList, ownExam, otherExams):
        # Update our list of ranks
        self.ranks = rankList
        # Store the quiz we were given, to give to the next bot
        self.e = ownExam[0]
        # Store our rank
        self.rank = rankList[self.ID]

Marx répond actuellement à un octet de trop, donc il ne peut pas concourir en ce moment
AlienAtSystem

Que voulez-vous dire? Ses examens / réponses sont-ils trop longs?
sugarfi

Sa réponse est une entrée trop longue
AlienAtSystem

OK, j'ai corrigé ça. Ça devrait aller maintenant.
sugarfi

Désolé, je vous ai donné une mauvaise rétroaction: maintenant, les réponses sont un octet trop court. Le vrai problème est que vous étendez self.e quand il est trop court (mais pas assez pour le moment), mais ne le coupez pas lorsque Marx est rétrogradé.
AlienAtSystem
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.