죄수의 트렐 레마 첫 라운드 인

과제 상태 : 열기

내가 봇을 잃어 버렸다면 의견을 말하거나 PR을 열거 나 그렇지 않으면 나에게 소리 지른다.


죄수의 딜레마 … 세 가지 선택. 미쳤어?

여기에 지불 행렬이 있습니다. 왼쪽의 플레이어 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()각 라운드마다 불리며 마지막 라운드에서 상대의 선택 (또는 첫 라운드 인 경우 없음)

한 달 안에 우승자에게 50 개의 보상 현상금이 있습니다.

사양

  • 모든 봇은 [편집 됨] 라운드에서 자신을 포함하여 다른 모든 봇 (1 대 1)을 플레이합니다.
  • 표준 허점은 허용되지 않습니다.
  • 수업 이외의 다른 손잡은 셰 나니 가인을 엉망으로 만들지 마십시오.
  • 최대 5 개의 봇을 제출할 수 있습니다.
  • 예, 악수를 구현할 수 있습니다.
  • 이외의 모든 응답은 C, N또는 D자동으로 이동합니다 N.
  • 그들이하는 모든 게임에서 각 봇의 점수는 합산됩니다.

제어 장치

검사!

다른 언어

누군가가 필요하면 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


답변

평가자 봇

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가 최적이어야하므로 이점이있을 수 있음을 제외하고 이전에 제출 된 모든 봇에 대해 이기고 자신에 대해 지속적인 추첨을합니다.


답변

내쉬 균형

이 봇은 대학에서 게임 이론 수업을 들었지만 게으 르며 반복되는 게임을 다루는 수업에는 가지 않았습니다. 그래서 그는 단일 게임 혼합 내쉬 평형을 재생합니다. 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"

지속적인 학대 내쉬 균형

이 봇은 게으른 형제에게서 교훈을 얻었습니다. 그의 게으른 형제의 문제는 고정 전략을 이용하지 않았다는 것입니다. 이 버전은 상대방이 일정한 플레이어인지 titfortat인지 확인하고 그에 따라 재생합니다. 그렇지 않으면 일반 내쉬 평형을 재생합니다.

유일한 단점은 자신을 상대로 한 턴당 평균 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

답변

TatForTit

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

이 봇은 지불 행렬을 올바르게 읽은 경우 TitForTat가 CDCD를 교체하는 동안 라운드 당 3 포인트의 평균 순 이득을 얻기 위해 DNDN을 번갈아 선택합니다. 나는 이것이 TitForTat에 대해 최적이라고 생각합니다. 분명히 TFT가 아닌 상대를 감지하고 다른 전략을 채택하는 것이 향상 될 수는 있지만 원래 현상금을 목표로했습니다.


답변

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

이 봇은 최근 게임 상태의 이전 발생을 검색하여 패턴 일치와 더 최근 일치를 선호하는 상대가 해당 발생에 어떻게 반응했는지 확인한 다음 상대방의 예상 이동을 “이길”수있는 동작을 재생합니다. 추적하는 모든 데이터로 더 똑똑해질 수있는 여지가 많이 있지만 작업 할 시간이 부족합니다.


답변

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'

낙관적으로 시작하지만 상대방이 협조하기를 거부함에 따라 점점 더 쓰다듬어집니다. 아마도 조정될 수있는 많은 마법 상수들이 있지만 이것은 아마도 시간을 정당화하기에 충분하지 않을 것입니다.


답변

앙상블

이것은 관련 모델의 앙상블을 실행합니다. 개별 모델은 서로 다른 양의 이력을 고려하고 항상 예상 지불금 차이를 최적화하는 이동을 선택하거나 예상 지불금 차이에 비례하여 이동을 임의로 선택할 수 있습니다.

그런 다음 앙상블의 각 구성원은 선호하는 움직임에 투표합니다. 그들은 상대방보다 얼마나 많은 돈을 얻었는지와 같은 많은 표를 얻습니다 (끔찍한 모델은 부정적인 표를 얻습니다). 그런 다음 투표에서 승리 한 것이 선택됩니다.

(아마도 각각의 선호도에 비례하여 투표권을 분할해야하지만 지금은 그렇게하기에는 충분하지 않습니다.)

EvaluaterBot 및 PatternFinder를 제외한 지금까지 게시 된 모든 내용을 능가합니다. (일대일로 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"

테스트 프레임 워크

다른 사람이 유용하다고 생각되면 여기 개별 매치업을보기위한 테스트 프레임 워크가 있습니다. 파이썬 2. 관심있는 모든 상대를 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

답변

OldTitForTat

올드 스쿨 플레이어는 새로운 규칙을 업데이트하기에는 너무 게으르다.

class OldTitForTat:
    def round(self, last):
        if(last == None)
            return "C"
        if(last == "C"):
            return "C"
        return "D"