反復囚人のトリレンマ


19

チャレンジステータス:オープン

あなたのボットが見つからない場合は、コメントするか、PRを開くか、そうでなければ私に叫んでください。


囚人のジレンマ... 3つの選択肢。クレイジーだよね?

ペイオフマトリックスは次のとおりです。左側のプレーヤーA、上部のB

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

ペイオフマトリックスは、両方のプレイヤーが常に協力するのが最善であるように設計されていますが、(通常)ニュートラルまたはディフェクションを選択することで獲得できます。

次に、いくつかの(競合する)ボットの例を示します。

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

ボットはPython3クラスです。ゲームごとに新しいインスタンスが作成され、round()各ラウンドと呼ばれます。対戦相手は最後のラウンドから選択します(最初のラウンドの場合はなし)。

勝者には1か月ほどで50の報奨金があります。

仕様

  • すべてのボットは、[編集済み]ラウンドで、それ自体を含む他のすべてのボット(1v1)をプレイします。
  • 標準の抜け穴は許可されていません。
  • クラスや他の手に負えないシェナニガイン以外のものをいじることはありません。
  • 最大5つのボットを送信できます。
  • はい、ハンドシェイクを実装できます。
  • 、、または以外の応答はC、黙ってとして扱われます。NDN
  • プレイするすべてのゲームの各ボットのポイントが合計され、比較されます。

コントローラ

チェック!

他の言語

誰かがそれを必要とするなら、私は一緒にAPIを投げます。

スコア: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
ボットはどのように相互に配置されますか?Grudgerからは、常に2つのボットが相互に敵対しており、敵の最後の選択がボットに渡されることがわかります。何ラウンドプレイされますか?そして、ゲームの場合:結果のみ(つまり、誰が勝ったか)またはポイントだけがカウントされますか?
ブラックフクロウカイ

1
この言語にとらわれない、または少なくともより広い範囲にした場合、より多くのエントリを取得できます。プロセスを生成し、テキストコマンドを送信してテキスト応答を取得するラッパーPythonクラスを作成できます。
スパー

1
できた これは1か月ほどサンドボックスにありました!
SIGSTACKFAULT

2
あなたがmain.pyのほとんどをラップした場合while len(botlist) > 1:botlist.remove(lowest_scoring_bot)、ループの一番下に、あなたは興味深い結果で除去トーナメントを取得します。
スパー

1
このいつかの別のバージョンは、最後の動きだけでなく、インタラクション履歴全体を渡すかもしれません。ユーザーコードをわずかに単純化しますが、あまり変わりません。しかし、時間の経過とともに明らかになるノイズの多い通信チャネルなどの拡張が可能になります。「本当に、D、Cを4回続けて言ったとしても、いいえ、Dとは言わなかった。ああ、すみません、あのラウンドを忘れることができますか?」
スコットサウイエット

回答:


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]

ランダムボット(ただし、ドローでDを選択し、Dが最適である必要があるため)を除く、以前に送信されたすべてのボットに勝ち、一定のドローを自分自身に対して行います。


うん、すべてを打ち負かす。
SIGSTACKFAULT

スクラッチ、PatternFinderは少しそれを打ちます。
SIGSTACKFAULT

7

NashEquilibrium

このボットは大学でゲーム理論のクラスを受講しましたが、怠け者であり、反復ゲームを扱うクラスには行きませんでした。したがって、彼は単一ゲームの混合ナッシュ均衡のみをプレイします。1/5 2/5 2/5は、ペイオフの混合NEです。

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

絶え間ないナッシュ均衡

このボットは、怠け者の兄弟から1つか2つのレッスンを受けました。彼の怠け者の兄弟の問題は、彼が固定戦略を利用しなかったことでした。このバージョンでは、相手がコンスタントプレイヤーかtitfortatかをチェックし、それに応じてプレイします。それ以外の場合は、通常のナッシュ均衡をプレイします。

唯一の欠点は、1ターンあたり平均2.2ポイントでプレイすることです。

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は、予想される関数プロトタイプに適合するように、引数を使用しない場合でも引数を取る必要があります。
レイ

修正していただきありがとうございます
Ofya

少し短く: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
ロバートグラント

7

TatForTit

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

このボットはDNDNを選択し、TitForTatはCDCDを選択します。ペイアウトマトリックスを正しく読み取った場合、ラウンドごとに平均3ポイントの純利益が得られます。これはTitForTatに対して最適だと思います。明らかに、TFT以外の敵を検出し、他の戦略を採用することで改善できる可能性がありますが、私は元々の報奨金を目指していました。


6

パターンファインダー

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

このボットは、最近のゲーム状態の以前の発生を探して、対戦相手がそれらの発生にどのように応答したかを確認し、より長いパターンマッチとより最近のマッチを優先し、対戦相手の予測された動きを「打つ」動きを再生します。追跡するすべてのデータを使用してよりスマートにする余地は大いにありますが、作業する時間が足りませんでした。


時間が取れたら、彼女に最適化パスを渡してください。簡単に最大のタイムシンクです。
SIGSTACKFAULT

2
私はちょうど10に100から最大パターン長を減少@Blacksilverあなたが<200回実行している場合、それは今、ほぼ瞬時に実行する必要があります
SPARRを

1
たぶん、高度に合成された数値(12など)を使用すると、より良いスコアが得られるでしょうか?
SIGSTACKFAULT

5

ヒスイ

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'

楽観的に始めますが、相手が協力することを拒否するにつれて、徐々に苦味が増します。おそらく微調整できる多くの魔法の定数ですが、これはおそらく時間を正当化するのに十分なほどうまくいきません。


5

アンサンブル

これにより、関連モデルのアンサンブルが実行されます。個々のモデルは異なる量の履歴を考慮し、予想される配当の差を最適化する動きを常に選択するか、予想される配当の差に比例して動きをランダムに選択するオプションがあります。

その後、アンサンブルの各メンバーは、希望する動きに投票します。彼らは、相手よりも多く勝った数に等しい票数を獲得します(つまり、ひどいモデルは否定的な票を獲得します)。どちらの動きが投票に勝ったかが選択されます。

(彼らはおそらく、彼らがそれぞれにどれだけ賛成しているかに比例して、彼らの票を動きの間で分けるべきです、しかし、私は今それをするのに十分気にしません。)

EvaluaterBotとPatternFinderを除いて、これまでに投稿されたすべてのものよりも優れています。(1対1、EvaluaterBotを破り、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"

テストフレームワーク

誰か他の人が便利だと思った場合に備えて、個々の対戦を見るためのテストフレームワークを紹介します。Python2。enemys.pyに興味のあるすべての対戦相手を入れて、Ensembleへの参照を自分のものに変更します。

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

コントローラーの準備が整ったので、すべてを行う必要はありませんでした
...-SIGSTACKFAULT

1
@Blacksilver私は、私が提出しようとしていたことにちょうど気づきました。しかし、これは3.6より前のバージョンで機能し、弱点を特定するのに役立つ個々の対戦に関する情報を提供するため、完全な時間の無駄ではありませんでした。
レイ

けっこうだ; 今走っている。同様のことをするために、おそらくコントローラーにオプションを追加します。
SIGSTACKFAULT

私は光栄「それはすべてがアンサンブルとPatternFinder除いて、これまでに掲載打つ」:)
SPARR

@Sparrおっと。それは、EvaluaterBotとPatternFinderと言うことになっていた。しかし、それは合計スコアをフィールド全体と比較するときです。PatternFinderは、直接マッチでこれに勝つ唯一のものです。
レイ

4

OldTitForTat

古い学校のプレーヤーは、新しいルールを更新するのが面倒です。

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"

相手のボットに欠陥があるか、中立である場合、欠陥を選択します。それ以外の場合、これが最初のターンであるか、相手のボットが協力する場合、ニュートラルを選択します。これがどれだけうまくいくかわかりません...


try / exceptは何ですか?
SIGSTACKFAULT

1
@Blacksilver if(last):Grudgerボットと同じ目的を果たし、前のラウンドがあったかどうかを検出すると思います。
ETHproductions

ええ、わかりました。None in "ND"エラー。
SIGSTACKFAULT

そのためif last and last in "ND":、あまりにも複雑になりましたか?
user253751

3

LastOptimalBot

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

相手のボットが常に同じ動きをすることを想定し、それに対して最高の見返りを持つものを選択します。

平均:

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

おー たぶんT4Tの方が良いでしょうreturn last
SIGSTACKFAULT

私はそれが欲しいです!TitForTatがの場合return last、LOBは6ラウンドで18-9になり、5ラウンドで13-10になります。現状のままで良いと思います-サンプルボットの最適化について心配する必要はありません。
Spitemaster

return lastこのチャレンジにとってより良い
T4T

試しただけです- if(last): return last; else: return "C"悪いことです。
SIGSTACKFAULT

しかし、@ Sparrが言っていたように、それはより適切かもしれません。あなた次第だと思います。
-Spitemaster

3

CopyCat

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

相手の最後の動きをコピーします。
これがうまくいくとは思いませんが、このクラシックをまだ実装している人はいませんでした。


2

改良されたディリクレダイス

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    

これは、ディリクレダイスの改良版です。ディリクレ分布から予想される多項分布を取得する代わりに、ディリクレ分布から多項分布をランダムに描画します。次に、多項式から引き出してそれに最適な応答を与えるのではなく、点を使用して、与えられた多項式に最適な予想応答を与えます。そのため、ランダム性は本質的に多項式引き分けからディリクレ引き分けにシフトしました。また、探査を促進するために、事前条件はよりフラットになりました。

これは、確率に対して最良の期待値を与えることでポイントシステムを考慮し、確率自体を引き出すことでランダム性を維持するため、「改善」されています。以前は、予想される確率から最高の予想される見返りを行うことを試みましたが、スタックしたばかりで、サイコロを更新するのに十分な探索をしなかったため、ひどくしました。また、より予測可能かつ悪用可能でした。


元の提出:

ディリクレダイス

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

基本的に、最後の出力に対する相手の応答は、各出力に1つずつの多項変数(加重ダイス)であると想定しているため、「C」、「N」、および「D」に1つのサイコロがあります。例えば、私の最後のロールが「N」だった場合、「N」を振って、「N」に対する反応を推測します。私は、対戦相手がいくぶん「スマート」であると仮定するディリクレの前から始めます(私の最後のロールに対して最高のペイオフでプレイする可能性が最も高く、最悪のペイオフでプレイする可能性が最も低い)。適切なディリクレ事前分布から「期待される」多項分布を生成します(これは、ダイスの重みに対する確率分布の期待値です)。最後の出力の重み付きサイコロを振って、

3ラウンド目から、2ラウンド前にプレイしたことに対する相手の最後の応答の前に、適切なディリクレのベイジアン更新を行います。私はサイコロの重みを繰り返し学習しようとしています。

また、サイコロを転がして結果に応答するのではなく、サイコロを生成してから、「予想される」最良の結果が得られる応答を選択することもできます。ただし、パターンを予測しようとするボットに対するボットの脆弱性を減らすために、ランダム性を維持したかったのです。


2

ケビン

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

最悪の選択を選択します。作られた最悪のボット。

役に立たない

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

相手によって行われた最後の2つの動きを見て、最も行われていないものを選択します。おそらくこれを行うより良い方法があります。


2

歴史的平均

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)

履歴を見て、平均して最高だったアクションを見つけます。協力を開始します。


ラウンドごとに平均を再計算しなければ、これはより速く実行できます。
スパー

@Sparr true。私が編集したので、すぐに編集できます。
メガトム

1

加重平均

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"

相手の行動は、CNDのコーナーがそれぞれ0,0 0,1 1,0の直角三角形としてモデル化されます。各対戦相手の動きは、その三角形内のポイントをそのコーナーに向けてシフトし、ポイントが示す動きに勝つためにプレーします(Cには三角形の調整可能な小さなスライスが与えられます)。理論的には、以前の動きにより大きな重みを持つより長いメモリが必要でしたが、実際には、現在のメタは急速に変化するボットを好むため、これはほとんどの敵に対するLastOptimalBotの近似になります。後世への投稿。誰かがインスピレーションを受けるでしょう。


1

テトラグラム

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

相手の動きも最後の動きを見ていると仮定して、相手の動きのパターンを見つけてください。


1

ハンドシェーク

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]

自分自身と対戦していることを認識し、協力します。そうでなければ、LastOptimalBotを模倣します。これは、最高の1行戦略のようです。ラウンド数に反比例する量だけ、LastOptimalBotよりもパフォーマンスが低下します。フィールド* cough ** wink *により多くのコピーがあれば、明らかに良くなります。


ハンドシェイク以外の動作が異なるいくつかのクローンを送信するだけです。
SIGSTACKFAULT

それはエクスプロイトYのようです。ここに示す単純な動作ごとに、このようなクローンを1つ送信できます。
スパー

最大5つのボットしか送信できないという追加の条項を追加しました。
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

このボットは、LastOptimalBotのアルゴリズムを使用します。ただし、他のボットが予測を開始した場合、相手が最後にプレイした動き(LastOptimalBotを打ち負かす動きを打ち負かす動き)でプレイを開始します。失われ続ける限り(または多くの描画で退屈する場合)、それらのアルゴリズムの単純な転置を繰り返します。

正直なところ、私はこれを投稿する際、LastOptimalBotが5位にいることに驚いています。私はこのpythonを正しく書いたと仮定して、これがうまくいくことをかなり確信しています。


0

ハンドシェイクパターンマッチ

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

自分でパターンマッチングを行う理由 握手と協力してください。


import PatternFinder私の本でごまかしています。
SIGSTACKFAULT

@Blacksilver KOTHで常に行われます。既存の回答のコードをコピーして使用することと同じです。ロボットルーレット:ハイステークスロボットギャンブルは、ボットがコードが対戦相手によって呼び出されているかどうかを検出し、リターンを妨害するまで、あらゆる場所で発生していました。
Draco18s

じゃあ TIL。
SIGSTACKFAULT

明日はクランチをします。
SIGSTACKFAULT

ここだ他のボットコードを使用しての完璧な例。通常は、「あの男はいくつかのトリッキーな数学を計算しました。これらの条件下で彼の結果が欲しいのです」。(私自身のエントリはそれをかなり良い効果をもたらしました; UpYoursはそのアプローチにおいてより散発的でした)。
Draco18s

0

ハードコーディング

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]

いくつかの上位決定性ボットに勝つために最適化されたハードコードされた一連の動きを再生します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.