騒々しい反復囚人のジレンマ


35

この挑戦では、騒々しい反復囚人のジレンマをプレイします。

囚人のジレンマが協力し、または欠陥:2人の選手、二つの選択肢と、それぞれがあるゲーム理論ではシナリオです。各プレーヤーは、協力する場合よりも欠陥がある場合のほうが自分にとって有利ですが、両方のプレーヤーは、両方のプレーヤーが欠陥のあるものよりも両方のプレーヤーが協力する結果を好むでしょう。

繰り返される囚人のジレンマは同じゲームです。ただし、同じ対戦相手と繰り返し対戦し、対戦相手が過去に何をプレイしたかを知っています。あなたの目的は、対戦相手の行動に関係なく、常に自分で最高のスコアを獲得することです。

騒々しい反復囚人のジレンマは、コミュニケーションにノイズを導入します。対戦相手が過去に何をプレイしたかを知っていると、ノイズが発生します。また、過去にどのような動きをしたかもわかります。ノイズレートは同じ対戦相手に対するラウンドでは一定ですが、ラウンド間で異なります。

チャレンジ

この課題では、騒々しい反復囚人のジレンマを再生するPython 3プログラムを作成します。

プログラムは3つの入力を受け取ります。

  • ランダムなフリップを適用せずに、独自の動き。

  • ランダムなフリップが適用された相手の動き。

  • 状態変数。各ラウンドで空のリストとして開始され、必要に応じて変更できます。使用しない場合は、これを無視できます。

あなたのプログラムは'c'協力するか'd'、欠陥があるように出力すべきです。

たとえば、ランダムフリップが適用された後、最初の10フリップで、過去に少なくとも60%の時間、対戦相手が協力した場合に協力するプログラムを次に示します。

def threshold(my_plays, their_flipped_plays, state):
    if len(their_flipped_plays) < 10:
        return 'c'
    opp_c_freq = their_flipped_plays.count('c')/len(their_flipped_plays)
    if opp_c_freq > 0.6:
        return 'c'
    else:
        return 'd'

Pythonがわからない場合は、提出物を擬似コードで書くと、誰か(私またはサイトの別のメンバー)が対応するPythonプログラムを作成できます。

ゲームプレイ

トーナメントランナーは、noisy-gameにあります。実行noisy-game.pyしてトーナメントを実行します。そのリポジトリを新しいサブミッションで更新し続けます。サンプルプログラムはbasic.py

プログラムの総合スコアは、ゲームの100回以上のプレイのスコアの合計です。

ゲームは、それ自体を含む各プレイヤーに対する各プレイヤーのラウンドロビン対戦で構成されます。対戦は100ラウンドで構成されます。ラウンドは300の動きで構成され、各動きには出力'c'または'd'

あなたの提出物は、あなた自身のものを含むすべての提出物と対戦します。各対戦は100ラウンドで構成されます。各ラウンド中、フリップ確率はから一様にランダムに選択され[0, 0.5]ます。

各ラウンドは300手で構成されます。各動きで、両方のプログラムは、試行されたすべての以前の再生、フリップが適用された後、他のプログラムが行ったすべての以前の再生、および状態変数(プログラムが必要に応じて変更できる可変リスト)を受け取ります。プログラムは動きを出力します。

移動のスコアは次のとおりです。プログラムがを再生する'c'場合、相手のプログラムは2ポイントを獲得します。プログラムが再生される場合'd'、そのプログラムは1ポイントを獲得します。

次に、各手は、フリップ確率に等しい確率で独立してフリップされ、対戦相手に見せるために保存されます。

すべてのラウンドがプレイされた後、各プレイヤーが各対戦で獲得したポイント数を合計します。次に、次のスコアリングシステムを使用して、各プレーヤーのゲームのスコアを計算します。このスコアリングは、すべての対戦が完了した後に実行されます。

得点

進化スコアリングを使用します。各プログラムは同じ重みで始まります。次に、ゲームからのポイント合計を使用して、100回の反復に対して、次のように重みが更新されます。

各プログラムの新しいウェイトは、前のウェイトと平均ポイント合計の積に比例し、相手のウェイトで重み付けされます。

このような更新が100回適用され、最終的な重みは、そのゲームの実行に対する各プログラムのスコアです。

全体のスコアは、ゲームの100回の実行の合計になります。

プレーヤーは、このチャレンジに対するすべての有効な回答に加えて、開始するための6つの基本プログラムになります。

注意事項

入力を変更しないでください。他のプログラムの実行に影響を与えようとしないでください。ただし、協力または欠陥がある場合を除きます。別の提出物を認識し、その費用をかけて相手に利益をもたらすことを試みる犠牲的提出物を作成しないでください。標準的な抜け穴は禁止されています。

編集:提出物は、基本プログラムのいずれとも正確に重複していない可能性がありますまたは以前の提出物の。

ご質問がある場合は、お気軽にお問い合わせください。

現在の結果

nicht_genug: 40.6311
stealer: 37.1416
enough: 14.4443
wait_for_50: 6.947
threshold: 0.406784
buckets: 0.202875
change_of_heart: 0.0996783
exploit_threshold: 0.0670485
kickback: 0.0313357
tit_for_stat: 0.0141368
decaying_memory: 0.00907645
tit_for_whoops: 0.00211803
slider: 0.00167053
trickster: 0.000654875
sounder: 0.000427348
tit_for_tat: 9.12471e-05
stubborn_stumbler: 6.92879e-05
tit_for_time: 2.82541e-05
jedi2sith: 2.0768e-05
cooperate: 1.86291e-05
everyThree: 1.04843e-05
somewhat_naive: 4.46701e-06
just_noise: 1.41564e-06
growing_distrust: 5.32521e-08
goldfish: 4.28982e-09
vengeful: 2.74267e-09
defect: 3.71295e-10
alternate: 2.09372e-20
random_player: 6.74361e-21

この質問への回答と、対戦相手のプレイを無視する基本プログラムのみの結果

nicht_genug: 39.3907
stealer: 33.7864
enough: 20.9032
wait_for_50: 5.60007
buckets: 0.174457
kickback: 0.0686975
change_of_heart: 0.027396
tit_for_stat: 0.024522
decaying_memory: 0.0193272
tit_for_whoops: 0.00284842
slider: 0.00153227
sounder: 0.000472289
trickster: 0.000297515
stubborn_stumbler: 3.76073e-05
cooperate: 3.46865e-05
tit_for_time: 2.42263e-05
everyThree: 2.06095e-05
jedi2sith: 1.62591e-05
somewhat_naive: 4.20785e-06
just_noise: 1.18372e-06
growing_distrust: 6.17619e-08
vengeful: 3.61213e-09
goldfish: 3.5746e-09
defect: 4.92581e-10
alternate: 6.96497e-20
random_player: 1.49879e-20

勝ち

コンテストは無期限に開かれたままで、新しい投稿が投稿されます。ただし、この質問が投稿されてから1か月後に、結果に基づいて勝者を宣言します(回答を受け入れます)。


tit_for_whoopsは対戦相手のプレイをどのように無視しますか?
LyricLy

@LyricLyこのカテゴリは、Isaacが提供する基本的なプログラムを指し、相手を無視していると思います。
FryAmTheEggman

1
状態変数を使用して、送信時にすべての動きを記録できるため、実際の動きと反転した動きの両方を把握し、反転確率を推定できることを理解していますか?
xnor

1
@xnorあなたはいつもあなたの本当の動きを伝えられます。反転する可能性があるのは対戦相手の動きだけです。
ニーモニック

1
@isaacg など、exploit_threshold()何度かコピーexploit_threshold1()してみて、playersリストに追加しました。同一の戦略に対して非常に異なる結果が得られるのはなぜですか?
ngn

回答:


4

Genug ist nicht genug

(とも呼ばれることができenough2たりstealback

def nicht_genug(m,t,s):
    if not s:
        s.append("c")
        return "c"
    if s[0]=="t":
        return "d"
    if m[-42:].count("d")>10 or len(t)+t.count("d")>300:
        s[0]="t"
        return "d"
    if t[-1]=="d":
        if s[0]=="d":
            s[0]="c"
            return "d"
        else:
            s[0]="d"
            return "c"
    else:
        if t[-3:].count("d")==0:
            s[0]="c"
        return "c"

2タットの元のシジュウカラは、2タットのように2 タット連続して待つことを学びました。tit_for_whoops実際、以前のシングルタットを許して忘れる必要があるようです。そして、多くのプレイヤーが最終ラウンドで敗北しています。私は今までのところすべてが順調だったときはいいと思っていますが、ボットの許容範囲は低くなり続けています。


11

Tit-For-Whoops

ncase.me/trustの戦略に触発された

def tit_for_whoops(m, t, s):
    if len(t) < 2:
        return 'c'
    else:
        return 'd' if all([x == 'd' for x in t[-2:]]) else 'c'

誤解を防ぐために、他のプレイヤーが連続して2回ディフェクトした場合にのみディフェクトします。


提出ありがとうございます!フリップの確率は平均1/4なので、16動きごとに1回ダブルフリップすることに注意してください。
isaacg

状態変数を追加しました。使用しない場合は無視できます。
isaacg

9

心変わり

def change_of_heart(m, t, s):
    return 'c' if len(t) < 180 else 'd'

途中で心の変化があります。驚くほどうまくいきます。


2位になりました。おめでとうございます。私は、戦略を無視する相手が非常にうまくいくことに感銘を受け、驚いています。
isaacg

9

ストラテジースティーラー

十分な、change_of_heart、およびtit-for-whoopsに触発されました。もう少し寛容でなければなりません。最良の結果を得るために数値を調整しようとしましたが、あまり変更したくありませんでした。

def stealer(mine, theirs, state):
    if len(mine) == 0:
        state.append('c')
        return 'c'
    elif len(mine) > 250:
        return "d"
    elif state[0] == 't':
        return 'd'
    elif mine[-40:].count('d') > 10:
        state[0] = 't'
        return 'd'
    elif theirs[-1] == 'd':
        if state[0] == 'd':
            state[0] = 'c'
            return 'd'
        else:
            state[0] = 'd'
            return 'c'
    elif all([x == 'c' for x in theirs[-3:]]):
        state[0] = 'c'
        return 'c'
    else:
        return 'c'

PPCGへようこそ!
ジュゼッペ

リードしていただき、ありがとうございます!
isaacg

8

シジュウカラ

def tit_for_time(mine, theirs, state):
    theirs = theirs[-30:]
    no_rounds = len(theirs)
    return "c" if no_rounds < 5 or random.random() > theirs.count("d") / no_rounds else "d"

あなたが私を傷つけるのにほとんどの時間を費やしてきたなら、私はあなたを傷つけます。多分。


素敵な投稿!あなたは現在、相手を意識した基本的なプログラムなしで1位にいます。
isaacg

7

高まる不信

import random

def growing_distrust(mine, theirs, state):
    # Start with trust.
    if len(mine) == 0:
        state.append(dict(betrayals=0, trust=True))
        return 'c'

    state_info = state[0]

    # If we're trusting and we get betrayed, trust less.
    if state_info['trust'] and theirs[-1] == 'd':
        state_info['trust'] = False
        state_info['betrayals'] += 1

    # Forgive, but don't forget.
    if random.random() < 0.5 ** state_info['betrayals']:
        state_info['trust'] = True

    return 'c' if state_info['trust'] else 'd'

相手が私を裏切るほど、それが単なるノイズであると信じることができなくなります。


ええ、ノーステートのことは残念ですが、提出物を統一したかったので、これは私が考えることができる最高です。状態を追加する方法についてのアイデアはありますか?
isaacg

stateデフォルトでリストである引数を持っていますか?リストは変更可能であるため、状態は簡単に変更できます。
LyricLy

どうして?それがどうなるかわかりません。
LyricLy

@Mnemonicこれを実装する方法を知っていると思います。私はそれに旋回を与えます。
-isaacg

最初は空のリストであり、変更可能な状態変数を追加しました。
isaacg

7

Jedi2Sith

すべての素晴らしく無私無欲から始まりますが、時間が経つにつれて、ダークサイドの影響は、戻りのない点まで着実に強くなります。この影響を止めることはできませんが、発生していると見られるすべての悪いことが、ダークサイドのパワーに貢献しています...

def jedi2sith(me, them, the_force):
  time=len(them)
  bad_things=them.count('d')
  dark_side=(time+bad_things)/300
  if dark_side>random.random():
    return 'd'
  else:
    return 'c'

オンラインでお試しください!


6

スライダー

def slider(m, t, s):
    z = [[2, 1], [0, 1], [2, 3], [2, 1]]
    x = 0
    for y in t:
      x = z[x][y == 'c']
    return 'c' if x < 2 else 'd'

「c」で始まり、「d」に向かって、または「d」から徐々にスライドします。


状態変数はかなりゆっくり実行されていたため、状態変数を使用するために機能的に同等の書き換えを行いました。ただし、何も変更する必要はありません。
isaacg

6

頑固なつまずき

def stubborn_stumbler(m, t, s):
    if not t:
        s.append(dict(last_2=[], last_3=[]))
    if len(t) < 5:
        return 'c'
    else:
        # Records history to state depending if the last two and three
        # plays were equal
        s = s[0]
        if t[-2:].count(t[-1]) == 2:
            s['last_2'].append(t[-1])
        if t[-3:].count(t[-1]) == 3:
            s['last_3'].append(t[-1])
    c_freq = t.count('c')/len(t)
    # Checks if you've consistently defected against me
    opp_def_3 = s['last_3'].count('d') > s['last_3'].count('c')
    opp_def_2 = s['last_2'].count('d') > s['last_2'].count('c')
    # dist func from 0 to 1
    dist = lambda x: 1/(1+math.exp(-5*(x-0.5)))
    # You've wronged me too much
    if opp_def_3 and opp_def_2:
        return 'd'
    # Otherwise, if you're consistently co-operating, co-operate more
    # the less naive you are
    else:
        return 'c' if random.random() > dist(c_freq) - 0.5 else 'd'

一貫性のあるプレイのみが欠陥と主に協力する間の切り替えを追跡するエクスプロイトしきい値戦略に基づいています

更新:2回の連続プレイと3回の連続プレイの両方を追跡し、過酷な条件下でのみ罰し、不明な場合はランダムに選択します

更新2:条件の削除と配布機能の追加


主導権を握る最初のプログラムを作成する際の矛盾。
isaacg

6

ノイズボット

def just_noise(m,t,s):
    return 'c' if random.random() > .2 else 'd'

私は間違いなく協力ボットです。それはただのノイズです。


6

十分です

def enough(m,t,s):
    if not s:
        s.append("c")
        return "c"
    if s[0]=="t":
        return "d"
    if m[-42:].count("d")>10:
        s[0]="t"
        return "d"
    if t[-1]=="d":
        if s[0]=="d":
            s[0]="c"
            return "d"
        else:
            s[0]="d"
            return "c"
    else:
        return "c"

2つのタットが連続している必要のない2 つのタットのtitとして開始します(とは異なりますtit_for_whoops)。d頻繁にプレイする必要がある場合は、d合計になります。


リードしていただき、ありがとうございます!
isaacg

6

金魚ボット

def goldfish(m,t,s):
    return 'd' if 'd' in t[-3:] else 'c'

金魚は決して許しませんが、すぐに忘れます。


6

トリックスター(再び復活)

最後の10回のプレイのみが考慮されますが、5つの2つのブロックに分割され、それぞれが良または不良として分類されて平均化されます。

対戦相手が平均して「ナイス」でプレーする場合、トリックスターのプレーは次第に悪くなります。結果があいまいな場合、トリックスターは相手を安全に誘い込むのに適しています。相手が「悪い」プレイをしているように見える場合、トリックスターは報復します。

アイデアは、時々ナイーブなプレイヤーからポイントを集め、早い段階で詐欺的なプレイヤーを捕まえることです。

import random
def trickster(player,opponent,state):
    pBad = 0.75
    pNice = 0.8
    pReallyBad =0.1
    decay = 0.98
    r = random.random()
    if len(player)<20: #start off nice
        return 'c' 
    else: #now the trickery begins
        last5 = opponent[-5:].count('c')/5.0 > 0.5
        last5old = opponent[-10:-5].count('c')/5.0  > 0.5
        if last5 and last5old: #she is naive, punish her
            pBad = pBad*decay #Increase punishment
            if r<pBad:
                return 'c'
            else:
                return 'd'
        elif last5 ^ last5old: #she is changing her mind, be nice!
            if r<pNice:
                return 'c'
            else:
                return 'd'
        else: #she's ratting you out, retaliate
            pReallyBad = pReallyBad*decay #Retaliate harder
            if r<pReallyBad:
                return 'c'
            else:
                return 'd'

免責事項:何か間違ったことをしている場合は、ここに投稿したことがありません。


サイトへようこそ!残念ながら、現在あなたのコードは機能していません。他の後にエリフがあります。修正してもらえますか?ありがとう
isaacg

elif以降のすべてをもう一度インデントする必要があると思いますか?
-isaacg

正しい、インデントします。
ヘクトールワートガルド

@isaacg新しいコードで回答を更新しました。質問コメントであなたに伝えるほどの評判はありません。状態変数を正しく使用していることは確かではありませんが、それが空のリストであると仮定します。
ヘクトールワートガルド

2
それは機能しません。各ターンの後に、現在の動きが反転されるかどうかが決定されます(2人のプレイヤーに対して独立して)。その後、その決定は修正されます。あなたはいつも同じ最初の動きを見るでしょう。
クリスチャンシーバーズ

5

減衰メモリ

def decaying_memory(me, them, state):
    m = 0.95
    lt = len(them)

    if not lt:
        state.append(0.0)
        return 'c'

    # If it's the last round, there is no reason not to defect
    if lt >= 299: return 'd'

    state[0] = state[0] * m + (1.0 if them[-1] == 'c' else -1.0)

    # Use a gaussian distribution to reduce variance when opponent is more consistent
    return 'c' if lt < 5 or random.gauss(0, 0.4) < state[0] / ((1-m**lt)/(1-m)) else 'd'

最近の歴史をもっと重くする。ゆっくりと過去を忘れます。


5

キックバック

def kickback(m, t, s):
  if len(m) < 10:
    return "c"
  td = t.count("d")
  md = m.count("d")
  f = td/(len(t)+1)
  if f < 0.3:
    return "d" if td > md and random.random() < 0.1 else "c"
  return "c" if random.random() > f+2*f*f else "d"

曖昧なアイデア...


アダプティブベーシックスペルが削除されたバージョンでリードしていただき、ありがとうございます。
isaacg

ありがとう。2つの結果がどれほど異なるかは驚くべきことだと思います!
クリスチャンシーバーズ

4

本当に「ノイズ」を完全に取得していない

def vengeful(m,t,s):
    return 'd' if 'd' in t else 'c'

裏切り者を決して許しません。


4

サウンダー:

編集:おそらく低ノイズのシナリオで報復を追加

基本的に、最初の4つの動きすべてが協調している場合、それは通常よりも少ないノイズを期待する必要があることを意味します。絶対に欠陥がないことから得られるより少ないポイントを補うために、頻繁に少しずつ欠陥を作成し、それをノイズのせいにすることができます。彼らは私たちに対して欠陥がある場合にも報復します

対戦相手がそれらのターン(2つ以上)で多くのディフェンスを行った場合、私たちはそれらに戻ってディフェクトします。それがただのノイズである場合、ノイズはとにかく私たちの動きに影響を与えます。

それ以外の場合、1つの動きだけが欠陥である場合、ゲームの残りの部分を単純に行うだけです。

def sounder(my, their, state):
    if len(my)<4:
        if their.count("d")>1:
            return "d"
        return "c"
    elif len(my) == 4:
        if all(i == "c" for i in their):
            state.append(0)
            return "d"
        elif their.count("c") == 3:
            state.append(1)
            return "c"
        else:
            state.append(2)
    if state[0] == 2:
        return "d"
    if state[0] == 0:
        if not "d" in my[-4:]:
            return "d"
        return their[-1]
    else:
        return their[-1]

3

代わりの

def alternate(m, t, s):
    if(len(m)==0):
        return 'c' if random.random()>.5 else 'd'
    elif(len(m)>290):
        return 'd'
    else:
        return 'd' if m[-1]=='c' else 'c'

最初のラウンドでランダムに選んでから、交互に選びます。常に最後の10ラウンドの欠陥。


3

50を待つ

def wait_for_50(m, t, s):
  return 'c' if t.count('d') < 50 else 'd'

50の欠陥の後、それらを手に入れましょう!


あなたの意図を維持しながら、私はあなたのpythonを修正しました。
isaacg

3位になったおめでとうございます。
isaacg

2

サムワットナイーブ

def somewhat_naive(m, t, s):
    p_flip = 0.25
    n = 10
    if len(t) < n:
        return 'c' if random.random() > p_flip else 'd'
    d_freq = t[-n:].count('d')/n
    return 'c' if d_freq < p_flip else 'd'

最後に(大体)フリップ確率よりも少ないディフェクトをした場合、 nターンで、それはノイズであり、あなたが意地悪であるとは思わないでしょう!

最高のnを把握していないので、さらに詳しく調べてください。


2

エブリスリー

def everyThree(me,him,s):
    if len(me) % 3 == 2:
        return "d"
    if len(me) > 250:
        return "d"
    if him[-5:].count("d")>3:
        return "d"
    else:
        return "c"

関係なく、3ターンごとに欠陥があります。また、最後の50ターンに欠陥があります。また、相手が最後のラウンドの5つのうち4つをディフェクトした場合はディフェクトします。


2

バケット

def buckets(m, t, s):
    if len(m) <= 5:
        return 'c'
    if len(m) >= 250:
        return 'd'
    d_pct = t[-20:].count('d')/len(t[-20:])
    if random.random() > (2 * d_pct - 0.5):
        return 'c'
    else:
        return 'd'

はじめまして。最後の20を調べ、<25%dの場合、cを返し、> 75%dを返し、dを返し、その間で線形確率関数に沿ってランダムに選択します。最後の50、欠陥。これは最後の10個でしたが、最後の50個の欠陥がたくさんありました。

ここでは初めてなので、何かを修正する必要があるかどうか(またはこれをテストする方法)を教えてください。


ローカルでテストしたい場合は、リポジトリを複製してnoisy-game.pyを実行できます。しばらく時間がかかるのでplayers、迅速な反復のためにいくつかの対戦相手を削除することができます。
-isaacg

アイザックに感謝します-それをいじって、いじくりまわす必要があります。
brian_t

1

シジュウカラ

相手が半分以上の時間でディフェクトした場合のディフェクト。

def tit_for_stat(m, t, s):
  if t.count('d') * 2 > len(m):
    return 'd'
  else:
    return 'c'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.