Trilemma del prigioniero iterato


19

STATO DELLE SFIDE: APERTO

Commenta, apri un PR o urlami altrimenti se mi manca il tuo bot.


Il dilemma del prigioniero ... con tre scelte. Pazzo, eh?

Ecco la nostra matrice di payoff. Giocatore A a sinistra, B in alto

A,B| C | N | D
---|---|---|---
 C |3,3|4,1|0,5
 N |1,4|2,2|3,2
 D |5,0|2,3|1,1

La matrice di payoff è progettata in modo che sia meglio per entrambi i giocatori cooperare sempre, ma puoi guadagnare (di solito) scegliendo Neutral o Defection.

Ecco alcuni esempi di robot (concorrenti).

# turns out if you don't actually have to implement __init__(). TIL!

class AllC:
    def round(self, _): return "C"
class AllN:
    def round(self, _): return "N"
class AllD:
    def round(self, _): return "D"
class RandomBot:
    def round(self, _): return random.choice(["C", "N", "D"])

# Actually using an identically-behaving "FastGrudger".
class Grudger:
    def __init__(self):
        self.history = []
    def round(self, last):
        if(last):
            self.history.append(last)
            if(self.history.count("D") > 0):
                return "D"
        return "C"

class TitForTat:
    def round(self, last):
        if(last == "D"):
            return "D"
        return "C"

Il tuo bot è una classe Python3. Viene creata una nuova istanza per ogni gioco e round()viene chiamata per ogni round, con la scelta del tuo avversario dall'ultimo round (o Nessuno, se è il primo round)

C'è un premio di 50 ripetizioni per il vincitore in circa un mese.

specifiche

  • Ogni bot gioca ogni altro bot (1v1), incluso se stesso, in round [REDATTO].
  • Scappatoie standard non consentite.
  • Non si scherza con nulla al di fuori della tua classe o con altri shenanigain subdoli.
  • Puoi inviare fino a cinque robot.
  • Sì, puoi implementare la stretta di mano.
  • Qualsiasi risposta diversa da C, No Dverrà presa silenziosamente come N.
  • I punti di ciascun bot di ogni gioco a cui partecipano verranno sommati e confrontati.

controllore

Dai un'occhiata!

Altre lingue

Metterò insieme un'API se qualcuno ne ha bisogno.

Punteggi: 27-11-2018

27 bots, 729 games.

name            | avg. score/round
----------------|-------------------
PatternFinder   | 3.152
DirichletDice2  | 3.019
EvaluaterBot    | 2.971
Ensemble        | 2.800
DirichletDice   | 2.763
Shifting        | 2.737
FastGrudger     | 2.632
Nash2           | 2.574
HistoricAverage | 2.552
LastOptimalBot  | 2.532
Number6         | 2.531
HandshakeBot    | 2.458
OldTitForTat    | 2.411
WeightedAverage | 2.403
TitForTat       | 2.328
AllD            | 2.272
Tetragram       | 2.256
Nash            | 2.193
Jade            | 2.186
Useless         | 2.140
RandomBot       | 2.018
CopyCat         | 1.902
TatForTit       | 1.891
NeverCOOP       | 1.710
AllC            | 1.565
AllN            | 1.446
Kevin           | 1.322

1
Come vengono messi i robot uno contro l'altro? Dal Grudger ottengo che ci sono sempre due robot l'uno contro l'altro e che l'ultima scelta del nemico è passata al robot. Quanti round vengono giocati? E per una partita: conta solo il risultato (ovvero chi ha vinto) o anche i punti?
Black Owl Kai,

1
Otterresti più voci se rendessi questa lingua indipendente, o almeno più ampia. Potresti avere una classe python wrapper che genera un processo e invia comandi di testo per ottenere risposte di testo.
Sparr,

1
Fatto. Questo è stato sulla sandbox per circa un mese!
SIGSTACKFAULT

2
Se avvolgi la maggior parte di main.py while len(botlist) > 1:con botlist.remove(lowest_scoring_bot)in fondo al ciclo, otterrai un torneo di eliminazione con risultati interessanti.
Sparr,

1
Un'altra versione di questo giorno potrebbe passare l'intera cronologia delle interazioni piuttosto che solo l'ultima mossa. Non cambia molto anche se semplifica leggermente il codice utente. Ma consentirebbe estensioni, come canali di comunicazione rumorosi che chiariscono nel tempo: "Davvero, una D, anche se ho detto C quattro volte di seguito? No, non ho detto D; cosa mi porti per? Oh, scusa, possiamo semplicemente dimenticare quel round? "
Scott Sauyet,

Risposte:


10

EvaluaterBot

class EvaluaterBot:
    def __init__(self):
        self.c2i = {"C":0, "N":1, "D":2}
        self.i2c = {0:"C", 1:"N", 2:"D"}
        self.history = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
        self.last = [None, None]

    def round(self, last):
        if self.last[0] == None:
            ret = 2
        else:
            # Input the latest enemy action (the reaction to my action 2 rounds ago)
            # into the history
            self.history[self.last[0]][self.c2i[last]] += 1
            # The enemy will react to the last action I did
            prediction,_ = max(enumerate(self.history[self.last[1]]), key=lambda l:l[1])
            ret = (prediction - 1) % 3
        self.last = [self.last[1], ret]
        return self.i2c[ret]

Vince contro tutti i robot precedentemente inviati tranne (forse) il robot casuale (ma potrebbe avere un vantaggio, perché sceglie D in un pareggio e D dovrebbe essere ottimale) e gioca un pareggio costante contro se stesso.


Sì, batte tutto.
SIGSTACKFAULT

Grattalo, PatternFinder lo batte un po '.
SIGSTACKFAULT

7

Equilibrio di Nash

Questo bot ha frequentato una lezione di teoria dei giochi al college, ma era pigro e non è andato alla classe in cui ha coperto i giochi ripetuti. Quindi gioca solo un equilibrio di nash misto a partita singola. Risulta 1/5 2/5 2/5 è il NE misto per i payoff.

class NashEquilibrium:
    def round(self, _):
        a = random.random()
        if a <= 0.2:
            return "C"
        elif a <= 0.6:
            return "N"
        else:
            return "D" 

Equilibrio costante di Nash

Questo robot ha preso una lezione o due da suo fratello pigro. Il problema di suo fratello pigro era che non sfruttava strategie fisse. Questa versione controlla se l'avversario è un giocatore o titfortat costante e gioca di conseguenza, altrimenti gioca il normale equilibrio di nash.

L'unico inconveniente è che ha una media di 2,2 punti per turno giocando contro se stesso.

class NashEquilibrium2:

    def __init__(self):
        self.opphistory = [None, None, None]
        self.titfortatcounter = 0
        self.titfortatflag = 0
        self.mylast = "C"
        self.constantflag = 0
        self.myret = "C"

    def round(self, last):
        self.opphistory.pop(0)
        self.opphistory.append(last)

        # check if its a constant bot, if so exploit
        if self.opphistory.count(self.opphistory[0]) == 3:
            self.constantflag = 1
            if last == "C":
                 self.myret = "D"
            elif last == "N":
                 self.myret = "C"
            elif last == "D":
                 self.myret = "N"

        # check if its a titfortat bot, if so exploit
        # give it 2 chances to see if its titfortat as it might happen randomly
        if self.mylast == "D" and last == "D":
            self.titfortatcounter = self.titfortatcounter + 1

        if self.mylast == "D" and last!= "D":
            self.titfortatcounter = 0

        if self.titfortatcounter >= 3:
            self.titfortatflag = 1

        if self.titfortatflag == 1:
            if last == "C":
                 self.myret = "D"
            elif last == "D":
                 self.myret = "N"    
            elif last == "N":
                # tit for tat doesn't return N, we made a mistake somewhere
                 self.titfortatflag = 0
                 self.titfortatcounter = 0

        # else play the single game nash equilibrium
        if self.constantflag == 0 and self.titfortatflag == 0:
            a = random.random()
            if a <= 0.2:
                self.myret = "C"
            elif a <= 0.6:
                self.myret = "N"
            else:
                self.myret = "D"


        self.mylast = self.myret
        return self.myret

1
NashEquilibrium.round deve prendere argomenti anche se non li utilizza, in modo da adattarsi al prototipo della funzione prevista.
Ray

Grazie risolto
Ofya il

Poco più corto:class NashEquilibrium: def round(self, _): a = random.random() for k, v in [(0.2, "C"), (0.6, "N"), (1, "D")]: if a <= k: return v
Robert Grant,

7

TatForTit

class TatForTit:
    def round(self, last):
        if(last == "C"):
            return "N"
        return "D"

Questo bot alternerà la selezione di DNDN mentre TitForTat alterna CDCD, per un guadagno netto medio di 3 punti per round se ho letto correttamente la matrice di pagamento. Penso che questo potrebbe essere ottimale contro TitForTat. Ovviamente potrebbe essere migliorato per rilevare un avversario non TFT e adottare altre strategie, ma stavo solo mirando alla taglia originale.


6

PatternFinder

class PatternFinder:
    def __init__(self):
        import collections
        self.size = 10
        self.moves = [None]
        self.other = []
        self.patterns = collections.defaultdict(list)
        self.counter_moves = {"C":"D", "N":"C", "D":"N"}
        self.initial_move = "D"
        self.pattern_length_exponent = 1
        self.pattern_age_exponent = 1
        self.debug = False
    def round(self, last):
        self.other.append(last)
        best_pattern_match = None
        best_pattern_score = None
        best_pattern_response = None
        self.debug and print("match so far:",tuple(zip(self.moves,self.other)))
        for turn in range(max(0,len(self.moves)-self.size),len(self.moves)):
            # record patterns ending with the move that just happened
            pattern_full = tuple(zip(self.moves[turn:],self.other[turn:]))
            if len(pattern_full) > 1:
                pattern_trunc = pattern_full[:-1]
                pattern_trunc_result = pattern_full[-1][1]
                self.patterns[pattern_trunc].append([pattern_trunc_result,len(self.moves)-1])
            if pattern_full in self.patterns:
                # we've seen this pattern at least once before
                self.debug and print("I've seen",pattern_full,"before:",self.patterns[pattern_full])
                for [response,turn_num] in self.patterns[pattern_full]:
                    score = len(pattern_full) ** self.pattern_length_exponent / (len(self.moves) - turn_num) ** self.pattern_age_exponent
                    if best_pattern_score == None or score > best_pattern_score:
                        best_pattern_match = pattern_full
                        best_pattern_score = score
                        best_pattern_response = response
                    # this could be much smarter about aggregating previous responses
        if best_pattern_response:
            move = self.counter_moves[best_pattern_response]
        else:
            # fall back to playing nice
            move = "C"
        self.moves.append(move)
        self.debug and print("I choose",move)
        return move

Questo bot cerca le occorrenze precedenti dello stato di gioco recente per vedere come l'avversario ha risposto a tali occorrenze, con una preferenza per le partite più lunghe e le partite più recenti, quindi gioca la mossa che "batterà" la mossa prevista dell'avversario. C'è molto spazio perché sia ​​più intelligente con tutti i dati di cui tiene traccia, ma ho finito il tempo per lavorarci su.


Quando hai tempo, ti dispiace darle un passaggio di ottimizzazione? È facilmente la più grande perdita di tempo.
SIGSTACKFAULT

2
@Blacksilver Ho appena ridotto la lunghezza massima del motivo da 100 a 10. Ora dovrebbe funzionare quasi istantaneamente se stai eseguendo <200 round
Sparr

1
Forse usare un numero altamente composito (cioè 12) avrebbe un punteggio migliore?
SIGSTACKFAULT

5

Giada

class Jade:
    def __init__(self):
        self.dRate = 0.001
        self.nRate = 0.003

    def round(self, last):
        if last == 'D':
            self.dRate *= 1.1
            self.nRate *= 1.2
        elif last == 'N':
            self.dRate *= 1.03
            self.nRate *= 1.05
        self.dRate = min(self.dRate, 1)
        self.nRate = min(self.nRate, 1)

        x = random.random()
        if x > (1 - self.dRate):
            return 'D'
        elif x > (1 - self.nRate):
            return 'N'
        else:
            return 'C'

Inizia ottimista, ma diventa progressivamente più amaro quando l'avversario rifiuta di cooperare. Molte costanti magiche che potrebbero essere modificate, ma questo probabilmente non farà abbastanza bene da giustificare il tempo.


5

insieme

Questo gestisce un insieme di modelli correlati. I singoli modelli considerano diverse quantità di cronologia e hanno la possibilità di scegliere sempre la mossa che ottimizzerà la differenza di pagamento prevista o di selezionare casualmente una mossa in proporzione alla differenza di pagamento prevista.

Ogni membro dell'ensemble quindi vota la sua mossa preferita. Ottengono un numero di voti pari a quanto hanno vinto di più rispetto all'avversario (il che significa che i modelli terribili otterranno voti negativi). Qualunque mossa vince il voto viene quindi selezionato.

(Probabilmente dovrebbero dividere i loro voti tra le mosse in proporzione a quanto favoriscono ciascuna, ma non mi interessa abbastanza farlo adesso.)

Batte tutto quanto pubblicato finora tranne EvaluaterBot e PatternFinder. (Uno contro uno, batte EvaluaterBot e perde a PatternFinder).

from collections import defaultdict
import random
class Number6:
    class Choices:
        def __init__(self, C = 0, N = 0, D = 0):
            self.C = C
            self.N = N
            self.D = D

    def __init__(self, strategy = "maxExpected", markov_order = 3):
        self.MARKOV_ORDER = markov_order;
        self.my_choices = "" 
        self.opponent = defaultdict(lambda: self.Choices())
        self.choice = None # previous choice
        self.payoff = {
            "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
            "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
            "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
        }
        self.total_payoff = 0

        # if random, will choose in proportion to payoff.
        # otherwise, will always choose argmax
        self.strategy = strategy
        # maxExpected: maximize expected relative payoff
        # random: like maxExpected, but it chooses in proportion to E[payoff]
        # argmax: always choose the option that is optimal for expected opponent choice

    def update_opponent_model(self, last):
        for i in range(0, self.MARKOV_ORDER):
            hist = self.my_choices[i:]
            self.opponent[hist].C += ("C" == last)
            self.opponent[hist].N += ("N" == last)
            self.opponent[hist].D += ("D" == last)

    def normalize(self, counts):
        sum = float(counts.C + counts.N + counts.D)
        if 0 == sum:
            return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
        return self.Choices(
            counts.C / sum, counts.N / sum, counts.D / sum)

    def get_distribution(self):
        for i in range(0, self.MARKOV_ORDER):
            hist = self.my_choices[i:]
            #print "check hist = " + hist
            if hist in self.opponent:
                return self.normalize(self.opponent[hist])

        return self.Choices(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)

    def choose(self, dist):
        payoff = self.Choices()
        # We're interested in *beating the opponent*, not
        # maximizing our score, so we optimize the difference
        payoff.C = (3-3) * dist.C + (4-1) * dist.N + (0-5) * dist.D
        payoff.N = (1-4) * dist.C + (2-2) * dist.N + (3-2) * dist.D
        payoff.D = (5-0) * dist.C + (2-3) * dist.N + (1-1) * dist.D

        # D has slightly better payoff on uniform opponent,
        # so we select it on ties
        if self.strategy == "maxExpected":
            if payoff.C > payoff.N:
                return "C" if payoff.C > payoff.D else "D"
            return "N" if payoff.N > payoff.D else "D"
        elif self.strategy == "randomize":
            payoff = self.normalize(payoff)
            r = random.uniform(0.0, 1.0)
            if (r < payoff.C): return "C"
            return "N" if (r < payoff.N) else "D"
        elif self.strategy == "argMax":
            if dist.C > dist.N:
                return "D" if dist.C > dist.D else "N"
            return "C" if dist.N > dist.D else "N"

        assert(0) #, "I am not a number! I am a free man!")

    def update_history(self):
        self.my_choices += self.choice
        if len(self.my_choices) > self.MARKOV_ORDER:
            assert(len(self.my_choices) == self.MARKOV_ORDER + 1)
            self.my_choices = self.my_choices[1:]

    def round(self, last):
        if last: self.update_opponent_model(last)

        dist = self.get_distribution()
        self.choice = self.choose(dist)
        self.update_history()
        return self.choice

class Ensemble:
    def __init__(self):
        self.models = []
        self.votes = []
        self.prev_choice = []
        for order in range(0, 6):
            self.models.append(Number6("maxExpected", order))
            self.models.append(Number6("randomize", order))
            #self.models.append(Number6("argMax", order))
        for i in range(0, len(self.models)):
            self.votes.append(0)
            self.prev_choice.append("D")

        self.payoff = {
            "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
            "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
            "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
        }

    def round(self, last):
        if last:
            for i in range(0, len(self.models)):
                self.votes[i] += self.payoff[self.prev_choice[i]][last]

        # vote. Sufficiently terrible models get negative votes
        C = 0
        N = 0
        D = 0
        for i in range(0, len(self.models)):
            choice = self.models[i].round(last)
            if "C" == choice: C += self.votes[i]
            if "N" == choice: N += self.votes[i]
            if "D" == choice: D += self.votes[i]
            self.prev_choice[i] = choice

        if C > D and C > N: return "C"
        elif N > D: return "N"
        else: return "D"

Quadro di prova

Nel caso in cui qualcun altro lo ritenga utile, ecco un framework di test per esaminare i singoli matchup. Python2. Metti semplicemente tutti gli avversari a cui sei interessato in oppers.py e cambia i riferimenti a Ensemble nel tuo.

import sys, inspect
import opponents
from ensemble import Ensemble

def count_payoff(label, them):
    if None == them: return
    me = choices[label]
    payoff = {
        "C": { "C": 3-3, "N": 4-1, "D": 0-5 },
        "N": { "C": 1-4, "N": 2-2, "D": 3-2 },
        "D": { "C": 5-0, "N": 2-3, "D": 1-1 },
    }
    if label not in total_payoff: total_payoff[label] = 0
    total_payoff[label] += payoff[me][them]

def update_hist(label, choice):
    choices[label] = choice

opponents = [ x[1] for x 
    in inspect.getmembers(sys.modules['opponents'], inspect.isclass)]

for k in opponents:
    total_payoff = {}

    for j in range(0, 100):
        A = Ensemble()
        B = k()
        choices = {}

        aChoice = None
        bChoice = None
        for i in range(0, 100):
            count_payoff(A.__class__.__name__, bChoice)
            a = A.round(bChoice)
            update_hist(A.__class__.__name__, a)

            count_payoff(B.__class__.__name__, aChoice)
            b = B.round(aChoice)
            update_hist(B.__class__.__name__, b)

            aChoice = a
            bChoice = b
    print total_payoff

Il controller è pronto, non devi fare tutto questo ...
SIGSTACKFAULT

1
@Blacksilver mi sono reso conto che proprio mentre stavo per inviare. Ma questo funziona nelle versioni precedenti alla 3.6 e fornisce informazioni sui singoli matchup che possono aiutare a identificare i punti deboli, quindi non è stata una completa perdita di tempo.
Ray

Giusto; in esecuzione ora. Probabilmente aggiungerò opzioni al mio controller per fare cose simili.
SIGSTACKFAULT

"Batte tutto quanto pubblicato finora tranne Ensemble e PatternFinder" Sono onorato :)
Sparr

@Sparr Oops. Ciò avrebbe dovuto dire EvaluaterBot e PatternFinder. Ma questo è quando si confronta il punteggio totale con l'intero campo. PatternFinder rimane l'unico che batte questo in un match up diretto.
Ray,

4

OldTitForTat

Il giocatore della vecchia scuola è troppo pigro per aggiornarsi per le nuove regole.

class OldTitForTat:
    def round(self, last):
        if(last == None)
            return "C"
        if(last == "C"):
            return "C"
        return "D"

3

NeverCOOP

class NeverCOOP:
    def round(self, last):
        try:
            if last in "ND":
                return "D"
            else:
                return "N"
        except:
            return "N"

Se il bot avversario è difettoso o è neutro, scegliere il difetto. Altrimenti se questo è il primo turno o il robot avversario coopera, scegli neutrale. Non sono sicuro di quanto funzionerà bene ...


A cosa serve / tranne?
SIGSTACKFAULT

1
@Blacksilver Suppongo che abbia lo stesso scopo del if(last):tuo bot Grudger, rilevando se ci fosse un round precedente.
ETHproductions

Ah! Capisco. None in "ND"errori.
SIGSTACKFAULT

Perché if last and last in "ND":era troppo complicato?
user253751

3

LastOptimalBot

class LastOptimalBot:
    def round(self, last):
        return "N" if last == "D" else ("D" if last == "C" else "C")

Presuppone che il bot avversario giocherà sempre di nuovo la stessa mossa e sceglie quello che ha il miglior profitto contro di esso.

medie:

Me   Opp
2.6  2    vs TitForTat
5    0    vs AllC
4    1    vs AllN
3    2    vs AllD
3.5  3.5  vs Random
3    2    vs Grudger
2    2    vs LastOptimalBot
1    3.5  vs TatForTit
4    1    vs NeverCOOP
1    4    vs EvaluaterBot
2.28 2.24 vs NashEquilibrium

2.91 average overall

oof. Forse T4T farebbe di meglio return last.
SIGSTACKFAULT

Mi piacerebbe! Se TitForTat fosse return last, LOB andrebbe 18-9 su 6 round anziché i 13-10 su 5 round che sta attualmente ricevendo. Penso che vada bene così - non preoccuparti di ottimizzare i robot di esempio.
Spitemaster

return lastsarebbe un T4T migliore per questa sfida, credo
Sparr

Ho appena provato - if(last): return last; else: return "C"è peggio.
SIGSTACKFAULT

Giusto, ma come diceva @Sparr, potrebbe essere più appropriato. A te suppongo.
Spitemaster,

3

Copione

class CopyCat:
    def round(self, last):
        if last:
            return last
        return "C"

Copia l'ultima mossa dell'avversario.
Non mi aspetto che funzioni bene, ma nessuno ha ancora implementato questo classico.


2

Dadi di Dirichlet migliorati

import random

class DirichletDice2:
    def __init__(self):

        self.alpha = dict(
                C = {'C' : 1, 'N' : 1, 'D' : 1},
                N = {'C' : 1, 'N' : 1, 'D' : 1},
                D = {'C' : 1, 'N' : 1, 'D' : 1}
        )
        self.myLast = [None, None]
        self.payoff = dict(
                C = { "C": 0, "N": 3, "D": -5 },
                N = { "C": -3, "N": 0, "D": 1 },
                D = { "C": 5, "N": -1, "D": 0 }
        )

    def DirichletDraw(self, key):
        alpha = self.alpha[key].values()
        mu = [random.gammavariate(a,1) for a in alpha]
        mu = [m / sum(mu) for m in mu]
        return mu

    def ExpectedPayoff(self, probs):
        expectedPayoff = {}
        for val in ['C','N','D']:
            payoff = sum([p * v for p,v in zip(probs, self.payoff[val].values())])
            expectedPayoff[val] = payoff
        return expectedPayoff

    def round(self, last):
        if last is None:
            self.myLast[0] = 'D'
            return 'D'

        #update dice corresponding to opponent's last response to my
        #outcome two turns ago
        if self.myLast[1] is not None:
            self.alpha[self.myLast[1]][last] += 1

        #draw probs for my opponent's roll from Dirichlet distribution and then return the optimal response
        mu = self.DirichletDraw(self.myLast[0])
        expectedPayoff = self.ExpectedPayoff(mu)
        res = max(expectedPayoff, key=expectedPayoff.get)

        #update myLast
        self.myLast[1] = self.myLast[0]
        self.myLast[0] = res

        return res    

Questa è una versione migliorata di Dirichlet Dice. Invece di prendere la distribuzione multinomiale prevista dalla distribuzione di Dirichlet, disegna una distribuzione multinomiale in modo casuale dalla distribuzione di Dirichlet. Quindi, invece di attingere dal Multinomiale e dare la risposta ottimale a quello, fornisce la risposta ottimale prevista al Multinomiale dato usando i punti. Quindi la casualità è stata sostanzialmente spostata dal sorteggio Multinomiale al sorteggio di Dirichlet. Inoltre, i priori ora sono più piatti, per incoraggiare l'esplorazione.

È "migliorato" perché ora tiene conto del sistema di punti dando il miglior valore atteso rispetto alle probabilità, mantenendo la sua casualità disegnando le probabilità stesse. In precedenza, ho provato semplicemente a ottenere il miglior risultato atteso dalle probabilità previste, ma ciò ha funzionato male perché si è bloccato e non ho esplorato abbastanza per aggiornare i suoi dadi. Inoltre era più prevedibile e sfruttabile.


Presentazione originale:

Dadi di Dirichlet

import random

class DirichletDice:
    def __init__(self):

        self.alpha = dict(
                C = {'C' : 2, 'N' : 3, 'D' : 1},
                N = {'C' : 1, 'N' : 2, 'D' : 3},
                D = {'C' : 3, 'N' : 1, 'D' : 2}
        )

        self.Response = {'C' : 'D', 'N' : 'C', 'D' : 'N'}
        self.myLast = [None, None]

    #expected value of the dirichlet distribution given by Alpha
    def MultinomialDraw(self, key):
        alpha = list(self.alpha[key].values())
        probs = [x / sum(alpha) for x in alpha]
        outcome = random.choices(['C','N','D'], weights=probs)[0]
        return outcome

    def round(self, last):
        if last is None:
            self.myLast[0] = 'D'
            return 'D'

        #update dice corresponding to opponent's last response to my
        #outcome two turns ago
        if self.myLast[1] is not None:
            self.alpha[self.myLast[1]][last] += 1

        #predict opponent's move based on my last move
        predict = self.MultinomialDraw(self.myLast[0])
        res = self.Response[predict]

        #update myLast
        self.myLast[1] = self.myLast[0]
        self.myLast[0] = res

        return res

Fondamentalmente suppongo che la risposta dell'avversario alla mia ultima uscita sia una variabile multinomiale (dadi ponderati), una per ciascuna delle mie uscite, quindi c'è un dado per "C", uno per "N" e uno per "D" . Quindi se il mio ultimo lancio fosse, ad esempio, una "N", allora lancio i "N-dice" per indovinare quale sarebbe la loro risposta alla mia "N". Comincio con un Dirichlet precedente che presume che il mio avversario sia in qualche modo "intelligente" (più probabilità di giocare quello con il miglior profitto rispetto al mio ultimo tiro, meno probabilità di giocare quello con il peggior profitto). Genero la distribuzione multinomiale "prevista" dal Dirichlet appropriato precedente (questo è il valore atteso della distribuzione di probabilità sui loro pesi dadi). Lancio i dadi ponderati della mia ultima uscita,

A partire dal terzo round, faccio un aggiornamento bayesiano del Dirichlet appropriato prima dell'ultima risposta del mio avversario alla cosa che ho giocato due round fa. Sto cercando di imparare iterativamente la loro ponderazione dei dadi.

Avrei potuto anche semplicemente scegliere la risposta con il miglior risultato "previsto" una volta generato i dadi, invece di semplicemente lanciare i dadi e rispondere al risultato. Tuttavia, volevo mantenere la casualità, in modo che il mio bot fosse meno vulnerabile a quelli che tentano di prevedere uno schema.


2

Kevin

class Kevin:
    def round(self, last):      
        return {"C":"N","N":"D","D":"C",None:"N"} [last]

Scegli la scelta peggiore. Il peggior bot creato.

Inutili

import random

class Useless:
    def __init__(self):
        self.lastLast = None

    def round(self, last):
        tempLastLast = self.lastLast
        self.lastLast = last

        if(last == "D" and tempLastLast == "N"):
            return "C"
        if(last == "D" and tempLastLast == "C"):
            return "N"

        if(last == "N" and tempLastLast == "D"):
            return "C"
        if(last == "N" and tempLastLast == "C"):
            return "D"

        if(last == "C" and tempLastLast == "D"):
            return "N"
        if(last == "C" and tempLastLast == "N"):
            return "D"

        return random.choice("CND")

Guarda le ultime due mosse fatte dall'avversario e prende il massimo, altrimenti sceglie qualcosa di casuale. C'è probabilmente un modo migliore per farlo.


2

Media storica

class HistoricAverage:
    PAYOFFS = {
        "C":{"C":3,"N":1,"D":5},
        "N":{"C":4,"N":2,"D":2},
        "D":{"C":0,"N":3,"D":1}}
    def __init__(self):
        self.payoffsum = {"C":0, "N":0, "D":0}
    def round(this, last):
        if(last != None):
            for x in this.payoffsum:
               this.payoffsum[x] += HistoricAverage.PAYOFFS[last][x]
        return max(this.payoffsum, key=this.payoffsum.get)

Guarda la storia e trova l'azione che sarebbe stata la migliore in media. Inizia cooperativa.


Questo potrebbe funzionare più velocemente se non ricalcola le medie ad ogni round.
Sparr,

@Sparr true. L'ho modificato così adesso.
MegaTom,

1

Media ponderata

class WeightedAverageBot:
  def __init__(self):
    self.C_bias = 1/4
    self.N = self.C_bias
    self.D = self.C_bias
    self.prev_weight = 1/2
  def round(self, last):
    if last:
      if last == "C" or last == "N":
        self.D *= self.prev_weight
      if last == "C" or last == "D":
        self.N *= self.prev_weight
      if last == "N":
        self.N = 1 - ((1 - self.N) * self.prev_weight)
      if last == "D":
        self.D = 1 - ((1 - self.D) * self.prev_weight)
    if self.N <= self.C_bias and self.D <= self.C_bias:
      return "D"
    if self.N > self.D:
      return "C"
    return "N"

Il comportamento dell'avversario è modellato come un triangolo rettangolo con angoli per CND rispettivamente a 0,0 0,1 1,0. Ogni mossa dell'avversario sposta il punto all'interno di quel triangolo verso quell'angolo, e noi giochiamo per battere la mossa indicata dal punto (con C che viene data una fetta del triangolo regolabile). In teoria volevo che questo avesse una memoria più lunga con più peso rispetto alle mosse precedenti, ma in pratica l'attuale meta favorisce i robot che cambiano rapidamente, quindi questo si trasforma in un'approssimazione di LastOptimalBot contro la maggior parte dei nemici. Invio per i posteri; forse qualcuno sarà ispirato.


1

tetragramma

import itertools

class Tetragram:
    def __init__(self):
        self.history = {x: ['C'] for x in itertools.product('CND', repeat=4)}
        self.theirs = []
        self.previous = None

    def round(self, last):
        if self.previous is not None and len(self.previous) == 4:
            self.history[self.previous].append(last)
        if last is not None:
            self.theirs = (self.theirs + [last])[-3:]

        if self.previous is not None and len(self.previous) == 4:
            expected = random.choice(self.history[self.previous])
            if expected == 'C':
                choice = 'C'
            elif expected == 'N':
                choice = 'C'
            else:
                choice = 'N'
        else:
            choice = 'C'

        self.previous = tuple(self.theirs + [choice])
        return choice

Cerca di trovare uno schema nelle mosse dell'avversario, supponendo che stiano guardando anche la nostra ultima mossa.


1

Stretta di mano

class HandshakeBot:
  def __init__(self):
    self.handshake_length = 4
    self.handshake = ["N","N","C","D"]
    while len(self.handshake) < self.handshake_length:
      self.handshake *= 2
    self.handshake = self.handshake[:self.handshake_length]
    self.opp_hand = []
    self.friendly = None
  def round(self, last):
    if last:
      if self.friendly == None:
        # still trying to handshake
        self.opp_hand.append(last)
        if self.opp_hand[-1] != self.handshake[len(self.opp_hand)-1]:
          self.friendly = False
          return "D"
        if len(self.opp_hand) == len(self.handshake):
          self.friendly = True
          return "C"
        return self.handshake[len(self.opp_hand)]
      elif self.friendly == True:
        # successful handshake and continued cooperation
        if last == "C":
          return "C"
        self.friendly = False
        return "D"
      else:
        # failed handshake or abandoned cooperation
        return "N" if last == "D" else ("D" if last == "C" else "C")
    return self.handshake[0]

Riconosce quando gioca contro se stesso, quindi collabora. Altrimenti imita LastOptimalBot che sembra la migliore strategia a una riga. Si comporta peggio di LastOptimalBot, di un importo inversamente proporzionale al numero di round. Ovviamente farebbe meglio se ci fossero più copie nel campo * tosse ** occhiolino *.


Basta inviare alcuni cloni che hanno comportamenti diversi dalla stretta di mano.
SIGSTACKFAULT

Sembra exploit-y. Potrei presentare uno di questi cloni per ogni semplice comportamento rappresentato qui.
Sparr,

Ho aggiunto una clausola in più che dice che puoi inviare solo un massimo di cinque robot.
SIGSTACKFAULT

1

ShiftingOptimalBot

class ShiftingOptimalBot:
    def __init__(self):
        # wins, draws, losses
        self.history = [0,0,0]
        self.lastMove = None
        self.state = 0
    def round(self, last):
        if last == None:
            self.lastMove = "C"
            return self.lastMove
        if last == self.lastMove:
            self.history[1] += 1
        elif (last == "C" and self.lastMove == "D") or (last == "D" and self.lastMove == "N") or (last == "N" and self.lastMove == "C"):
            self.history[0] += 1
        else:
            self.history[2] += 1

        if self.history[0] + 1 < self.history[2] or self.history[2] > 5:
            self.state = (self.state + 1) % 3
            self.history = [0,0,0]
        if self.history[1] > self.history[0] + self.history[2] + 2:
            self.state = (self.state + 2) % 3
            self.history = [0,0,0]

        if self.state == 0:
            self.lastMove = "N" if last == "D" else ("D" if last == "C" else "C")
        elif self.state == 1:
            self.lastMove = last
        else:
            self.lastMove = "C" if last == "D" else ("N" if last == "C" else "D")
        return self.lastMove

Questo bot utilizza l'algoritmo di LastOptimalBot fintanto che sta vincendo. Se l'altro bot inizia a prevederlo, tuttavia, inizierà a giocare qualsiasi mossa giocata per ultima dall'avversario (che è la mossa che batte la mossa che avrebbe battuto LastOptimalBot). Passa attraverso semplici trasposizioni di quegli algoritmi fintanto che continua a perdere (o quando si annoia disegnando molto).

Onestamente, sono sorpreso che LastOptimalBot sia seduto al 5 ° posto mentre lo pubblico. Sono abbastanza sicuro che questo farà di meglio, supponendo di aver scritto correttamente questo pitone.


0

HandshakePatternMatch

from .patternfinder import PatternFinder
import collections

class HandshakePatternMatch:
    def __init__(self):
        self.moves = [None]
        self.other = []
        self.handshake = [None,"N","C","C","D","N"]
        self.friendly = None
        self.pattern = PatternFinder()
    def round(self, last):
        self.other.append(last)
        if last:
            if len(self.other) < len(self.handshake):
                # still trying to handshake
                if self.friendly == False or self.other[-1] != self.handshake[-1]:
                    self.friendly = False
                else:
                    self.friendly = True
                move = self.handshake[len(self.other)]
                self.pattern.round(last)
            elif self.friendly == True:
                # successful handshake and continued cooperation
                move = self.pattern.round(last)
                if last == "C":
                    move = "C"
                elif last == self.handshake[-1] and self.moves[-1] == self.handshake[-1]:
                    move = "C"
                else:
                    self.friendly = False
            else:
                # failed handshake or abandoned cooperation
                move = self.pattern.round(last)
        else:
            move = self.handshake[1]
            self.pattern.round(last)
        self.moves.append(move)
        return move

Perché il modello si abbina a te stesso? Stretta di mano e collaborare via.


import PatternFindersta tradendo i miei libri.
SIGSTACKFAULT

@Blacksilver Si fa tutto il tempo in KOTH. Non è diverso dalla copia del codice in una risposta esistente e dal suo utilizzo. Robot Roulette: il gioco d'azzardo robotizzato con puntate alte ha avuto luogo ovunque, al punto che i robot avrebbero rilevato se il loro codice veniva chiamato da un avversario e sabotavano il ritorno.
Draco18s

Bene allora. TIL.
SIGSTACKFAULT

Farò lo scricchiolio domani.
SIGSTACKFAULT

Ecco un esempio perfetto dell'uso del codice di altri robot. Di solito si tratta di "quel ragazzo ha elaborato un po 'di matematica complicata, voglio i suoi risultati in queste condizioni". (La mia voce ha avuto un effetto piuttosto positivo; UpYours è stato più sparpagliato nel suo approccio).
Draco18s

0

hardcoded

class Hardcoded:
    sequence = "DNCNNDDCNDDDCCDNNNNDDCNNDDCDCNNNDNDDCNNDDNDDCDNCCNNDNNDDCNNDDCDCNNNDNCDNDNDDNCNDDCDNNDCNNDDCDCNNDNNDDCDNDDCCNNNDNNDDCNNDDNDCDNCNDDCDNNDDCCNDNNDDCNNNDCDNDDCNNNNDNDDCDNCDCNNDNNDDCDNDDCCNNNDNDDCNNNDNDCDCDNNDCNNDNDDCDNCNNDDCNDNNDDCDNNDCDNDNCDDCNNNDNDNCNDDCDNDDCCNNNNDNDDCNNDDCNNDDCDCNNDNNDDCDNDDCCNDNNDDCNNNDCDNNDNDDCCNNNDNDDNCDCDNNDCNNDNDDCNNDDCDNCNNDDCDNNDCDNDNCDDCNDNNDDCNNNDDCDNCNNDNNDDCNNDDNNDCDNCNDDCNNDCDNNDDCNNDDNCDCNNDNDNDDCDNCDCNNNDNDDCDCNNDNNDDCDNDDCCNNNDNNDDCNDNDNCDDCDCNNNNDNDDCDNCNDDCDNNDDCNNNDNDDCDNCNNDCNNDNDDNCDCDNNNDDCNNDDCNNDDNNDCDNCNDDCNNDDNDCDNNDNDDCCNCDNNDCNNDDNDDCNCDNNDCDNNNDDCNNDDCDCDNNDDCNDNCNNDNNDNDNDDCDNCDCNNNDNDDCDNCNNDDCDNNDCNNDDCNNDDCDCDNNDDCNDNCNNNDDCDNNDCDNDNCNNDNDDNNDNDCDDCCNNNDDCNDNDNCDDCDCNNNDNNDDCNDCDNDDCNNNNDNDDCCNDNNDDCDCNNNDNDDNDDCDNCCNNDNNDDCNNDDCDCNNDNNDDCNNDDNCNDDNNDCDNCNDDCNNDDNDCDNNDNDDCCNCDNNDCNNDNDDCNNDDNCDCDNNDCNNDNDDCDCDNNNNDDCNNDDNDCCNNDDNDDCNCDNNDCNNDDNDDCDNCNDDCNNNNDCDNNDDCNDNDDCDNCNNDCDNNDCNNDNDDNCDCNNDNDDCDNDDCCNNNNDNDDCNNDDCDCNNDNNDDCDCDNNDDC"
    def __init__(self):
        self.round_num = -1
    def round(self,_):
        self.round_num += 1
        return Hardcoded.sequence[self.round_num % 1000]

Gioca solo una sequenza di mosse hardcoded ottimizzata per battere alcuni dei principali robot deterministici.

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