La burocracia celestial KoTH


14

En la China imperial, los rangos de la sociedad no se decidían por nacimiento o riqueza, sino por la capacidad de una persona para sobresalir en los exámenes imperiales. El Emperador de Jade, el divino gobernante de los Cielos, ha pedido que todos sus súbditos sean examinados para determinar su valor, y a quién dar el Mandato Divino para gobernar China.

Reglas de la burocracia:

  • La Divina Burocracia consiste en rangos de valores enteros no negativos, comenzando con 0. Cada miembro (bot) de la burocracia pertenece a un rango. Cada rango puede contener muchos miembros arbitrarios, pero no puede estar vacío a menos que todos los rangos anteriores estén vacíos
  • Al comienzo del juego, todos los miembros tienen rango 0
  • Cada turno, cada miembro de la burocracia tiene que responder a un examen. El examen consiste en adivinar correctamente los valores booleanos de una lista. La longitud de la lista es el número del rango por encima del miembro.
  • Las preguntas del examen son preparadas por un miembro aleatorio del rango anterior. Los miembros del rango más alto obtienen sus preguntas directamente del JadeEmperor(ver más abajo)
  • Un miembro que obtenga al menos un 50% en su examen es elegible para la promoción. Un miembro que obtenga menos del 50% en su examen es elegible para Demotion.
  • A un miembro elegible para Demotion se le reduce su rango en uno solo si hay un miembro elegible para Promoción en el rango siguiente para tomar su lugar.
  • Todos los miembros elegibles para la promoción tienen su rango aumentado en uno siempre que esto no deje ningún rango vacío.
  • Si no todos los miembros elegibles pueden ser degradados o promocionados, la preferencia se aplicará a los de menor (para Demotion) resp. puntuación más alta (para promoción). Los lazos se rompen al azar.
  • El rango de un miembro solo puede cambiar como máximo 1 por turno.

Reglas del juego:

  • A cada bot se le asignará aleatoriamente una ID al comienzo del juego, que no cambiará a lo largo de su curso. El JadeEmperortiene el ID -1, todos los demás tienen IDs no negativos consecutivos, comenzando con 0.
  • Todos los bots compiten al mismo tiempo.
  • El juego dura 100 turnos, el puntaje del bot es su rango promedio poseído durante ese tiempo.
  • La puntuación total se adquiere al ejecutar 1000 juegos y promediar los resultados.
  • Cada Bot es una clase de Python 3 que implementa las siguientes cuatro funciones:
    • ask(self,n,ID), que realiza un examen devolviendo un listbooleano de longitud n. ID es la ID del bot que tiene que adivinar esa lista. ask()se puede llamar muchas veces durante una sola ronda para cualquier bot, pero tampoco en absoluto.
    • answer(self,n,ID), que es un intento de responder a un examen devolviendo un listbooleano de longitud n. ID es la ID del bot que ask()generó el examen. answer()se llama exactamente una vez por ronda para cada bot.
    • update(self,rankList,ownExam,otherExams)se llama una vez que el controlador ha realizado todos los Pro y Demotions. Sus argumentos son: Una lista de enteros, que enumera todos los rangos por ID de todos los bots; una tupla, que consta de dos listas, primero las preguntas del examen, luego las respuestas que dio el bot (en caso de que se haya olvidado); luego una lista de tuplas, que consiste de manera similar en pares examen-respuesta, esta vez para todos los exámenes que el bot entregó.
    • __init__(self, ID, n) le pasa al bot su propia identificación y el número de bots competidores.
  • Las clases pueden implementar otras funciones para uso privado.
  • Se permite explícitamente definir más variables y usarlas para almacenar datos sobre exámenes pasados.
  • Los metaefectos de programación están prohibidos, lo que significa que cualquier intento de acceder directamente al código de otros bots, el código del controlador, causando excepciones o similares. Este es un concurso de estrategias para los exámenes, no de pirateo de códigos.
  • Los bots que intentan ayudarse entre sí están explícitamente permitidos, siempre que no lo hagan a través de metaefectos, sino únicamente por la información transmitida update()
  • Solo se permiten otros idiomas en caso de que se puedan convertir fácilmente a Python 3.
  • La biblioteca numpy se importará como np. La versión es 1.6.5, lo que significa que usa la antigua biblioteca aleatoria. Si tiene Numpy 1.7, las funciones antiguas están disponibles numpy.random.mtrandpara probar. Por favor, recuerde quitar la mtrand para enviarla.
  • Si un bot causa una excepción durante el tiempo de ejecución, se descalifica. Cualquier robot cuyo código es tan ofuscado que es imposible decir si se genera una lista de longitud n cuando ask()o answer()se llama también será descalificado de forma preventiva. Un bot que me obliga a copiar en profundidad obtiene -1 en la puntuación.
  • Los nombres de clase deben ser únicos
  • Se permiten varios bots por persona, pero solo se tomará la última versión de los bots actualizados de forma iterativa.
  • Dado que parece haber cierta confusión sobre la similitud de bot:
    • No está permitido publicar una copia de otro bot. Esta es la única escapatoria estándar que realmente se aplica en este desafío.
    • Se le permite compartir código con otros bots, incluidos los bots de otras personas.
    • No está permitido enviar un bot que difiera de otro solo por un cambio trivial a la estrategia (como un cambio en la semilla para la generación de preguntas) a menos que pueda probar que el número de tales bots de copia de carbón es el mínimo requerido para tener éxito promulgación de su estrategia (generalmente serán dos bots para una cooperación).

Bots de ejemplo:

El JadeEmperorsiempre es parte del juego, pero no compite; Él sirve como generador para los exámenes de los robots de mayor rango. Sus exámenes son aleatorios, pero no de manera uniforme, para permitir a los robots inteligentes una forma de avanzar.

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]

El borracho produce exámenes y respuestas completamente al azar. Él será parte del juego.

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

El plagio solo copia los exámenes anteriores. Él también será parte del juego.

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]

Código de controlador disponible aquí . Para las pruebas, puede colocar su propia clase en un archivo Contestants.py en la misma carpeta, y se importarán.

Sala de chat se puede encontrar aquí .

Los exámenes comienzan!

Puntuación actual, en mayor precisión (10000 carreras) para Oct20:

EntranteAutorPuntuaciónAlfaSleafar9.669691GamaSleafar9.301362BetaSleafar9.164597WiQeLuP púrpura7.870821StudiousBotDignissimus - Spammy7.538537SantayanaSara J7.095528Plagiario6.522047CountOracularIFcoltransG5.881175ThomasAlien @ System5.880041ContrarioDraco18s5.529652MarxSugarfi5.433808Borracho5.328178YinYangP púrpura5.102519IgualadaMnemotécnico4.820996TitForTatAnónimo3.35801

Los concursos se ejecutarán con cada nueva entrada en el futuro previsible.


1
Las copias de los bots son una escapatoria estándar, por lo que no. Si intentas abusar de la regla de múltiples bots por autor enviando copias casi pero no del todo, lo eliminaré.
AlienAtSystem

1
@AlienAtSystem ¿Por qué permiten que los bots se ayuden unos a otros? Parece que hay más caos y aleatoriedad con los que lidiar.
Don Thousand

2
¿Por qué los argumentos del constructor son los argumentos ID, ndel otro método n, ID?
Purple P

1
@DonThousand porque creo que, bajo las restricciones dadas, es una gran hazaña hacer dos bots que A) aprieten con la mano con éxito (tenga en cuenta que Plagiarizer podría jugar accidentalmente al hombre en el medio) y B) luego promulgar una estrategia que ayude de manera confiable a ese bot pero no otro para levantarse.
AlienAtSystem

1
@algunos rangos cuentan hacia arriba. Comienzas en 0 y
avanzas

Respuestas:


4

Santayana

Los que no pueden recordar el pasado están condenados a repetirlo. Por lo tanto, tomamos nuestras decisiones en función de cómo los demás han actuado en el pasado, respondiendo en función de la respuesta que el autor de la pregunta generalmente esperaba de nosotros en un índice dado, y pidiendo la respuesta que nos dieron con menos frecuencia en un índice dado .

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

Bot estudioso

¡Este bot estudia para las pruebas! Intenta encontrar patrones en las pruebas realizadas por varios bots y actúa de manera acorde.

Por mi parte, hasta ahora, este robot supera a todos los demás robots que pude trabajar en mi computadora, excepto Alpha, Beta y Gamma (que han sido programados para trabajar juntos). El bot no hace uso del hecho de que el trabajo en equipo está permitido porque sentí que era un poco como hacer trampa y un poco sucio. Sin embargo, al analizarlo, la formación de equipos parece ser bastante efectiva.

El bot intenta reconocer cuándo las respuestas a las pruebas son aleatorias y, en respuesta, coincide con un promedio de 50% en las pruebas.

El bot también intenta reconocer cuándo un bot simplemente ha cambiado sus respuestas para rechazar a otros bots que se esfuerzan por predecir su comportamiento, sin embargo, no lo he programado para que actúe específicamente sobre esto todavía.

He anotado el código con algunos comentarios para que sea más fácil de leer.

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]

A juzgar por nuestro rendimiento, tiene el mejor algoritmo para responder y Wi Qe Lu tiene el mejor algoritmo para preguntar. Propongo que combinemos nuestros bots en un solo bot, llamado Xuézhě (chino para "erudito"), que casualmente suena como un "conmutador".
Purple P

Lo pirateé y ejecuté los exámenes en mi máquina. Curiosamente, superó a Studious Bot, pero no a Wi Qe Lu.
Purple P

@PurpleP Jaja! Eso suena muy interesante, no creo que haya tiempo suficiente para mejorar mi bot, pero puedes publicarlo como una presentación aquí
Dignissimus - Spammy

3

Conde Oracular

Este bot utiliza un algoritmo que promedia los exámenes de todos los demás bots en funcionamiento (dado el número de ronda y algunas heurísticas terribles) para decidir qué establecerá cada otro bot como examen.
El conde pide sus exámenes usando un hash md5. Tanto sus preguntas como sus respuestas son, por lo tanto, deterministas. Ignora la mayoría de las entradas, pregunta y responde exactamente las mismas secuencias de booleanos, lluvia o sol, incluso contra 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

Una gran idea en teoría, pero en su primer concurso, el Conde Oracular se desempeñó peor que YinYang, a pesar de sus esfuerzos para simular YinYang.
Púrpura

1
@PurpleP Sí, no es muy bueno. La razón es que trata de elegir una estrategia "generalmente óptima" promediando todas las estrategias específicas juntas. Por ejemplo, no utiliza una estrategia diseñada para vencer a YinYang cuando se encuentra con YinYang. Ni siquiera utiliza una estrategia específica en Jade Emporer: solo agrega la estrategia de Jade Emporer al promedio. Será mejor que al azar, pero no por mucho.
IFcoltransG

Marx ha sido reparado. Debe actualizar Count Oracular para predecirlo.
Púrpura

@PurpleP Marx debería ser compatible ahora. Es como si fuera 1917 otra vez.
IFcoltransG

2

YinYang

Responde todos Trueo todos False, excepto un índice elegido al azar para ser el opuesto. Pide lo contrario de lo que responde. Cambia al azar para arrojar a los oponentes.

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)

Responde y pregunta al azar en la primera ronda. Luego, usa las respuestas del examen anterior y cambia una pregunta si un número de competidores por encima del promedio acertó.

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]

55
Según el traductor de Google "wi qe lu" se traduce aproximadamente como "Soy el camino del pingüino".
Purple P

2

Un bot propio:

Thomas

Un viajero de una tierra lejana, tiene algunas ideas peligrosas sobre resultados pasados ​​que son indicativos de rendimiento futuro. Los usa para mantener a otros bots abajo, a menos que eso sofoque su propio avance.

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

¿Olvidó sangrar su código después de la declaración de clase?
pppery

Ese es solo el formato SE que me sorprende. Lo arreglaré junto con lo que haya causado un error en la prueba de alguien al usar este bot
AlienAtSystem

2

Alfa

Lea el chat antes de votar abajo. Estos bots no violan ninguna regla. El OP incluso está alentando a los bots cooperantes.

Alpha está formando un equipo junto con Beta. Ambos están utilizando un conjunto predefinido de exámenes para ayudarse mutuamente a ascender de rango. Además, ambos aprovechan los bots que utilizan los mismos exámenes una y otra vez.

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]

Creo que estos tres bots violan las reglas de los OP como se indica tanto en el aviso como en los comentarios.
Don Thousand

@DonThousand Si lees la discusión en el chat, verás que no violan las reglas. chat.stackexchange.com/rooms/98905/imperial-exams-office
Sleafar

Lo suficientemente justo. Culpa mía.
Don Thousand

@DonThousand Entonces, ¿cuál era el punto de rechazarlos a todos?
Sleafar

Solo rechacé a Alpha. Sin embargo, no puedo cancelar la votación. Haz una edición superflua y la arreglaré.
Don Thousand

1

Igualada

Todos deberían ser iguales (sin ninguna de estas tonterías de emperador tonto), por lo tanto, proporcione tanta movilidad social como sea posible. Haga las preguntas realmente fáciles (la respuesta siempre es verdadera) para que las personas puedan tener éxito.

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

Beta

Lea el chat antes de votar abajo. Estos bots no violan ninguna regla. El OP incluso está alentando a los bots cooperantes.

Beta está formando un equipo junto con Alpha. Ambos están utilizando un conjunto predefinido de exámenes para ayudarse mutuamente a ascender de rango. Además, ambos aprovechan los bots que utilizan los mismos exámenes una y otra vez.

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

Gama

Lea el chat antes de votar abajo. Estos bots no violan ninguna regla. El OP incluso está alentando a los bots cooperantes.

Gamma ha descubierto los planes de Alfa y Beta y está tratando de aprovecharse de ambos disfrazándose como uno de ellos.

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

Le hace preguntas fáciles si le hizo preguntas fáciles en el pasado. Si nunca le ha hecho un examen, el valor predeterminado es preguntas fáciles.

Además, no confía en nadie que haga preguntas difíciles y les dará respuestas impredecibles.

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

Este bot funciona bien si otros bots cooperan con él. Actualmente, solo Equalizer coopera, pero es de esperar que esto sea suficiente.


Por el momento, el bot no puede competir porque no sigue las especificaciones. Asegúrese de que devuelva listobjetos en todo momento. Además, según las reglas antiguas y actualizadas, las copias perfectas de un bot no son envíos válidos, por lo que el número permitido de instancias de este bot en ejecución es 1.
AlienAtSystem

Lo edité para devolver listas. En cuanto a las copias perfectas, no hay un bot actual que coopere adecuadamente con él, por lo que la cantidad de bots de copia al carbón, el mínimo requerido para la implementación exitosa de la estrategia, es al menos 1 (este bot y 1 copia son necesarios )
Anónimo

Estás argumentando que calificas para una excepción bajo la cláusula 3 mientras intentas enviar algo que cae bajo la cláusula 1: las copias perfectas de un bot nunca son válidas, sin excepciones. Y para calificar para la excepción de la cláusula 3, debe probar que su estrategia requiere estrictamente que todos estos socios reaccionen a ella, como por ejemplo una señal de apretón de manos, que de hecho es inútil sin que alguien escuche. El tuyo no. Equalizer le entregará los exámenes para activar la cláusula "amigable", refutando así que se necesita una copia de su bot.
AlienAtSystem

Bien entonces. Haré algunos ajustes finales.
Anónimo el

0

Contrario

El Emperador de Jade siempre tiene la razón, por lo que implementa la función de preguntas del Emperador de Jade como su propia función de respuesta cuando necesita más de 2 respuestas. Por solo 1 respuesta responde true(probabilidades decentes de ser correctas) y por 2 respondetrue,false (esta respuesta pasa "al menos la mitad" de las preguntas tres de cuatro posibles cuestionarios, mejor que elegir al azar).

Utiliza una lógica similar en su Actualización con respecto a cómo altera su patrón de preguntas, pero su lógica de preguntas es similar a la del Emperador de Jade, pero con un peso diferente. Fluctúa entre valores más altos de truecon valores más altos de falsecuando demasiados candidatos obtienen una puntuación lo suficientemente alta como para aprobar.

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
No true, falsefalla si el examen es false, true?
pppery

Las primeras líneas en answertienen errores de sintaxis y de nombre, truey falsedeberían ser Truey False, y iffaltan las :s al final
Sara J

Gracias a los dos; No tenía Python configurado en mi máquina ya que no lo uso con tanta frecuencia, así que estropeo la sintaxis regularmente.
Draco18s ya no confía en SE

newExam está configurado pero nunca leído update. passes un comando NOP, puedes eliminarlo. (El comentario detrás es solo un juego de palabras para el borracho que copiaste). Además, estás usando implícitamente módulos mathy randomno declaraste que los importaste. Lo he reescrito en mi archivo de concurso con np.copysigny np.random.uniformeso debería hacer lo mismo.
AlienAtSystem

@AlienAtSystem Debería arreglarse ahora.
Draco18s ya no confía en SE

0

Marx

Este es el bot de Marx. Él cree que, en lugar de una burocracia, deberíamos tener un sistema comunista. Para ayudar a alcanzar este objetivo, ofrece pruebas más difíciles a los bots de mayor rango. También da más respuestas aleatorias a los cuestionarios de bots superiores, porque probablemente sean más inteligentes, porque están más arriba.

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 actualmente responde demasiados bytes, por lo que no puede competir en este momento
AlienAtSystem

¿Qué quieres decir? ¿Son sus exámenes / respuestas demasiado largos?
sugarfi

Su respuesta es una entrada demasiado larga
AlienAtSystem

OK, lo arreglé. Debería estar bien ahora.
sugarfi

Lo siento, te di una respuesta incorrecta: ahora, las respuestas son un byte demasiado corto. El verdadero problema es que te extiendes solo cuando es demasiado corto (aunque no lo suficiente en este momento), pero no lo recortes cuando Marx sea degradado.
AlienAtSystem
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.