白象交换


11

现在是七月的圣诞节,所以有什么比虚拟的白象礼物交换更好的庆祝方式!

对于这次的“山丘之王”挑战,您必须创建一个在“白象”交流模拟中发挥作用的机器人,力争获得最高价值的礼物。

游戏规则

  • 游戏将进行很多回合,每个回合由不同的回合数组成。
  • 回合设置:游戏中会出现与玩家人数一样多的礼物,每个礼物在[0 ... 1)范围内均一地随机赋值,直到“打开”礼物时该值才是未知的。玩家将被随机排列在队列中。第一个玩家将从队列的前面弹出。
  • 轮到一个玩家时,他们可以打开礼物或偷走另一个玩家的礼物,然后将回合传递给被盗礼物的玩家。
    • 每个礼物最多可被盗3次。
    • 您不能从刚刚从您身上偷走的玩家那里窃取钱财。
    • 每个玩家一次只能有一个礼物。
  • 打开礼物后,播放前进到从队列前面弹出的下一个播放器。这将是下一个尚未转牌的玩家。
  • 回合结束:打开所有礼物后,回合结束,每个玩家持有的礼物的价值将添加到该玩家的分数中。新一轮比赛开始,每个玩家现在都没有礼物,而玩家的顺序也被打乱了。
  • 游戏结束:当至少一名玩家获得100 500分时,游戏将结束,并以最高总礼物价值授予该玩家胜利。

编码

所有提交都应与Python 3.7兼容。您必须编写一个直接继承自的类WhiteElephantBot。例如:

class FooBot(WhiteElephantBot):
    # Your implementation here

您可以在bot类中提供必须调用的__init__方法(采用一个参数namesuper().__init__(name)。您的类必须具有take_turn按以下顺序期望以下参数的方法:

  • players:依次列出所有尚没有礼物的球员的球员名称。
  • presents:将玩家名称映射到2元组的字典,其中包含该玩家所拥有的当前值以及当前被盗的次数。这仅包括当前持有礼物的其他玩家。
  • just_stole:如果最后采取的动作是抢断,这将是刚偷走的玩家的名字。如果不是,它将为None

每个参数都是不可变的,也可以是新的对象,因此,对它们进行任何更改都不会影响游戏。如果您愿意,可以保留任何参数的副本。

的示例值presents

{
    'Alice':   (0.35, 0),
    'Bob':     (0.81, 2),
    'Charlie': (0.57, 1)
}

您的take_turn方法应返回您想从中窃取或None打开礼物的玩家的名字。如果引发异常,返回str或以外的值None,或者返回您不能从中窃取的玩家的姓名,则默认情况下将打开礼物。

您的构造函数将在每个回合开始时被调用,因此您不会记​​住每个回合的状态。

通过继承自WhiteElephantBot,您将可以访问一种steal_targets方法,该方法将使用present dict just_stole并返回可以从中窃取的玩家的姓名列表。

您脚本所需的任何模块都必须在条目顶部导入。

测试驱动

可以在这里找到测试驱动程序。您无需from white_elephant import WhiteElephantBot在发布的答案中包含,但是本地模块将需要这样做。

基准竞争对手

  • 随机:随机选择是打开新礼物还是要偷窃,并随机选择一致的偷窃目标。
  • 贪婪:偷走可以偷的最有价值的礼物。如果没有礼物可以偷,打开礼物。
  • 尼斯:总是打开一个新的礼物。永远不要偷。

附加规则

  • 捕获所有异常是您的责任。如果您的班级未能捕获到异常,它将被取消比赛资格。另外,请不要捕获KeyboardInterrupts。
  • 不要使用文件或其他方法绕过无法保存游戏之间状态的功能。例如,您可能不会将神经网络状态保存到运行中的文件中。
  • 您的机器人程序必须独立于类代码和相关常量。
  • 您只能使用标准库导入。
  • 没有严格的性能要求。要合理谨慎。如果性能成为问题,我保留增加时间限制的权利。
  • 每人一次。如果您提交多个条目,则您的机器人可能无法一起使用。我现在将允许每人多个条目,但是如果出现问题我稍后可能会禁止它。
  • 这是一场公开比赛,没有明确的结束日期。发生重大更改时,只要有能力,它将在任何时间重新运行。

编辑1:将获胜分数从100更改为500,以使排名更加一致。测试驱动程序具有一个新的错误修正,还反映了获胜分数的变化。

EDIT2:澄清有关所需进口的说明。


页首横幅(截至2018年8月8日)

  1. SampleBot(500.093)
  2. LastMinuteBot(486.163)
  3. 罗宾·霍德(463.160)
  4. 奇托德(448.825)
  5. 贪婪的机器人(438.520)
  6. SecondPlaceBot(430.598)
  7. ThresholdBot(390.480)
  8. 赌徒(313.362)
  9. NiceBot(275.536)
  10. 随机机器人(256.172)
  11. 好撒玛利亚人(136.298)

连续可以有多少次抢断?当我上场比赛时,通常一连串的抢断限制是2次,某物,并且第三人必须打开一个。这样可以防止同一礼物每回合被盗多次。
mbomb007 '18

@ mbomb007是的。除非有其他规则可以使某些礼物不受偷窃,否则链条窃取是无限的:每个礼物只能被偷3次,并且您不能从刚刚从您那里偷走的玩家那里偷走。
Beefster

您能偷一件礼物然后重新偷走原来的礼物吗?
暴民埃里克(Erik the Outgolfer)

@EriktheOutgolfer:是的,只要之间有另一个转弯。您的礼物失窃后,您无法立即进行重新窃取。
Beefster

1
洋基交换!?下一步,共享生日聚会?
ngm

Answers:


3

LastMinuteBot

(非常感谢@Mnemonic提供了代码的框架,因为我几乎不了解Python。)

class LastMinuteBot(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):
        targets = self.steal_targets(presents, just_stole)
        if len(targets) <= 1:
            return None

        target = None

        # If most of the presents are already distributed, try to steal an 
        #  un-restealable gift of high value
        if len(presents) > (len(players) + len(presents)) * 0.75:
            at_threshold = [t for t in targets if presents[t][1]==2 and presents[t][0]>=0.8]
            if at_threshold:
                target = max(at_threshold, key=lambda x: presents[x][0])

        # Otherwise, take the best available
        if not target:
            target = max(targets, key=lambda x: presents[x][0])

        return target if presents[target][0] > 0.5 else None

利用礼物不能被偷的次数超过三次的事实,如果发现高价值的礼物并且大多数礼物已被打开,则第三次偷走自己。


简单而美丽
r_j

2

奇托德

class OddTodd(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):

        targets = self.steal_targets(presents, just_stole)

        # if none to steal, pick present
        if len(targets) <= 1:
            return None

        # steals the best gift that he can, as long as he's the 1st/3rd steal
        targets = [t for t in targets if presents[t][1] % 2 == 0]
        if targets:
            return max(targets, key=lambda x:presents[x][0])

        else:
            return None

窃取他可能会得到的最好的礼物,但又不想成为偷礼物的第二个人,因为如果礼物被他偷走,他将无法取回。


第11行的语法错误。您需要在列表理解中使用a ==而不是a =
Beefster

固定,谢谢!不要过多使用Python。
brian_t

1

SecondPlaceBot

class SecondPlaceBot(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):
        targets = self.steal_targets(presents, just_stole)
        if len(targets) <= 1:
            return None

        # If most of the presents are already distributed, take the second best.
        if len(presents) > (len(players) + len(presents)) * 0.8:
            target = sorted(targets, key=lambda x: presents[x][0])[-2]
        # Otherwise, take the best and hope someone steals it later.
        else:
            target = max(targets, key=lambda x: presents[x][0])

        return target if presents[target][0] > 0.5 else None

每个人都将争取最有价值的礼物。下一个最好的礼物几乎一样好,但被盗的可能性要小得多。


1

阈值启动

import random

class ThresholdBot(WhiteElephantBot):
    def __init__(self, name):
        self.name = name
        # Choose a minimum value to be happy.
        self.goal = 1 - random.random() ** 2

    def take_turn(self, players, presents, just_stole):
        # Find who has a gift that's sufficiently valuable.
        targets = self.steal_targets(presents, just_stole)
        targets = [x for x in targets if presents[x][0] >= self.goal]
        targets = sorted(targets, key=lambda x: presents[x][0])

        if not targets:
            return None

        # Choose a target (biased toward the best gifts).
        weighted = []
        for i, target in enumerate(targets, 1):
            weighted += [target] * i ** 2
        return random.choice(weighted)

我们真的不关心获得最好的礼物,只是足够好的东西。只要有值得偷的东西,我们就会做。


1

SampleBot

import random

class SampleBot(WhiteElephantBot):
    def rollout(self, values, counts, just_stole, next_move):
        targets = set()
        move_chosen = False
        for i, (v, n) in enumerate(zip(values, counts)):
            if v and n < 3 and i != just_stole and i != 0:
                targets.add(i)
        for i in range(len(values)):
            if values[i]:
                break
            while True:
                if not targets:
                    break
                if move_chosen:
                    j = max(targets, key=lambda i: values[i])
                    if values[j] < 0.5:
                        break
                else:
                    move_chosen = True
                    if next_move is None:
                        break
                    j = next_move
                values[i] = values[j]
                counts[i] = counts[j] + 1
                values[j] = 0
                counts[j] = 0
                if just_stole is not None and counts[just_stole] < 3:
                    targets.add(just_stole)
                if j in targets:
                    targets.remove(j)
                just_stole = i
                i = j
            values[i] = random.random()
            for player in (just_stole, i):
                if player is not None and values[player] and counts[player] < 3:
                    targets.add(player)
        return values[0]
    def take_turn(self, players, presents, just_stole, n_rollouts=2000):
        names = [self.name] + players + list(presents.keys())
        values = [presents[name][0] if name in presents else None for name in names]
        counts = [presents[name][1] if name in presents else 0 for name in names]
        if just_stole is not None:
            just_stole = names.index(just_stole)
        targets = [None]
        for i, (v, n) in enumerate(zip(values, counts)):
            if v and n < 3 and i != just_stole and i != 0:
                targets.append(i)
        if len(targets) == 1:
            return targets[0]
        scores = [0. for _ in targets]
        n = n_rollouts // len(targets)
        for i, target in enumerate(targets):
            for _ in range(n):
                scores[i] += self.rollout(list(values), list(counts), just_stole, target) / float(n)
        target_index = targets[scores.index(max(scores))]
        if target_index is None:
            return None
        return names[target_index]

运行2000个模拟,让每个玩家贪婪地行动,并选择最佳动作。


这个机器人到底做什么?
Beefster

@Beefster在每个玩家贪婪的行为下运行2000次随机游戏,并选择平均最终得分最高的棋步。
user1502040

名称错误。您需要随机导入。
Beefster

1

罗宾汉

class RobinHood(WhiteElephantBot):       
    def take_turn(self, players, presents, just_stole):
        #get the possible steal targets
        targets = self.steal_targets(presents, just_stole)
        #who stole his gift?
        targets = [x for x in targets if presents[x][1] > 0]
        #sort by value
        targets = sorted(targets, key=lambda x: presents[x][0])        
        #only steal back if it's worth it        
        targets = [x for x in targets if presents[x][0] > 0.5]

        if len(targets)>0:
           return targets.pop()

从没有赚到礼物的富人那里偷走


您有缩进错误。
Beefster '18

0

好撒玛利亚人

class GoodSamaritan(WhiteElephantBot):     
    def take_turn(self, players, presents, just_stole):  
        targets = self.steal_targets(presents, just_stole)

         #if only one player has a gift, don't steal it!
        if len(presents)<=1 or len(targets)==0:
             return None
        else:       
             #Steal the worst present  
             return min(targets, key=lambda x: presents[x][0])

给不幸的人带来另一次发财的机会


0

赌徒

class Gambler(WhiteElephantBot):
    def take_turn(self, players, presents, just_stole):        
        #get the possible steal targets
        targets = self.steal_targets(presents, just_stole)        

        #last player 
        if len(players)==0:
            #lets gamble! Try and get the highest score
            return None

        #If you are not last, steal the best gift that can be restolen so maybe you can become the last player
        targets = [t for t in targets if presents[t][1]<2 ]
        if targets:
            return max(targets, key=lambda x: presents[x][0])   

赌徒上瘾了,他试图成为最后一个玩家,然后他在新礼物上赌博,击败了所有其他玩家。


0

Top3Bot

class Top3Bot(WhiteElephantBot):
    def __init__(self, name):
        super().__init__(name)
        self.firstturn = True

    def take_turn(self, players, presents, just_stole):
        if self.firstturn:
            num_presents = len(players) + len(presents) + 1
            self.value_limit = (num_presents - 3) / num_presents
            self.firstturn = False

        targets = self.steal_targets(presents, just_stole)

        if players:
            targets += None

        return max(
            targets,
            key=lambda name: self.steal_ranking(name, presents, len(players))
        )


    def steal_ranking(self, name, presents, presents_remaining):
        if name is None:
            return (0, 0)

        present_value = presents[name][0]
        num_steals = presents[name][1]
        if present_value >= self.value_limit:
            if num_steals == 2:
                return (5, present_value)
            elif  num_steals == 0:
                return (4, -presemt_value)
            elif num_steals == 1 and presents_remaining == 0:
                return (3, -present_value)
            else:
                return (-1, present_value)
        else:
            if num_steals < 2:
                return (2, present_value)
            else:
                return (-2, present_value)

该机器人不会尝试获取尽可能最好的礼物,而是尝试获取价值> =(n-3)/ n的礼物,其中n是礼物的数量。在大多数情况下,都会有如此高价的礼物,Top3Bot会尝试使用其中一种,但是他并不真正在乎哪一个。


__init__缺少self论据
Beefster
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.