嘈杂的囚徒困境


35

在此挑战中,您将扮演吵杂的囚徒困境。

囚徒困境是博弈论中一个场景,其中有两名球员,每两种选择:合作或者缺陷。如果每个球员都缺阵,他们会为自己做得比他们合作时更好。

反复犯人的困境是相同的游戏,只是您反复与同一个对手比赛,并且您知道对手过去玩过什么。您的目标始终是为自己积累最高分,而不管对手的表现如何。

嘈杂的囚徒困境使沟通变得有些混乱。您对对手过去玩过的游戏的了解会带来一些干扰。您还将了解过去的举动。面对同一个对手的回合中,噪声率是恒定的,但是不同回合之间的噪声率却不同。

挑战

在这个挑战中,您将编写一个Python 3程序来解决嘈杂的被囚徒的困境。

您的程序将收到三个输入:

  • 您自己的动作,无需应用随机翻转。

  • 对手的动作,并应用随机翻转。

  • 状态变量,每轮以一个空列表开头,可以根据需要进行修改。如果您不想使用它,则可以忽略它。

您的程序应输出'c'以配合或'd'缺陷。

例如,这是一个程序,如果对手过去至少60%的时间在应用了随机翻转之后并且在前10次翻转中进行了合作,则该程序可以进行合作:

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步。在每次移动时,两个程序都将收到他们尝试过的所有先前播放的声音,以及在应用了翻转之后另一个程序已经做出的所有先前播放的声音,以及一个状态变量,该变量是可变的列表,程序可以根据需要对其进行修改。程序将输出其动作。

动作得分如下:如果一个程序播放a 'c',则相反的程序将获得2分。如果某个程序播放'd',则该程序将获得1分。

然后,以等于翻转概率的概率独立地翻转每个动作,并存储以显示给对手。

在完成所有回合之后,我们求和每个对战中每个球员获得的积分。然后,我们使用以下评分系统来计算每个玩家的游戏得分。在所有比赛完成后执行此评分。

计分

我们将使用进化评分。每个程序以相等的权重开始。然后,使用游戏中的总点数,权重更新如下,进行100次迭代:

每个程序的新权重与之前权重和平均总分的乘积成正比,再乘以其对手的权重。

应用100次此类更新,最终权重是该游戏运行时每个程序的得分。

总得分将是游戏100遍的总和。

玩家将是应对挑战的所有有效答案,另外还有六个基本程序可以帮助我们入门。

注意事项

请勿修改输入。除非通过合作或背叛,否则不要试图影响任何其他程序的执行。不要做出牺牲性尝试,试图认出另一种提议,并自费让该对手受益。禁止出现标准漏洞

编辑:提交的内容可能与任何基本程序或任何较早的提交内容均不完全相同。

如果你有任何问题随时问。

当前结果

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

Answers:


4

Genug ist nicht genug

(也可以称为enough2stealback

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"

我了解到,原来的两次刺的山雀确实像连续两次一样等待着tit_for_whoops,实际上我们似乎应该原谅和忘记(好吧,几乎...)以前的一次刺。在最后一轮中,很多球员都处于劣势。到目前为止一切都还不错,但我仍然希望保持友善,但该机器人的公差范围一直在降低。


11

针锋相对

受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'

仅当另一位玩家连续两次叛逃时才出现缺陷,以防止误会。


感谢您的提交!请记住,由于翻转概率平均为1/4,因此每16步左右移动一次。
isaacg

我添加了一个状态变量,如果您不想使用它,可以忽略它。
isaacg

9

心变

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

在途中改变了主意。做得好得令人惊讶。


恭喜您取得领先/第二名。令我印象深刻的是,一个忽略策略的对手表现如此出色。
isaacg

9

策略窃取者

受到足够的启发,change_of_heart和针锋相对的灵感。应该多一点宽容。我试图调整数字以获得最佳结果,但是他们并不想改变太多。

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"

如果您大部分时间都在伤害我,那么我只会伤害您。大概。


不错的提交!您目前处于第一位,没有对手意识的基本程序。
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 '18年

只是有一个state默认情况下是列表的参数?列表是可变的,因此状态可以轻松修改。
LyricLy

为何如此?我不知道怎么回事。
LyricLy

@Mnemonic我想我知道该怎么实现。我会旋转的。
isaacg '18年

我添加了一个状态变量,该变量最初是一个空列表,您可以对其进行修改。
isaacg

7

绝地武士

开始时一切都是美好而无私的,但是随着时间的流逝,黑暗面的影响会逐渐增强,直到无路可退。不能阻止这种影响,但是它看到的所有坏事只会助长黑暗面的力量...

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”。


我对它进行了功能上等效的重写以使用状态变量,因为它运行得很慢。但是,您无需更改任何内容。
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:删除条件并添加了分发功能


编写第一个带头程序的矛盾!
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"

从两次尝试的山雀开始,两次尝试不必连续(不像tit_for_whoops)。如果必须d经常播放,则d总计。


恭喜您带头!
isaacg

6

金鱼机器人

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

金鱼永远不会原谅,但很快就会忘记。


6

骗子(再次恢复)

仅计入了最后10场比赛,但分为5块的两个区块,将其平均分为好坏。

如果对手平均打“不错”,那么骗子的打法就会越来越好。如果结果是模棱两可的,那么骗子就可以很好地诱使对手进入安全状态。如果对手似乎在打“坏”牌,则骗子会进行报复。

这样做的想法是不时从天真的玩家那里收集点数,同时尽早发现那些欺骗性的东西。

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

我猜从小数点开始的所有内容都应该再次缩进?
isaacg

正确,我会缩进。
Hektor-Waartgard '18年

@isaacg我已经用新代码更新了答案。我的声誉不足,无法在问题评论中告诉您。我不确定我是否正确使用了状态变量,我认为它是一个空列表,可以在其中附加任何我想添加的内容,对吗?
Hektor-Waartgard '18年

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

谢谢。我认为这两个结果有多么不同很神奇!
Christian Sievers

4

并没有真正获得整个“噪音”

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

永远不要原谅叛徒。


4

发声器:

编辑:在可能低噪声的情况下增加了报复

基本上,如果前四个动作全部配合,则意味着我们所期望的噪音比平时要少。每隔一段时间就会出现一点缺陷,以弥补我们从永远不会缺陷中获得的更少的点数,并能够将其归咎于噪音。如果他们背叛我们,我们也会进行报复

如果我们的对手在这些回合中有很多叛逃(2个或更多),我们只会背叛他们。如果只是噪音,噪音还是会影响我们的行动。

否则,如果只有一招是缺陷,我们将在剩下的游戏中进行简单的针锋相对。

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'

在第一轮中随机选择,然后轮流选择。在最近十轮中始终存在缺陷。


3

等待50

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

经过50个缺陷后,让我们拥有它!


我在保留您意图的同时修复了python。
isaacg

祝贺您进入第三名。
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"

不论每三个回合都有缺陷。最后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

谢谢Isaac-我将不得不使用它并做一些修补。
brian_t

1

针锋相对

如果对手叛逃的时间超过一半,则为缺陷。

def tit_for_stat(m, t, s):
  if t.count('d') * 2 > len(m):
    return 'd'
  else:
    return 'c'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.