Niebiańska biurokracja KoTH

14

W cesarskich Chinach o randze w społeczeństwie nie decydowały narodziny ani bogactwo, ale zdolność człowieka do przodowania w egzaminach cesarskich. Nefrytowy Cesarz, boski władca Niebios, wezwał do zbadania wszystkich jego poddanych w celu ustalenia ich wartości i do przekazania Boskiego mandatu do rządzenia Chinami.

Zasady biurokracji:

  • Boska Biurokracja składa się z nieujemnych szeregów o wartości całkowitej, zaczynających się od 0. Każdy członek (bot) biurokracji należy do jednej rangi. Każda ranga może posiadać dowolną liczbę członków, ale nie może być pusta, chyba że wszystkie rangi powyżej są puste
  • Na początku gry wszyscy członkowie mają rangę 0
  • Na każdym kroku każdy członek biurokracji musi odpowiadać na egzamin. Egzamin polega na prawidłowym odgadnięciu wartości boolowskich listy. Długość listy to numer rangi nad członkiem.
  • Pytania egzaminacyjne są przygotowywane przez losowego członka powyższej rangi. Członkowie najwyższej rangi otrzymują pytania bezpośrednio z JadeEmperor(patrz poniżej)
  • Członek, który uzyska co najmniej 50% na egzaminie, kwalifikuje się do promocji. Członek, który uzyska mniej niż 50% na egzaminie, kwalifikuje się do obniżenia.
  • W przypadku członka kwalifikującego się do obniżenia ranga jego ranga została zmniejszona o jeden tylko, jeśli jego miejsce zajmuje członek kwalifikujący się do promocji na niższej pozycji.
  • Wszyscy członkowie kwalifikujący się do promocji mają zwiększoną rangę o jeden, o ile nie pozostawia to pustej rangi.
  • Jeśli nie wszyscy kwalifikujący się członkowie mogą zostać zdegradowani lub awansowani, pierwszeństwo mają osoby o najniższym poziomie (dla zdegradowania) lub. najwyższy wynik (za promocję). Więzy są zrywane losowo.
  • Ranga członka może się zmienić maksymalnie o 1 w każdej turze.

Zasady gry:

  • Każdemu botowi zostanie losowo przypisany identyfikator na początku gry, który nie zmieni się w trakcie gry. JadeEmperorMa ID-1, wszystkie inne mają kolejnych identyfikatorów nieujemne, od 0.
  • Wszystkie boty konkurują w tym samym czasie
  • Gra trwa 100 tur, a wynikiem bota jest jego średnia pozycja w tym czasie.
  • Całkowity wynik jest uzyskiwany przez uruchomienie 1000 gier i uśrednienie wyników.
  • Każdy bot to klasa Python 3 implementująca następujące cztery funkcje:
    • ask(self,n,ID), co sprawia, że ​​egzamin jest listzwracany przez boolean o długości n. ID to identyfikator bota, który musi odgadnąć tę listę. ask()może być wywoływany wiele razy podczas jednej rundy dla dowolnego bota, ale też wcale.
    • answer(self,n,ID), która jest próbą odpowiedzi na egzamin przez zwrócenie listliczby booleanów o długości n. ID to identyfikator bota, który ask()wygenerował egzamin. answer()nazywa się dokładnie raz na rundę dla każdego bota.
    • update(self,rankList,ownExam,otherExams)jest wywoływany, gdy kontroler wykona wszystkie za i przeciw. Jego argumentami są: Lista liczb całkowitych, zawierająca wszystkie rangi według identyfikatora wszystkich botów; krotka składająca się z dwóch list, najpierw pytań egzaminacyjnych, a następnie odpowiedzi udzielonych przez bota (na wypadek, gdyby zapomniał); następnie lista krotek, podobnie składająca się z par egzamin-odpowiedź, tym razem do wszystkich egzaminów, które wykonał bot.
    • __init__(self, ID, n) przekazuje botowi swój własny identyfikator i liczbę konkurujących botów.
  • Klasy mogą implementować inne funkcje do użytku prywatnego
  • Definiowanie dalszych zmiennych i wykorzystywanie ich do przechowywania danych o poprzednich egzaminach jest wyraźnie dozwolone.
  • Programowanie meta-efektów jest zabronione, co oznacza wszelkie próby bezpośredniego dostępu do kodu innych botów, kodu kontrolera, powodujące wyjątki lub podobne. To jest konkurs strategii na egzaminy, a nie hakowanie kodu.
  • Boty próbujące sobie nawzajem pomagać są wyraźnie dozwolone, o ile nie robią tego za pomocą meta-efektów, ale wyłącznie na podstawie przekazywanych informacji update()
  • Inne języki są dozwolone tylko w przypadku, gdy można je łatwo przekonwertować na Python 3.
  • Numer biblioteki zostanie zaimportowany jako np. Wersja to 1.6.5, co oznacza, że ​​używa starej losowej biblioteki. Jeśli masz Numpy 1.7, stare funkcje są dostępne w numpy.random.mtrandcelu przetestowania. Pamiętaj, aby usunąć mtrand w celu przesłania.
  • Jeśli bot spowoduje wyjątek podczas działania, zostanie zdyskwalifikowany. Wszelkie bot którego kod jest tak ukrywane, że to niemożliwe, aby powiedzieć, czy to generuje listę długości n, gdy ask()albo answer()nazywa zostanie zdyskwalifikowany także zapobiegawczo. Bot zmuszający mnie do głębokiego kopiowania wyników otrzymuje -1 na wynik.
  • Nazwy klas muszą być unikalne
  • Dozwolonych jest wiele botów na osobę, ale pobierana będzie tylko najnowsza wersja iteracyjnie zaktualizowanych botów.
  • Ponieważ wydaje się, że istnieje pewne zamieszanie dotyczące podobieństwa bota:
    • Jesteś niedozwolone aby umieścić kopię innego bota. To jedyna standardowa luka, która naprawdę ma zastosowanie w tym wyzwaniu.
    • Jesteś wolno mieć kod wspólny z innymi robotami, w tym boty innych ludzi.
    • Teraz nie wolno złożyć bot, który różni się od drugiego tylko trywialny zmiany strategii (podobnie jak zmiany w nasieniu do generowania zapytania) , chyba że można udowodnić, że liczba takich botów kopiowanie węgla jest minimalna wymagana sukces uchwalenie ich strategii (zwykle będą to dwa boty do współpracy).

Przykładowe boty:

JadeEmperorJest zawsze częścią gry, ale nie konkurować; służy jako generator do egzaminów botów najwyższej rangi. Jego egzaminy są losowe, ale nie jednolite, aby umożliwić inteligentnym botom awans.

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]

Drunkard produkuje egzaminy i odpowiedź całkowicie losowo. Będzie częścią gry.

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

Plagiarist tylko kopie poprzednich egzaminów. Będzie także częścią gry.

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]

Kod kontrolera dostępny tutaj . W celu przetestowania możesz umieścić własną klasę w pliku Contestants.py w tym samym folderze i zostaną one zaimportowane.

Chatroom można znaleźć tutaj .

Rozpoczęły się egzaminy!

Aktualny wynik, z większą precyzją (10000 przebiegów) dla października 20:

UczestnikAutorWynikAlfaSleafar9,669691GammaSleafar9,301362BetaSleafar9,164597WiQeLuFioletowy P.7,870821StudiousBotDignissimus - Spammy7,538537SantayanaSara J.7,095528Plagiator6,522047CountOracularIFcoltransG5,881175TomaszAlien @ System5,880041PrzeciwnieDraco18s5,529652Markssugarfi5,433808Pijak5,328178YinYangFioletowy P.5.102519WyrównywaczMnemoniczny4,820996TitForTatAnonimowy3,35801

Konkursy będą przeprowadzane przy każdym nowym wpisie w dającej się przewidzieć przyszłości.

AlienAtSystem
źródło
1
Kopie botów to standardowa luka, więc nie. Jeśli spróbujesz nadużyć wielu botów według reguły autora, przesyłając prawie, ale nie całkiem, kopie, usunę je.
AlienAtSystem
1
@AlienAtSystem Dlaczego pozwalasz botom sobie pomagać? Po prostu wydaje się, że trzeba poradzić sobie z większym chaosem i przypadkowością.
Don Thousand
2
Dlaczego argumenty konstruktora nie są argumentami ID, ninnych metod n, ID?
Purpurowy P
1
@DonThousand, ponieważ uważam, że zgodnie z podanymi ograniczeniami, jest dość wyczyn zrobić dwa boty, które A) z powodzeniem uścisk dłoni (zauważ, że Plagiarizer może przypadkowo zagrać w człowieka na środku) i B) następnie wprowadzić strategię, która niezawodnie pomaga temu botowi ale nikt inny nie powstanie.
AlienAtSystem
1
@ ktoś zalicza się w górę. Zaczynasz od 0 i pracujesz do wyższych liczb
AlienAtSystem

Odpowiedzi:

4

Santayana

Ci, którzy nie pamiętają przeszłości, skazani są na jej powtórzenie. Podejmujemy więc decyzje na podstawie tego, jak zachowali się inni w przeszłości, odpowiadając na podstawie odpowiedzi, której pytający zwykle oczekiwał od nas przy danym indeksie, i prosząc o odpowiedź, której udzielili nam najrzadziej przy danym indeksie .

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 = []
Sara J.
źródło
3

Staranny Bot

Ten bot studiuje do testów! Próbuje znaleźć wzorce w testach wydanych przez różne boty i działa zgodnie z nimi.

Z mojej strony, jak dotąd, ten bot przewyższa wszystkie inne boty, które mogłem pracować na moim komputerze, z wyjątkiem Alpha, Beta i Gamma (które zostały zaprogramowane do współpracy). Bot nie korzysta z faktu, że łączenie drużyn jest dozwolone, ponieważ czułem, że to było trochę jak oszukiwanie i trochę brudne. Patrząc na to, współpraca wydaje się dość skuteczna.

Bot próbuje rozpoznać, kiedy odpowiedzi na testy są losowe, a odpowiedź odpowiada, że ​​mam nadzieję, że średnio 50% na testach.

Bot próbuje także rozpoznać, kiedy bot po prostu przewrócił swoje odpowiedzi, aby zrzucić inne boty, które próbują przewidzieć ich zachowanie, jednak nie zaprogramowałem go specjalnie, aby działał w tej sprawie.

Kod opatrzony adnotacjami z kilkoma komentarzami, aby ułatwić czytanie

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]
Dignissimus - Spammy
źródło
Sądząc po naszej wydajności, masz najlepszy algorytm odpowiadania, a Wi Qe Lu ma najlepszy algorytm do zapytań. Proponuję, aby połączyć nasze boty w jednego bota, zwanego Xuézhě (chińskie określenie „scholar”), co przypadkowo brzmi jak „przełącznik”.
Purpurowy P
Zhakowałem go i przeprowadziłem testy na moim komputerze. Co ciekawe, wyprzedził Studious Bot, ale nie Wi Qe Lu.
Purple P
@PurpleP Haha! Brzmi bardzo interesująco, nie sądzę, że mam wystarczająco dużo czasu, aby ulepszyć mojego bota, ale możesz opublikować go tutaj jako przesłanie
Dignissimus - Spammy
3

Policz Oracular

Ten bot używa algorytmu, który uśrednia egzaminy wszystkich innych działających botów (biorąc pod uwagę okrągłą liczbę i jakąś straszną heurystykę) do decydowania, co każdy bot ustawi jako egzamin.
Hrabia pyta się o egzaminy za pomocą skrótu md5. Zarówno pytania, jak i odpowiedzi są zatem deterministyczne. Ignoruje większość danych wejściowych, pytając i odpowiadając dokładnie te same sekwencje booleanów, deszczu lub blasku, w tym przeciwko 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
IFcoltransG
źródło
Świetny pomysł w teorii, ale w swoim pierwszym konkursie hrabia Oracular wypadł gorzej niż YinYang, pomimo wysiłków na rzecz symulacji YinYang.
Purpurowy P
1
@PurpleP Tak, to nie jest bardzo dobre. Powodem jest to, że stara się wybrać strategię „ogólnie optymalną”, uśredniając razem wszystkie konkretne strategie. Na przykład nie używa strategii dostosowanej do pokonania YinYang, gdy napotka on YinYang. Nie używa nawet konkretnej strategii w Jade Emporer: po prostu dodaje strategię Jade Emporer do średniej. Będzie lepszy niż przypadkowy, ale nie za dużo.
IFcoltransG,
Marks został naprawiony. Należy zaktualizować Count Oracular, aby to przewidzieć.
Purpurowy P
@PurpleP Marks powinien być teraz obsługiwany. Znowu jest jak w 1917 roku.
IFcoltransG
2

YinYang

Odpowiedzi na wszystkie Truelub wszystkie False, oprócz jednego losowo wybranego indeksu. Pyta o przeciwieństwo odpowiedzi. Zamienia losowo, aby zrzucić przeciwników.

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)

Odpowiedzi i pytania losowo w pierwszej rundzie. Następnie korzysta z odpowiedzi z poprzedniego egzaminu i zmienia pytanie, czy ponadprzeciętna liczba zawodników dobrze to zrozumiała.

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]
Fioletowy P.
źródło
5
Według Tłumacza Google „wi qe lu” jest z grubsza tłumaczone jako „Jestem drogą pingwina”.
Purple P,
2

Jeden mój bot:

Tomasz

Podróżnik z dalekiej krainy ma niebezpieczne pomysły na to, że wcześniejsze wyniki wskazują na przyszłe wyniki. Używa ich do powstrzymywania innych botów, chyba że stłumi to jego awans.

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]})
```
AlienAtSystem
źródło
Czy zapomniałeś wciąć swój kod po instrukcji klasy?
pppery
Po prostu zaskakuje mnie formatowanie SE. Naprawię to razem z tym, co spowodowało błąd w czyimś teście podczas korzystania z tego bota
AlienAtSystem
2

Alfa

Przeczytaj czat przed oddaniem głosu. Te boty nie naruszają żadnych zasad. PO zachęca nawet współpracujących botów.

Alpha tworzy zespół wraz z Beta. Obaj korzystają ze wstępnie zdefiniowanego zestawu egzaminów, aby pomóc sobie nawzajem awansować. Obaj również korzystają z botów używających ciągle tych samych egzaminów.

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]
Sleafar
źródło
Uważam, że te trzy boty naruszają zasady PO, jak podano zarówno w podpowiedzi, jak i w komentarzach.
Don Thousand
@DonThousand Jeśli przeczytasz dyskusję na czacie, zobaczysz, że nie naruszają zasad. chat.stackexchange.com/rooms/98905/imperial-exams-office
Sleafar
Słusznie. Mój błąd.
Don Thousand
@DonThousand Więc po co głosować za nimi wszystkimi?
Sleafar
Poprosiłem tylko Alphę. Nie mogę jednak nie docenić. Dokonaj zbędnej edycji, a ja to naprawię.
Don Thousand
1

Wyrównywacz

Wszyscy powinni być równi (bez tych głupich bzdur cesarza), więc zapewnij jak największą mobilność społeczną. Spraw, aby pytania były naprawdę łatwe (odpowiedź zawsze brzmi Prawda), aby ludzie mogli odnieść sukces.

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)

źródło
1

Beta

Przeczytaj czat przed oddaniem głosu. Te boty nie naruszają żadnych zasad. PO zachęca nawet współpracujących botów.

Beta tworzy zespół wraz z Alphą. Obaj korzystają ze wstępnie zdefiniowanego zestawu egzaminów, aby pomóc sobie nawzajem awansować. Obaj również korzystają z botów korzystających z tych samych egzaminów w kółko.

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]
Sleafar
źródło
1

Gamma

Przeczytaj czat przed oddaniem głosu. Te boty nie naruszają żadnych zasad. PO zachęca nawet współpracujących botów.

Gamma odkryła plany Alpha i Beta i stara się wykorzystać oba z nich, przebierając się za jednego z nich.

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]
Sleafar
źródło
1

TitForTat

Zadaje łatwe pytania, jeśli zadawałeś je w przeszłości. Jeśli nigdy nie poddałeś się egzaminowi, domyślnie są to proste pytania.

Ponadto nie ufa nikomu, kto zadaje trudne pytania, i udzieli im nieprzewidzianych odpowiedzi.

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

Ten bot działa dobrze, jeśli współpracują z nim inne boty. Obecnie współpracuje tylko Equalizer, ale mam nadzieję, że powinno to wystarczyć.

Anonimowy
źródło
W tej chwili bot nie może konkurować, ponieważ nie spełnia specyfikacji. Upewnij się, że zwraca listobiekty przez cały czas. Ponadto, zarówno według starych, jak i zaktualizowanych zasad, idealne kopie bota nie są prawidłowymi zgłoszeniami, więc dozwolona liczba instancji tego bota to 1.
AlienAtSystem
Zredagowałem go, aby zwrócić listy. Jeśli chodzi o perfekcyjne kopie, nie ma obecnego bota, który poprawnie z nim współpracuje, więc liczba botów kopiujących - minimum wymagane do pomyślnego wdrożenia strategii - wynosi co najmniej 1 (ten bot i 1 kopia są potrzebne ).
Anonimowy
Argumentujesz, że kwalifikujesz się do wyjątku na podstawie klauzuli 3, podczas próby przesłania czegoś, co podlega klauzuli 1: Doskonałe kopie bota nigdy nie są ważne, bez wyjątków. Aby zakwalifikować się do wyjątku z klauzuli 3, musisz udowodnić, że twoja strategia ściśle wymaga od wszystkich partnerów zareagowania na nią, jak na przykład sygnał uścisku dłoni, który jest rzeczywiście bezużyteczny bez kogoś słuchającego. Twoja nie. Equalizer przekaże ci egzaminy, aby uruchomić „przyjazną” klauzulę, odrzucając w ten sposób, że potrzebna jest kopia twojego bota.
AlienAtSystem
Dobrze więc. Dokonam kilku końcowych poprawek.
Anonimowy
0

Przeciwnie

Nefrytowy Cesarz ma zawsze rację, więc implementuje funkcję pytającą Nefrytowego Cesarza jako własną funkcję odpowiedzi, gdy potrzebuje więcej niż 2 odpowiedzi. Tylko na 1 odpowiedź odpowiada true(przyzwoite szanse na poprawność), a na 2 odpowiada true,false(ta odpowiedź „co najmniej połowa” pytań trzy z czterech możliwych quizów, lepiej niż losowo).

Używa podobnej logiki w swojej Aktualizacji w odniesieniu do tego, jak zmienia swój wzór pytania, ale jego logika pytania jest podobna do Nefrytowego Cesarza, tylko z inną wagą. Zmienia się między wyższymi wartościami truei wyższymi wartościami, falsegdy zbyt wielu kandydatów uzyska wystarczająco wysoką ocenę, aby przejść.

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)
Draco18s nie ufa już SE
źródło
1
Nie true, falsezawiedzie, jeśli egzamin jest false, true?
pppery
W pierwszych kilku wierszach answerwystępują błędy składniowe i nazwy - truei falsepowinny być Truei False, a na końcu ifbrakuje :s
Sara J
Dzięki wam dwoje; Nie miałem skonfigurowanego Pythona na moim komputerze, ponieważ nie używam go tak często, więc regularnie zmieniam składnię.
Draco18s nie ufa już SE
newExam jest ustawiony, ale nigdy nie wczytywany update. passto polecenie NOP, możesz je usunąć. (Komentarz, który za tym stoi, to tylko gra słów dla pijaka, którego skopiowałeś.) Ponadto domyślnie używasz modułów mathi random, ale nie zadeklarowałeś, że je zaimportowałeś. Ponownie napisałem to w pliku konkursowym za pomocą np.copysigninp.random.uniform to powinno zrobić to samo.
AlienAtSystem
@AlienAtSystem Powinien zostać teraz naprawiony.
Draco18s nie ufa już
0

Marks

To jest bot Marksa. Uważa, że ​​zamiast biurokracji powinniśmy mieć system komunistyczny. Aby osiągnąć ten cel, daje trudniejsze quizy botom o wyższym rankingu. Daje także więcej losowych odpowiedzi na quizy z wyższych botów, ponieważ są one prawdopodobnie mądrzejsze, ponieważ są wyżej.

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]
sugarfi
źródło
Marks obecnie odbiera bajt za dużo, więc nie może teraz konkurować
AlienAtSystem
Co masz na myśli? Czy jego egzaminy / odpowiedzi są zbyt długie?
sugarfi
Jego odpowiedź to jeden wpis za długi
AlienAtSystem
OK, naprawiłem to. Teraz powinno być dobrze.
sugarfi
Przepraszam, przekazałem wam błędne informacje zwrotne: teraz odpowiedzi są zbyt krótkie. Prawdziwy problem polega na tym, że przedłużasz siebie. E, gdy jest za krótki (choć w tej chwili za mało), ale nie przycinaj go, gdy Marks zostanie zdegradowany.
AlienAtSystem