Trilemma Tahanan Iterasi


19

STATUS TANTANGAN: BUKA

Komentari, buka PR, atau berteriak pada saya jika saya kehilangan bot Anda.


Dilema tahanan ... dengan tiga pilihan. Gila, ya?

Inilah matriks hasil kami. Pemain A di sebelah kiri, B di atas

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

Matriks hasil direkayasa sehingga yang terbaik bagi kedua pemain untuk selalu bekerja sama, tetapi Anda bisa mendapatkan (biasanya) dengan memilih Netral atau Defection.

Inilah beberapa bot contoh (yang bersaing).

# 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"

Bot Anda adalah kelas Python3. Sebuah instance baru dibuat untuk setiap pertandingan, dan round()disebut setiap putaran, dengan pilihan lawan Anda dari babak terakhir (atau Tidak ada, jika itu babak pertama)

Ada hadiah 50 rep untuk pemenang dalam waktu sebulan.

Spesifik

  • Setiap bot memainkan setiap bot lainnya (1v1), termasuk dirinya sendiri, dalam putaran [DIURANGI].
  • Celah standar tidak diijinkan.
  • Jangan main-main dengan apa pun di luar kelas Anda atau alasan buruk lainnya.
  • Anda dapat mengirim hingga lima bot.
  • Ya, Anda dapat menerapkan jabat tangan.
  • Tanggapan selain C, Natau Dakan diam-diam diambil sebagaiN .
  • Setiap poin bot dari setiap pertandingan yang mereka mainkan akan dijumlahkan dan dibandingkan.

Pengendali

Memeriksa!

Bahasa lainnya

Saya akan mengumpulkan API jika ada yang membutuhkannya.

Skor: 2018-11-27

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
Bagaimana bot ditempatkan satu sama lain? Saya dapatkan dari Grudger bahwa selalu ada dua bot melawan / dengan satu sama lain dan pilihan terakhir musuh diteruskan ke bot. Berapa putaran yang dimainkan? Dan untuk sebuah game: Apakah hanya hasilnya yang dihitung (yaitu siapa yang menang) atau juga poin?
Black Owl Kai

1
Anda akan mendapatkan lebih banyak entri jika Anda membuat bahasa ini agnostik, atau setidaknya lebih luas. Anda bisa memiliki kelas python wrapper yang memunculkan proses dan mengirimkannya perintah teks untuk mendapatkan kembali respons teks.
Sparr

1
Selesai Ini ada di bak pasir selama sebulan!
SIGSTACKFAULT

2
Jika Anda membungkus sebagian besar main.py while len(botlist) > 1:dengan botlist.remove(lowest_scoring_bot)di bagian bawah loop, Anda mendapatkan turnamen eliminasi dengan hasil yang menarik.
Sparr

1
Versi lain dari ini suatu hari nanti mungkin melewati seluruh riwayat interaksi daripada hanya langkah terakhir. Itu tidak banyak berubah meskipun sedikit menyederhanakan kode pengguna. Tapi itu akan memungkinkan untuk ekstensi, seperti saluran komunikasi berisik yang mengklarifikasi dari waktu ke waktu: "Sungguh, D, meskipun saya sudah mengatakan C empat kali berturut-turut? Tidak, saya tidak mengatakan D; apa yang Anda ambil saya untuk? Oh, maaf, bisakah kita melupakan putaran itu? "
Scott Sauyet

Jawaban:


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]

Menangkan semua bot yang sebelumnya dikirim kecuali (mungkin) bot acak (tetapi bot itu bisa memiliki keuntungan, karena mengambil D dalam hasil imbang dan D harus optimal) dan memainkan imbang konstan melawan diri mereka sendiri.


Ya, mengalahkan segalanya.
SIGSTACKFAULT

Gosok itu, PatternFinder mengalahkannya sedikit.
SIGSTACKFAULT

7

NashEquilibrium

Bot ini telah mengambil kelas teori permainan di perguruan tinggi tetapi malas dan tidak pergi ke kelas tempat mereka membahas permainan yang diulang. Jadi dia hanya memainkan game tunggal nash equilibrium campuran. Ternyata 1/5 2/5 2/5 adalah NE campuran untuk imbalan.

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

Constant Nash Penyalahgunaan Keseimbangan

Bot ini mengambil satu atau dua pelajaran dari saudaranya yang malas. Masalah saudara laki-lakinya yang malas adalah dia tidak memanfaatkan strategi tetap. Versi ini memeriksa apakah lawannya adalah pemain konstan atau titfortat dan bermain sesuai, selain itu ia memainkan keseimbangan nash biasa.

Satu-satunya downside adalah bahwa rata-rata 2,2 poin per giliran bermain melawan dirinya sendiri.

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 perlu mengambil argumen bahkan jika itu tidak menggunakannya, sehingga sesuai dengan prototipe fungsi yang diharapkan.
Ray

Terima kasih telah memperbaikinya
Ofya

Sedikit lebih pendek: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"

Bot ini akan bergantian memilih DNDN sementara TitForTat alternatif CDCD, untuk keuntungan bersih rata-rata 3 poin per putaran jika saya telah membaca matriks pembayaran dengan benar. Saya pikir ini mungkin optimal terhadap TitForTat. Jelas itu dapat ditingkatkan untuk mendeteksi lawan non-TFT dan mengadopsi strategi lain, tetapi saya hanya bertujuan untuk karunia asli.


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

Bot ini mencari kejadian sebelumnya dari kondisi permainan terakhir untuk melihat bagaimana lawan merespons kejadian itu, dengan preferensi untuk pertandingan pola yang lebih lama dan pertandingan yang lebih baru, kemudian memainkan gerakan yang akan "mengalahkan" gerakan yang diprediksi lawan. Ada banyak ruang untuk menjadi lebih pintar dengan semua data yang dilacaknya, tetapi saya kehabisan waktu untuk mengerjakannya.


Ketika Anda mendapatkan waktu, keberatan memberinya izin pengoptimalan? Ini adalah time-sink terbesar.
SIGSTACKFAULT

2
@ Blacksilver Saya baru saja mengurangi panjang pola maksimum dari 100 menjadi 10. Ini seharusnya berjalan hampir seketika sekarang jika Anda menjalankan <200 putaran
Sparr

1
Mungkin dengan menggunakan angka yang sangat komposit (yaitu, 12) akan skor lebih baik?
SIGSTACKFAULT

5

Giok

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'

Mulailah dengan optimis, tetapi semakin pahit karena lawan tidak mau bekerja sama. Banyak konstanta ajaib yang mungkin bisa diubah, tetapi ini mungkin tidak cukup baik untuk membenarkan waktu.


5

Ansambel

Ini menjalankan ansambel model terkait. Masing-masing model mempertimbangkan jumlah riwayat yang berbeda, dan memiliki opsi untuk selalu memilih langkah yang akan mengoptimalkan perbedaan pembayaran yang diharapkan, atau akan secara acak memilih langkah dalam proporsi terhadap perbedaan pembayaran yang diharapkan.

Setiap anggota ensemble kemudian memberikan suara pada langkah yang mereka sukai. Mereka mendapatkan jumlah suara yang sama dengan berapa banyak yang telah mereka menangkan daripada lawan (yang berarti bahwa model yang buruk akan mendapatkan suara negatif). Langkah mana pun yang memenangkan suara kemudian dipilih.

(Mereka mungkin harus membagi suara mereka di antara gerakan sesuai dengan seberapa besar mereka menyukai masing-masing, tapi saya tidak cukup peduli untuk melakukan itu sekarang.)

Itu mengalahkan semua yang diposting sejauh ini kecuali EvaluaterBot dan PatternFinder. (Satu-satu, itu mengalahkan EvaluaterBot dan kehilangan ke 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"

Kerangka Uji

Jika ada orang lain yang merasakan manfaatnya, inilah kerangka uji coba untuk melihat pertarungan individu. Python2. Masukkan saja semua lawan yang Anda minati di dalam oposisi.py, dan ubah referensi ke Ensemble menjadi milik Anda.

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

Kontrolernya sudah siap, Anda tidak perlu melakukan semua itu ...
SIGSTACKFAULT

1
@ Blacksilver saya menyadari bahwa sama seperti saya akan mengirimkan. Tapi yang ini bekerja dalam versi sebelum 3.6, dan memberikan informasi tentang pertarungan individu yang dapat membantu mengidentifikasi titik-titik lemah, jadi itu bukan buang-buang waktu.
Ray

Cukup adil; sedang berlari sekarang. Saya mungkin akan menambahkan opsi ke controller saya untuk melakukan hal serupa.
SIGSTACKFAULT

"Ini mengalahkan semua yang diposting sejauh ini kecuali Ensemble dan PatternFinder" Saya merasa terhormat :)
Sparr

@Sparr Ups. Itu seharusnya mengatakan EvaluaterBot dan PatternFinder. Tapi saat itulah membandingkan skor total melawan seluruh bidang. PatternFinder tetap satu-satunya yang mengalahkan ini dalam pertandingan langsung.
Ray

4

OldTitForTat

Pemain sekolah lama terlalu malas untuk memperbarui aturan baru.

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"

Jika bot lawan yang berlawanan cacat atau netral, pilih cacat. Kalau tidak, jika ini giliran pertama atau bot lawan bekerja sama, pilih netral. Saya tidak yakin seberapa bagus ini akan bekerja ...


Apa coba / kecuali untuk
SIGSTACKFAULT

1
@ Blacksilver Saya menganggap itu melayani tujuan yang sama seperti if(last):di bot Grudger Anda, mendeteksi apakah ada putaran sebelumnya.
ETHproduk

ahh, begitu. None in "ND"kesalahan.
SIGSTACKFAULT

Karena if last and last in "ND":terlalu rumit?
user253751

3

LastOptimalBot

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

Diasumsikan bahwa bot lawan akan selalu memainkan gerakan yang sama lagi, dan memilih bot yang memiliki hasil terbaik melawannya.

Rata-rata:

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 Mungkin T4T akan lebih baik return last.
SIGSTACKFAULT

Aku suka itu! Jika TitForTat adalah return last, LOB akan pergi 18-9 lebih dari 6 putaran daripada 13-10 lebih dari 5 putaran itu saat ini. Saya pikir tidak apa-apa - jangan khawatir tentang mengoptimalkan bot contoh.
Spitemaster

return lastakan menjadi T4T yang lebih baik untuk tantangan ini, saya pikir
Sparr

Hanya mencoba - yang if(last): return last; else: return "C"lebih buruk.
SIGSTACKFAULT

Benar, tapi seperti yang dikatakan @Sparr, mungkin lebih tepat. Terserah Anda, saya kira.
Spitemaster

3

Peniru

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

Menyalin gerakan terakhir lawan.
Saya tidak berharap ini bekerja dengan baik, tetapi belum ada yang mengimplementasikan klasik ini.


2

Peningkatan Dirichlet Dice

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    

Ini adalah versi yang lebih baik dari Dirichlet Dice. Alih-alih mengambil distribusi multinomial yang diharapkan dari distribusi Dirichlet, ia mengambil distribusi Multinomial secara acak dari distribusi Dirichlet. Kemudian, alih-alih menggambar dari Multinomial dan memberikan respons optimal untuk itu, itu memberikan respons yang diharapkan optimal untuk Multinomial yang diberikan menggunakan titik-titik. Jadi keacakan pada dasarnya telah bergeser dari undian Multinomial ke undian Dirichlet. Juga, prior sekarang lebih datar, untuk mendorong eksplorasi.

Ini "ditingkatkan" karena sekarang memperhitungkan sistem poin dengan memberikan nilai terbaik yang diharapkan terhadap probabilitas, sambil mempertahankan keacakannya dengan menggambar probabilitas itu sendiri. Sebelumnya, saya mencoba melakukan hasil terbaik yang diharapkan dari probabilitas yang diharapkan, tetapi itu sangat buruk karena macet, dan tidak cukup mengeksplorasi untuk memperbarui dadu. Juga lebih mudah diprediksi dan dieksploitasi.


Pengajuan asli:

Dadu 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

Pada dasarnya saya berasumsi bahwa respons lawan terhadap output terakhir saya adalah variabel multinomial (dadu berbobot), satu untuk setiap output saya, jadi ada dadu untuk "C", satu untuk "N", dan satu untuk "D" . Jadi jika gulungan terakhir saya, misalnya, "N" maka saya melempar "N-dadu" untuk menebak apa tanggapan mereka terhadap "N" saya. Saya mulai dengan Dirichlet sebelum yang mengasumsikan bahwa lawan saya agak "pintar" (lebih cenderung memainkan yang dengan hasil terbaik melawan daftar terakhir saya, paling tidak bermain dengan yang dengan hasil terburuk). Saya menghasilkan distribusi Multinomial "yang diharapkan" dari Dirichlet yang sesuai sebelumnya (ini adalah nilai yang diharapkan dari distribusi probabilitas di atas bobot dadu mereka). Saya melempar dadu berbobot dari hasil terakhir saya,

Mulai di babak ketiga, saya melakukan pembaruan Bayesian tentang Dirichlet yang sesuai sebelum tanggapan terakhir lawan saya untuk hal yang saya mainkan dua putaran lalu. Saya mencoba secara iteratif mempelajari bobot dadu mereka.

Saya bisa saja memilih respons dengan hasil "terbaik" yang diharapkan setelah membuat dadu, daripada hanya menggulirkan dadu dan merespons hasilnya. Namun, saya ingin menjaga keacakan, sehingga bot saya kurang rentan terhadap yang mencoba memprediksi suatu pola.


2

Kevin

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

Pilihan pilihan terburuk. Bot terburuk dibuat.

Tak berguna

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

Itu terlihat pada dua gerakan terakhir yang dilakukan oleh lawan dan mengambil yang paling tidak dilakukan selain itu mengambil sesuatu secara acak. Mungkin ada cara yang lebih baik untuk melakukan ini.


2

Rata-rata Bersejarah

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)

Lihatlah sejarah dan temukan tindakan yang seharusnya menjadi yang terbaik. Mulai kooperatif.


Ini bisa berjalan lebih cepat jika tidak menghitung ulang rata-rata setiap putaran.
Sparr

@Parr benar. Saya mengeditnya jadi sekarang.
MegaTom

1

Rata-rata tertimbang

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"

Perilaku lawan dimodelkan sebagai segitiga siku-siku dengan sudut untuk CND masing-masing sebesar 0,0 0,1 1,0. Setiap gerakan lawan menggeser titik di dalam segitiga itu ke sudut itu, dan kami bermain untuk mengalahkan langkah yang ditunjukkan oleh titik tersebut (dengan C diberi irisan kecil segitiga yang dapat disesuaikan). Secara teori saya ingin ini memiliki memori yang lebih panjang dengan bobot lebih untuk pergerakan sebelumnya, tetapi dalam praktiknya meta saat ini lebih menyukai bot yang berubah dengan cepat, jadi ini berubah menjadi perkiraan LastOptimalBot terhadap sebagian besar musuh. Posting untuk anak cucu; mungkin seseorang akan terinspirasi.


1

Tetragram

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

Cobalah untuk menemukan pola dalam gerakan lawan, dengan asumsi mereka juga mengawasi langkah terakhir kami.


1

Jabatan tangan

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]

Mengakui saat bermain melawan dirinya sendiri, lalu bekerja sama. Kalau tidak, meniru LastOptimalBot yang tampaknya seperti strategi satu baris terbaik. Berkinerja lebih buruk daripada LastOptimalBot, dengan jumlah yang berbanding terbalik dengan jumlah putaran. Jelas akan lebih baik jika ada lebih banyak salinan di bidang * batuk ** wink *.


Kirimkan beberapa klon yang memiliki perilaku non-jabat tangan yang berbeda.
SIGSTACKFAULT

Itu sepertinya exploit-y. Saya bisa mengirimkan satu klon untuk setiap perilaku sederhana yang diwakili di sini.
Sparr

Saya telah menambahkan klausa tambahan yang mengatakan bahwa Anda hanya dapat mengirim maksimal lima bot.
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

Bot ini menggunakan algoritma LastOptimalBot selama itu menang. Namun, jika bot lain mulai memprediksikannya, ia akan mulai memainkan gerakan yang dimainkan lawannya yang terakhir (yang merupakan langkah yang mengalahkan langkah yang akan mengalahkan LastOptimalBot). Itu siklus melalui transposisi sederhana dari algoritma tersebut selama itu terus hilang (atau ketika bosan dengan menggambar banyak).

Jujur, saya terkejut bahwa LastOptimalBot duduk di posisi ke 5 saat saya memposting ini. Saya cukup yakin ini akan lebih baik, dengan asumsi saya menulis python ini dengan benar.


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

Mengapa pola cocok dengan diri Anda sendiri? Berjabat tangan dan bekerja sama.


import PatternFindercurang di buku saya.
SIGSTACKFAULT

@ Blacksilver Itu dilakukan sepanjang waktu di KOTH. Tidak ada bedanya dengan menyalin kode dalam jawaban yang ada dan menggunakannya. Robot Roulette: Taruhan robot taruhan tinggi telah terjadi di mana-mana hingga bot akan mendeteksi jika kode mereka dipanggil oleh lawan dan menyabot kembalinya.
Draco18s

Baik-baik saja maka. TIL.
SIGSTACKFAULT

Saya akan melakukan crunching besok.
SIGSTACKFAULT

Berikut adalah contoh sempurna menggunakan kode bot lain. Biasanya sampai pada "orang itu mengerjakan beberapa matematika yang rumit, saya ingin hasilnya di bawah kondisi ini." ( Entri saya sendiri melakukan itu dengan efek yang cukup baik; UpYour lebih suka melakukan scatter-shot dalam pendekatannya).
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]

Putar saja urutan gerakan yang dioptimalkan untuk mengalahkan beberapa bot deterministik teratas.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.