KOTH - Loaded RPS


12

Konkurs trwale otwarty - zaktualizowany 10 sierpnia 2017 r

Mimo że 5 czerwca 2017 roku ogłosiłem zwycięzcę (który zostanie zachowany jako najlepsza odpowiedź), będę przeglądał nowe boty i aktualizował wyniki.

Wyniki z 5 czerwca

Gratulacje dla użytkownika 1502040

Ponieważ nie ma remisów, pokazuję tylko% wygranych meczów.

Statistician2- 95,7%
Fitter- 89,1%
Nash- 83,9%
Weigher- 79,9%
ExpectedBayes- 76,4%
AntiRepeater- 72,1%
Yggdrasil- 65,0%
AntiGreedy- 64,1%
Reactor- 59,9%
NotHungry- 57,3%
NashBot- 55,1%
Blodsocer- 48,6%
BestOfBothWorlds- 48,4%
GoodWinning- 43,9%
Rockstar- 40,5%
ArtsyChild- 40,4%
Assassin- 38,1 %
WeightedRandom- 37,7%
Ensemble- 37,4%
UseOpponents- 36,4%
GreedyPsychologist- 36,3%
TheMessenger- 33,9%
Copycat- 31,4%
Greedy- 28,3%
SomewhatHungry- 27,6%
AntiAntiGreedy- 21,0%
Cycler- 20,3%
Swap- 19,8%
RandomBot- 16,2%

Utworzyłem Arkusz Google z siatką wyników każdego parowania: https://docs.google.com/spreadsheets/d/1KrMvcvWMkK-h1Ee50w0gWLh_L6rCFOgLhTN_QlEXHyk/edit?usp=sharing


Dzięki dylematowi Petri mogłem poradzić sobie z tym Królem Wzgórza.

Gra

Gra jest prosta „Rock-Paper-Scissors” z niespodzianką: Punkty zdobywane z każdym wzrostem zwycięstwa podczas meczu (twoje R, P lub S są ładowane).

  • Paper wygrywa Rock
  • Nożyczki wygrywają papier
  • Rock wygrywa nożyczkami

Zwycięzca otrzymuje tyle punktów, ile obciążenia w swojej grze.

Przegrany zwiększa o 1 obciążenie swojej gry.

W przypadku remisu każdy gracz zwiększa obciążenie swojej gry o 0,5.

Po 100 grach wygrywa ten, który ma więcej punktów.

np .: P1 ma obciążenia [10,11,12] (kamień, papier, nożyczki) i P2 [7,8,9]. P1 gra R, P2 gra P. P2 wygrywa i otrzymuje 8 punktów. Obciążenia P1 stają się [11,11,12], obciążenia P2 pozostają takie same.

Specyfikacja wyzwania

Twój program musi być napisany w języku Python (przepraszam, nie wiem, jak inaczej go obsłużyć). Masz utworzyć funkcję, która będzie traktować każdą z tych zmiennych jako argument przy każdym wykonaniu:

my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history

points - Aktualne punkty (twoje i twojego przeciwnika)

loaded- Tablica z ładunkami (w kolejności RPS) (twoja i twój przeciwnik)

history- Łańcuch ze wszystkimi zagraniami, ostatnia postać to ostatnia gra (twoja i twój przeciwnik)

Musisz wrócić "R", "P"albo "S". Jeśli zwrócisz coś innego, będzie to automatyczna przegrana meczu.

Zasady

Nie można zmieniać wbudowanych funkcji.

Testowanie

Będę na bieżąco aktualizować Gita za pomocą kodu i wszystkich botów kompilujących: https://github.com/Masclins/LoadedRPS

Osądzać

Zwycięzca zostanie wyłoniony przez wybór osoby, która wygra najwięcej meczów po 1000 pełnych rundach. Remisy zostaną zerwane przez remisy. Rozgrywanych jest 1000 meczów, a nie jeden, ponieważ spodziewam się dużo losowości. W ten sposób losowość byłaby mniej istotna.

Możesz przesłać do 5 botów.

Konkurs kończy się 4 lipca (to będzie ostatni dzień, w którym przyjmę odpowiedź), a 5 lipca opublikuję ostatnie stadiony (może postaram się zamieścić awans).


Ponieważ jest to mój pierwszy KOTH, jestem w 100% otwarty na zmiany w celu ulepszeń, takich jak liczba meczów rozegranych z każdym botem.

Edytowano do 1000 dopasowań, ponieważ widzę, że naprawdę wiąże się to z przypadkowością.


z kilkoma losowymi botami, naprawdę chcesz stworzyć wiele gier z wielu rund
Destructible Lemon

@DestructibleLemon Myślałem o tym, aby każdy bot grał przeciwko sobie trzy razy, a nie jeden raz. Widząc, że myślisz podobnie, zrobię to.
Masclins

1
(naprawdę potrzebujesz całkiem sporej liczby, ponieważ niektóre probability naprawdę rozciągają się na wiele dopasowań. zobacz mojego bota, gdzie może się zdenerwować, ale prawdopodobnie nie będzie z dużą ilością dopasowań)
Zniszczalna Cytryna

1
Cieszę się, że moje pytanie pomogło ci to uruchomić, @AlbertMasclans!
Gryphon

2
@AlbertMasclans Czy możesz opublikować pełny skrypt testowy (w tym runcodei bots)?
CalculatorFeline

Odpowiedzi:


8

Statystyka (już nie gra)

import random
import collections

R, P, S = moves = range(3)
move_idx = {"R": R, "P": P, "S": S}
name = "RPS"
beat = (P, S, R)
beaten = (S, R, P)

def react(_0, _1, _2, _3, _4, opp_history):
    if not opp_history:
        return random.randrange(0, 3)
    return beat[opp_history[-1]]

def anti_react(_0, _1, _2, _3, _4, opp_history):
    if not opp_history:
        return random.randrange(0, 3)
    return beaten[opp_history[-1]]

def random_max(scores):
    scores = [s + random.normalvariate(0, 1) for s in scores]
    return scores.index(max(scores))

def greedy_margin(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    scores = [my_loaded[move] - opp_loaded[beat[move]] for move in moves]
    return random_max(scores)

def anti_greedy(my_points, opp_pints, my_loaded, opp_loaded, my_history, opp_history):
    scores = [-my_loaded[move] for move in moves]
    return random_max(scores)

def recent_stats(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    opp_history = opp_history[-10:-1]
    counts = collections.Counter(opp_history)
    scores = [(counts[beaten[move]] + 1) * my_loaded[move] - 
              (counts[beat[move]] + 1) * opp_loaded[move] for move in moves]
    return random_max(scores)

def statistician(_0, _1, _2, _3, my_history, opp_history):
    m1 = []
    o1 = []
    my_loaded = [0] * 3
    opp_loaded = [0] * 3
    my_points = 0
    opp_points = 0
    strategies = [react, anti_react, greedy_margin, anti_greedy, recent_stats]
    strategy_scores = [0 for _ in strategies]
    for i, (mx, ox) in enumerate(zip(my_history, opp_history)):
        mx = move_idx[mx]
        ox = move_idx[ox]
        for j, strategy in enumerate(strategies):
            strategy_scores[j] *= 0.98
            move = strategy(my_points, opp_points, my_loaded, opp_loaded, m1, o1)
            if move == beat[ox]:
                strategy_scores[j] += my_loaded[move]
            elif move == beaten[ox]:
                strategy_scores[j] -= opp_loaded[ox]
        m1.append(mx)
        o1.append(ox)
        if mx == beat[ox]:
            opp_loaded[ox] += 1
            my_points += my_loaded[mx]
        elif mx == beaten[ox]:
            my_loaded[mx] += 1
            opp_points += opp_loaded[ox]
        else:
            my_loaded[mx] += 0.5
            opp_loaded[ox] += 0.5
    strategy = strategies[random_max(strategy_scores)]
    return name[strategy(my_points, opp_points, my_loaded, opp_loaded, m1, o1)]

Przełącza między kilkoma prostymi strategiami opartymi na oczekiwanej wydajności w przeszłości

Statystyka 2

import random
import collections
import numpy as np

R, P, S = moves = range(3)
move_idx = {"R": R, "P": P, "S": S}
names = "RPS"
beat = (P, S, R)
beaten = (S, R, P)

def react(my_loaded, opp_loaded, my_history, opp_history):
    if not opp_history:
        return random.randrange(0, 3)
    counts = [0, 0, 0]
    counts[beat[opp_history[-1]]] += 1
    return counts

def random_max(scores):
    scores = [s + random.normalvariate(0, 1) for s in scores]
    return scores.index(max(scores))

def argmax(scores):
    m = max(scores)
    return [s == m for s in scores]

def greedy_margin(my_loaded, opp_loaded, my_history, opp_history):
    scores = [my_loaded[move] - opp_loaded[beat[move]] for move in moves]
    return argmax(scores)

recent_counts = None

def best_move(counts, my_loaded, opp_loaded):
    scores = [(counts[beaten[move]] + 0.5) * my_loaded[move] - 
              (counts[beat[move]] + 0.5) * opp_loaded[move] for move in moves]
    return argmax(scores)

def recent_stats(my_loaded, opp_loaded, my_history, opp_history):
    if len(opp_history) >= 10:
        recent_counts[opp_history[-10]] -= 1
    recent_counts[opp_history[-1]] += 1
    return best_move(recent_counts, my_loaded, opp_loaded)

order2_counts = None

def order2(my_loaded, opp_loaded, my_history, opp_history):
    if len(my_history) >= 2:
        base0 = 9 * my_history[-2] + 3 * opp_history[-2]
        order2_counts[base0 + opp_history[-1]] += 1
    base1 = 9 * my_history[-1] + 3 * opp_history[-1]
    counts = [order2_counts[base1 + move] for move in moves]
    return best_move(counts, my_loaded, opp_loaded)

def nash(my_loaded, opp_loaded, my_history, opp_history):
    third = 1.0 / 3
    p = np.full(3, third)
    q = np.full(3, third)
    u = np.array(my_loaded)
    v = np.array(opp_loaded)
    m0 = np.zeros(3)
    m1 = np.zeros(3)
    lr = 0.2
    for _ in range(10):
        de0 = u * np.roll(q, 1) - np.roll(v * q, 2)
        de1 = v * np.roll(p, 1) - np.roll(u * p, 2)
        m0 = 0.9 * m0 + 0.1 * de0
        m1 = 0.9 * m1 + 0.1 * de1
        p += lr * m0
        q += lr * m1
        p[p < 0] = 0
        q[q < 0] = 0
        tp, tq = np.sum(p), np.sum(q)
        if tp == 0 or tq == 0:
            return np.full(3, third)
        p /= tp
        q /= tq
        lr *= 0.9
    return p

strategies = [react, greedy_margin, recent_stats, order2, nash]

predictions = strategy_scores = mh = oh = None

def statistician2func(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    global strategy_scores, history, recent_counts, mh, oh, predictions, order2_counts
    if not opp_history:
        strategy_scores = [0 for _ in strategies]
        recent_counts = collections.Counter()
        order2_counts = collections.Counter()
        mh, oh = [], []
        predictions = None
        return random.choice(names)
    my_move = move_idx[my_history[-1]]
    opp_move = move_idx[opp_history[-1]]
    if predictions is not None:
        for j, p in enumerate(predictions):
            good = beat[opp_move]
            bad = beaten[opp_move]
            strategy_scores[j] += (my_loaded[good] * p[good] - opp_loaded[opp_move] * p[bad]) / sum(p)
    mh.append(my_move)
    oh.append(opp_move)
    predictions = [strategy(my_loaded, opp_loaded, mh, oh) for strategy in strategies]
    strategy = random_max(strategy_scores)
    p = predictions[strategy]
    r = random.random()
    for i, pi in enumerate(p):
        r -= pi
        if r <= 0:
            break
    return names[i]

Nash

import numpy as np
import random

def nashfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    third = 1.0 / 3
    p = np.full(3, third)
    q = np.full(3, third)
    u = np.array(my_loaded)
    v = np.array(opp_loaded)
    m0 = np.zeros(3)
    m1 = np.zeros(3)
    lr = 0.2
    for _ in range(10):
        de0 = u * np.roll(q, 1) - np.roll(v * q, 2)
        de1 = v * np.roll(p, 1) - np.roll(u * p, 2)
        m0 = 0.9 * m0 + 0.1 * de0
        m1 = 0.9 * m1 + 0.1 * de1
        p += lr * m0
        q += lr * m1
        p[p < 0] = 0
        q[q < 0] = 0
        tp, tq = np.sum(p), np.sum(q)
        if tp == 0 or tq == 0:
            return random.choice("RPS")
        p /= tp
        q /= tq
        lr *= 0.9
    r = random.random()
    for i, pi in enumerate(p):
        r -= pi
        if r <= 0:
            break
    return "RPS"[i]

Oblicza przybliżoną równowagę Nasha na podstawie spadku gradientu.


1
Bardzo podoba mi się to podejście i rozumiem, dlaczego chcesz być w stanie utrzymać stan między rundami. Mimo że widzę ogromny problem ze zmianą, biorąc pod uwagę liczbę zgłoszeń. Wezmę to pod uwagę przy kolejnych wyzwaniach (których oczekuję po zakończeniu).
Masclins,

5

Waga

Straciłem rozumowanie podczas eksperymentowania z kodem, ale podstawową ideą jest oszacowanie prawdopodobieństwa ruchu przeciwnika przez ostatnie 3 ruchy przy użyciu niektórych wag i pomnożenie ich przez inną wagę, która zależy od obciążeń. Myślałem, że też mogę jakoś użyć my_loaded, ale nie mogłem zdecydować, jak to zrobić, więc pominęłem.

def weigher(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    idx = {"R": 0, "P": 1, "S": 2}
    sc = [0, 0, 0]
    for i, m in enumerate(reversed(opp_history[-3:])):
        sc[idx[m]] += (1 / (1 + i))

    for i in range(3):
        sc[i] *= (opp_loaded[i] ** 2)

    return "PSR"[sc.index(max(sc))]

szatan

Prawdopodobnie zostanie zdyskwalifikowany, ponieważ jest to rodzaj oszustwa i przyjmuje pewne założenia dotyczące funkcji testowania (musi mieć funkcję przeciwnika w zmiennej na ramce stosu), ale technicznie nie łamie żadnych obecnych reguł - nie przedefiniuj lub przepisz wszystko. Po prostu używa czarnej magii, aby wykonać funkcję przeciwnika, aby zobaczyć, co zrobiła / zrobi tura. Nie radzi sobie z przypadkowością, ale deterministyczne boty nie mają szans na pokonanie Szatana.

def satan(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    import inspect, types
    f = inspect.currentframe()
    s = f.f_code.co_name
    try:
        for v in f.f_back.f_locals.values():
            if isinstance(v, types.FunctionType) and v.__name__ != s:
                try:
                    return "PSR"[{"R": 0, "P": 1, "S": 2}[
                        v(opp_points, my_points, opp_loaded, my_loaded, opp_history, my_history)]]
                except:
                    continue
    finally:
        del f

Bez wątpienia najlepszy pod względem rezultatów prostoty
Masclins

Nawiasem mówiąc, aby użyć, my_loadedmożesz dodać wagę, która ceni ruch, który straciłby w stosunku do twojego ostatniego ruchu (ruchów). To tak, jakby zakładać, że przeciwnik zrobi coś podobnego do tego, co zrobiłeś, a zatem karać go za to, że będziesz nadal grał tak samo. Coś w stylu:for i, m in enumerate(reversed(my_history[-3:])): sc[(idx[m]+1)%3] += (K / (1 + i))
Masclins

@AlbertMasclans dodał inne rozwiązanie
Nazwa wyświetlana

1
Naprawdę lubię Szatana. Ale jak powiedziałeś, uważam, że nie powinno się to kwalifikować: nawet jeśli nie złamie żadnej wyraźnej reguły, jest to wyraźnie sprzeczne z duchem gry. Gratulujemy pomysłu!
Masclins

4

Monter

Ten bot ulepsza Wzór i łączy go z Ekonomistą (Wzór i Ekonomista nie będą już brać udziału)

Ulepszenie Wzorca polega na tym, że Bot szuka teraz dwóch dwóch rodzajów wzorów: Przeciwnik reagujący na jego ostatnią grę i przeciwnik reagujący na moją ostatnią grę. Następnie ocenia obie prognozy, aby zastosować tę, która najlepiej pasuje.

Na podstawie tego wzoru Bot ma teraz prawdopodobieństwo dla R, P i S. Biorąc to pod uwagę i oczekiwaną wartość każdej gry (tak jak zrobił to Ekonomista), Bot gra tę, która daje największą wartość.

import random
import numpy as np
def fitterfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
        t = len(opp_history)
        RPS = ["R","P","S"]
        if t <= 2:
                return RPS[t]
        elif t == 3:
                return random.choice(RPS)

        def n(c): return RPS.index(c)

        total_me = np.zeros(shape=(3,3))
        total_opp= np.zeros(shape=(3,3))
        p_me = np.array([[1/3]*3]*3)
        p_opp = np.array([[1/3]*3]*3)

        for i in range(1, t):
                total_me[n(my_history[i-1]), n(opp_history[i])] += 1
                total_opp[n(opp_history[i-1]), n(opp_history[i])] += 1
        for i in range(3):
                if np.sum(total_me[i,:]) != 0:
                        p_me[i,:] = total_me[i,:] / np.sum(total_me[i,:])
                if np.sum(total_opp[i,:]) != 0:
                        p_opp[i,:] = total_opp[i,:] / np.sum(total_opp[i,:])

        error_me = 0
        error_opp = 0

        for i in range(1, t):
                diff = 1 - p_me[n(my_history[i-1]), n(opp_history[i])]
                error_me += diff * diff
                diff = 1 - p_opp[n(opp_history[i-1]), n(opp_history[i])]
                error_opp += diff * diff

        if error_me < error_opp:
                p = p_me[n(my_history[-1]),:]
        else:
                p = p_opp[n(opp_history[-1]),:]


# From here, right now I weight values, though not 100% is the best idea, I leave the alternative in case I'd feel like changing it
        value = [(p[2]*my_loaded[0] - p[1]*opp_loaded[1], "R"), (p[0]*my_loaded[1] - p[2]*opp_loaded[2], "P"), (p[1]*my_loaded[2] - p[0]*opp_loaded[0], "S")]
        value.sort()

        if value[-1][0] > value[-2][0]:
                return value[-1][1]
        elif value[-1][0] > value[-3][0]:
                return random.choice([value[-1][1], value[-2][1]])
        else:
                return random.choice(RPS)

#       idx = p.tolist().index(max(p))
#       return ["P", "S", "R"][idx]

Oto dwa stare kody

Wzór (nie gra już)

Wzór próbuje znaleźć wzory na swoim przeciwniku. Wygląda na to, w co grał przeciwnik po ostatniej grze, którą wykonał (przykładając większą wagę do ostatnich gier). Dzięki temu odgaduje, co będzie grał przeciwnik, i gra przeciw temu.

import random
import numpy as np
def patternfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
        if len(opp_history) == 0:
                return random.choice(["R","P","S"])
        elif len(opp_history) == 1:
                if opp_history == "R":
                        return "P"
                elif opp_history == "P":
                        return "S"
                elif opp_history == "S":
                        return "R"

        p = np.array([1/3]*3)
        c = opp_history[-1]
        for i in range(1, len(opp_history)):
                c0 = opp_history[i-1]
                c1 = opp_history[i]
                if c0 == c:
                        p *= .9
                        if c1 == "R":
                                p[0] += .1
                        elif c1 == "P":
                                p[1] += .1
                        elif c1 == "S":
                                p[2] += .1

        idx = p.tolist().index(max(p))
        return ["P", "S", "R"][idx]

Ekonomista (już nie gra)

The Economist wykonuje następujące czynności: Odgaduje prawdopodobieństwo każdej gry przeciwnika, obserwując, w co grał w ostatnich 9 turach. Na tej podstawie oblicza oczekiwaną korzyść z każdej gry i idzie z tą, która ma najlepszą oczekiwaną wartość.

import random
def economistfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
        if len(opp_history) == 0:
                return random.choice(["R","P","S"])
        if len(opp_history) > 9:
                opp_history = opp_history[-10:-1]
        p = [opp_history.count("R"), opp_history.count("P"), opp_history.count("S")]

        value = [(p[2]*my_loaded[0] - p[1]*opp_loaded[1], "R"), (p[0]*my_loaded[1] - p[2]*opp_loaded[2], "P"), (p[1]*my_loaded[2] - p[0]*opp_loaded[0], "S")]
        value.sort()

        if value[-1][0] > value[-2][0]:
                return value[-1][1]
        elif value[-1][0] > value[-3][0]:
                return random.choice([value[-1][1], value[-2][1]])
        else:
                return random.choice(["R","P","S"])

4

Yggdrasil

Nazywa się to „Yggdrasil”, ponieważ patrzy w przyszłość w drzewie gry. Ten bot nie wykonuje żadnej prognozy przeciwnika, po prostu stara się utrzymać przewagę statystyczną, jeśli ją otrzyma (poprzez zrównoważenie obecnych i przyszłych zysków). Oblicza w przybliżeniu idealną strategię mieszaną i zwraca ruch wybrany losowo z tymi wagami. Gdyby ten bot był doskonały (co nie jest, ponieważ funkcja wyceny stanu jest dość zła i nie wygląda daleko w przyszłość), nie byłoby możliwe pokonanie tego bota przez ponad 50% czasu. Nie wiem, jak dobrze ten bot poradzi sobie w praktyce.

def yggdrasil(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    cache = {}
    def get(turn, ml, ol):
        key = str(turn) + str(ml) + str(ol)
        if not key in cache:
            cache[key] = State(turn, ml, ol)
        return cache[key]

    def wrand(opts):
        total = sum(abs(w) for c,w in opts.items())
        while True:
            r = random.uniform(0, total)
            for c, w in opts.items():
                r -= abs(w)
                if r < 0:
                    return c
            print("error",total,r)

    class State():
        turn = 0
        ml = [1,1,1]
        ol = [1,1,1]
        val = 0
        strat = [1/3, 1/3, 1/3]
        depth = -1
        R = 0
        P = 1
        S = 2
        eps = 0.0001
        maxturn = 1000

        def __init__(self, turn, ml, ol):
            self.turn = turn
            self.ml = ml
            self.ol = ol
        def calcval(self, depth):
            if depth <= self.depth:
                return self.val
            if turn >= 1000:
                return 0
            a = 0
            b = -self.ol[P]
            c = self.ml[R]
            d = self.ml[P]
            e = 0
            f = -self.ol[S]
            g = -self.ol[R]
            h = self.ml[S]
            i = 0
            if depth > 0:
                a += get(self.turn+1,[self.ml[R]+1,self.ml[P],self.ml[S]],[self.ol[R]+1,self.ol[P],self.ol[S]]).calcval(depth-1)
                b += get(self.turn+1,[self.ml[R]+2,self.ml[P],self.ml[S]],[self.ol[R],self.ol[P],self.ol[S]]).calcval(depth-1)
                c += get(self.turn+1,[self.ml[R],self.ml[P],self.ml[S]],[self.ol[R],self.ol[P],self.ol[S]+2]).calcval(depth-1)
                d += get(self.turn+1,[self.ml[R],self.ml[P],self.ml[S]],[self.ol[R]+2,self.ol[P],self.ol[S]]).calcval(depth-1)
                e += get(self.turn+1,[self.ml[R],self.ml[P]+1,self.ml[S]],[self.ol[R],self.ol[P]+1,self.ol[S]]).calcval(depth-1)
                f += get(self.turn+1,[self.ml[R],self.ml[P]+2,self.ml[S]],[self.ol[R],self.ol[P],self.ol[S]]).calcval(depth-1)
                g += get(self.turn+1,[self.ml[R],self.ml[P],self.ml[S]+2],[self.ol[R],self.ol[P],self.ol[S]]).calcval(depth-1)
                h += get(self.turn+1,[self.ml[R],self.ml[P],self.ml[S]],[self.ol[R],self.ol[P]+2,self.ol[S]]).calcval(depth-1)
                i += get(self.turn+1,[self.ml[R],self.ml[P],self.ml[S]+1],[self.ol[R],self.ol[P],self.ol[S]+1]).calcval(depth-1)
            self.val = -9223372036854775808
            for pr in range(0,7):
                for pp in range(0,7-pr):
                    ps = 6-pr-pp
                    thisval = min([pr*a+pp*d+ps*g,pr*b+pp*e+ps*h,pr*c+pp*f+ps*i])
                    if thisval > self.val:
                        self.strat = [pr,pp,ps]
                        self.val = thisval
            self.val /= 6


            if depth == 0:
                self.val *= min(self.val, self.maxturn - self.turn)
            return self.val

    turn = len(my_history)
    teststate = get(turn, [x * 2 for x in my_loaded], [x * 2 for x in opp_loaded])
    teststate.calcval(1)
    return wrand({"R":teststate.strat[R],"P":teststate.strat[P],"S":teststate.strat[S]})

usuń komentarze, które nie czynią kodu bardziej zrozumiałym
Nazwa wyświetlana

@SargeBorsch zrobione
PhiNotPi

1
@PhiNotPi Wiem, że nie opublikowałem żadnego ograniczenia czasowego, ale Yggdrasil zabiera więcej niż minutę każdemu przeciwnikowi. Czy można by to trochę zoptymalizować?
Masclins

tak, jest nieznośnie powolny
Nazwa wyświetlana

@AlbertMasclans na minutę na przeciwnika, czy masz na myśli całkowitą 1 minutę dla wszystkich gier z przeciwnikiem? Mogę też spróbować przyspieszyć, ale tak naprawdę nie wiem, jak to zrobić, wygląda na to, że jest o 1 krok naprzód.
PhiNotPi

4

Anti-Repeater

from random import choice
def Antirepeaterfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    s = opp_history.count("S")
    r = opp_history.count("R")
    p = opp_history.count("P")

    if s>p and s>r:
        return "R"
    elif p>s and p>r:
        return "S"
    else:
        return "P"

Podnosi papier w pierwszej turze, po czym zwraca to, co bije to, co przeciwnik zrobił najwięcej, wybierając papier w przypadku remisu.

Copycat

import random
def copycatfunc(I,dont,care,about,these,enmoves):
    if not enmoves:
        return random.choice(["R","P","S"])
    else:
        return enmoves[len(enmoves)-1]

Wystarczy skopiować ostatni ruch przeciwnika.

Anti-Anti-Greedy

from random import choice
def antiantigreedy(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    if opp_loaded[0] > opp_loaded[1] and opp_loaded[0] > opp_loaded[2]:
        return "S"
    if opp_loaded[1] > opp_loaded[0] and opp_loaded[1] > opp_loaded[2]:
        return "R"
    if opp_loaded[2] > opp_loaded[0] and opp_loaded[2] > opp_loaded[1]:
        return "P"
    else:
        return choice(["R","P","S"])

Wybiera cokolwiek, co przegrywa z najcięższym wyborem przeciwnika.

Nieco głodny

from random import choice
def somewhathungryfunc(blah, blah2, load, blah3, blah4, blah5):
    if load[0] > load[1] and load[0] < load[2] or load[0] < load[1] and load[0] > load[2]:
        return "R"
    if load[1] > load[0] and load[1] < load[2] or load[1] < load[0] and load[1] > load[2]:
        return "P"
    if load[2] > load[1] and load[2] < load[0] or load[2] < load[1] and load[2] > load[0]:
        return "S"
    else:
        return choice(["R","P","S"])

3

Posłaniec

def themessengerfunc (I, no, not, need, these, arguments): return "P"

Gwiazda rocka

def rockstarfunc (I, no, no, need, these, arguments): return "R"

Morderca

def assassinfunc (I, no, not, need, these, arguments): return "S"

Wyjaśnienie

Teraz możesz myśleć, że te boty są całkowicie głupie.

nie do końca prawdą, są one w rzeczywistości oparte na pomyśle, zebranie ogromnej premii, a wróg popełnia błąd i dostaje się z nim do ściany.

teraz te boty grają bardzo podobnie do chciwych, są jednak prostsze i nie wybierają losowo, dopóki nie obciążą jednej broni, trzymają się wybranej broni.

Kolejna rzecz do zapamiętania: każdy z nich pobije chciwość mniej więcej w połowie czasu, losując jedną trzecią czasu i tracąc jedną szóstą czasu. kiedy wygrają, będą często wygrywać. dlaczego to?

Chciwy, dopóki nie przegra rundy, losowo wybierze broń. oznacza to, że jeśli nie wygra rundy, ponownie wybierze broń losowo, co może być znowu zwycięską. jeśli chciwy ciągnie lub przegrywa, trzyma się tej broni. jeśli chciwy wygrywa co najmniej jedną rundę, to wybiera tę samą broń co bot, chciwy wygrywa. jeśli chciwy wybierze przegrywającą broń w pewnym momencie, nasz bot wygra, ponieważ obciążenie naszej broni byłoby wyższe niż wynik chciwości.

Zakładając, że zachłanność nie zawsze wybiera zwycięską broń przypadkowo, oznacza to, że szanse są następujące:

1/3: {1/2 wygranej (w sumie 1/6). 1/2 straty (w sumie 1/6). }

1/3 losowania

1/3 wygranej

więc: 1/3 szansy na remis, 1/6 szansy na przegraną, 1/2 szansy na wygraną.

prawdopodobnie oznacza to, że musisz zrobić wiele gier w wielu rundach

mają one przede wszystkim na celu sprostanie wyzwaniu


3

Reaktor

Sprawia, że ​​gra wygrałaby poprzednią rundę.

import random
def reactfunc(I, dont, need, all, these, opp_history):
    if not opp_history:
        return random.choice(["R","P","S"])
    else:
        prev=opp_history[len(opp_history)-1]
        if prev == "R":
            return "P"
        if prev == "P":
            return "S"
        else:
            return "R"

1
Można wymienić opp_history[len(opp_history)-1]z opp_history[-1].
CalculatorFeline

3

Artsy Child

Ten bot zachowuje się jak dziecko bawiące się sztuką i rzemiosłem, zacznie od papieru i losowo użyje papieru lub nożyczek, ale nie będzie używać nożyczek po kamieniu lub nożyczkach, ponieważ musi używać nożyczek na papierze. Rzuci kamieniem w każdego, kto rzuci w nią kamieniem.

import random
def artsychildfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    if len(opp_history) == 0:
            return "P"
    elif opp_history[-1] == "R":
            return "R"
    elif my_history[-1] != "P":
            return "P"
    else:
            return random.choice(["P", "S"])

2

Oto trzy boty, które zbudowałem do testowania:


RandomBot

import random
def randombotfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
        return random.choice(["R","P","S"])

Chciwy

Po prostu wybiera najbardziej obciążoną opcję.

import random
def greedyfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
        if my_loaded[0] > my_loaded[1]:
                if my_loaded[0] > my_loaded[2]:
                        return "R"
                elif my_loaded[0] < my_loaded[2]:
                        return "S"
                else:
                        return random.choice(["R","S"])
        elif my_loaded[0] < my_loaded[1]:
                if my_loaded[1] > my_loaded[2]:
                        return "P"
                elif my_loaded[1] < my_loaded[2]:
                        return "S"
                else:
                        return random.choice(["P","S"])
        else:
                if my_loaded[0] > my_loaded[2]:
                        return random.choice(["R","P"])
                elif my_loaded[0] < my_loaded[2]:
                        return "S"
                else:
                        return random.choice(["R","P","S"])

Antykoncepcja

Zakłada, że ​​przeciwnik zagra chciwy i gra zwycięską alternatywą.

import random
def antigreedyfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
        if opp_loaded[0] > opp_loaded[1]:
                if opp_loaded[0] > opp_loaded[2]:
                        return "P"
                elif opp_loaded[0] < opp_loaded[2]:
                        return "R"
                else:
                        return "R"
        elif opp_loaded[0] < opp_loaded[1]:
                if opp_loaded[1] > opp_loaded[2]:
                        return "S"
                elif opp_loaded[1] < opp_loaded[2]:
                        return "R"
                else:
                        return "S"
        else:
                if opp_loaded[0] > opp_loaded[2]:
                        return "P"
                elif opp_loaded[0] < opp_loaded[2]:
                        return "R"
                else:
                        return random.choice(["R","P","S"])

1

Nie głodny

def nothungryfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    if my_loaded[0] < my_loaded[1]:
            if my_loaded[0] < my_loaded[2]:
                    return "R"
            elif my_loaded[0] > my_loaded[2]:
                    return "S"
            else:
                    return random.choice(["R","S"])
    elif my_loaded[0] > my_loaded[1]:
            if my_loaded[1] < my_loaded[2]:
                    return "P"
            elif my_loaded[1] > my_loaded[2]:
                    return "S"
            else:
                    return random.choice(["P","S"])
    else:
            if my_loaded[0] < my_loaded[2]:
                    return random.choice(["R","P"])
            elif my_loaded[0] > my_loaded[2]:
                    return "S"
            else:
                    return random.choice(["R","P","S"])

Jest to dosłownie odwrotność Chciwego, wybiera opcję najniższych dostępnych punktów.


1

Użyj ulubionego przeciwnika

from collections import Counter
import random
def useopponents(hi, my, name, is, stephen, opp_history):
  if opp_history:
    data = Counter(opp_history)
    return data.most_common(1)[0][0]
  else:
    return random.choice(["R","P","S"])

W pierwszej turze wybiera losowy przedmiot. Co drugą turę używa najczęstszego wyboru przeciwnika. Jeśli jest remis, domyślnie wybierany jest najwcześniejszy najczęściej wybierany wybór.

// Ukradłem stąd kod


Zwycięstwo jest dobre

import random
def goodwinning(no, yes, maybe, so, my_history, opp_history):
  if opp_history:
    me = my_history[len(my_history)-1]
    you = opp_history[len(opp_history)-1]
    if you == me:
      return goodwinning(no, yes, maybe, so, my_history[:-1], opp_history[:-1])
    else:
      if me == "R":
        if you == "P":
          return "P"
        else:
          return "R"
      elif me == "P":
        if you == "S":
          return "S"
        else:
          return "R"
      else:
        if you == "R":
          return "R"
        else:
          return "P"
  else:
    return random.choice(["R","P","S"])

Zwraca wybór zwycięzcy poprzedniej rundy. Jeśli poprzednia runda była remisowa, rekurencyjnie sprawdza rundę wcześniej. Jeśli były to tylko remisy lub pierwsza runda, zwraca losowy wybór.


1

Najlepsze z obu światów

Ten bot w zasadzie łączy Anti-Greedy i Greedy (stąd nazwa).

def bobwfunc(a, b, my_loaded, opp_loaded, c, d):
    opp_max = max(opp_loaded)
    opp_play = "PSR"[opp_loaded.index(opp_max)]

    my_max = max(my_loaded)
    my_play = "RPS"[my_loaded.index(my_max)]

    if opp_play == my_play:
        return opp_play
    else:
        return my_play if opp_max < my_max else opp_play

To jest Antigreedy, już opublikowany jako przykład.
Masclins,

@AlbertMasclans Zmieniłem go na innego bota.
clismique

findjest na smyczki. my_loadedi opp_loadedobie są listami. indexpowinno być dobre dla tego, czego chcesz.
Masclins

@AlbertMasclans Whoops, naprawione teraz. Dzięki za haczyk! Mam nadzieję, że to nie jest kolejny dup ... Nie chcę ponownie usuwać tego postu.
clismique

W porządku, dziękuję za grę
Masclins,

1

NashBot

import random
def nashbotfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    r = opp_loaded[0] * opp_loaded[2]
    p = opp_loaded[0] * opp_loaded[1]
    s = opp_loaded[1] * opp_loaded[2]
    q = random.uniform(0, r + p + s) - r
    return "R" if q < 0 else "P" if q < p else "S"

Losowo wybiera pomiędzy trzema opcjami w taki sposób, że przeciwnik statystycznie nie ma preferencji między ruchami w odniesieniu do tego, ile zdobędzie; innymi słowy, zarówno Chciwi, jak i Niegłodni powinni mieć taki sam średni oczekiwany wynik przeciwko niemu.


1

Oczekiwany

Edycja: Zaktualizowany ranking

To jest nowy najwyższy ranking po uwzględnieniu Expectedbayes:

  • statystyki2func 91,89%
  • fitterfunc 85,65%
  • nashfunc 80,40%
  • weigherfunc 76,39%
  • oczekiwanebayesfunc 73,33%
  • antirepeaterfunc 68,52%
  • ...

Objaśnienia

(Uwaga: przesłanie 05.06.2017)

Ten bot próbuje zmaksymalizować oczekiwaną wartość następnego ruchu poprzez:

  • Obliczanie prawdopodobieństwa każdego kolejnego możliwego ruchu przeciwnika
  • Wykorzystanie tej liczby i obciążeń do obliczenia oczekiwanej wartości dla każdego z R, P i S.
  • Wybór ruchu, który ma najwyższą oczekiwaną wartość
  • Losowe wybieranie wartości, jeśli przewidywanie nie powiodło się

Prawdopodobieństwa są aktualizowane co dziesięć ruchów. Liczba przeszłych ruchów użytych do obliczenia prawdopodobieństw została ustawiona na 10 dla każdego bota (więc ogółem 20 funkcji). Prawdopodobnie jest to za dużo danych, ale nie próbowałem dalej sprawdzać.

Opiera się na bibliotece scikit do obliczania prawdopodobieństwa ruchu przeciwnika (mówię to w przypadku, gdy źle przeczytałem zasady i nie było to dozwolone).

Z łatwością wygrywa z botami, które zawsze dokonują tego samego wyboru. Zaskakujące jest to, że jest dość skuteczny przeciwko losowemu botowi ze wskaźnikiem wygranych 93% (uważam, że wynika to z faktu, że ogranicza liczbę punktów, które może zdobyć jego przeciwnik, jednocześnie maksymalizując własną liczbę możliwych punktów w każdej rundzie).

Zrobiłem szybką próbę z turą 100 i tylko ograniczoną liczbą botów, i oto, co otrzymałem z wynik_zrozumienia:

  • randombotfunc, 35
  • nashbotfunc, 333
  • greedyfunc, 172
  • antigreedyfunc, 491
  • themessengerfunc, 298
  • rockstarfunc, 200
  • statistician2func, 748
  • fitterfunc, 656
  • oczekiwano Bayesfunc, 601

Co nie jest takie złe!

from sklearn.naive_bayes import MultinomialNB
import random

#Number of past moves used to compute the probability of next move
#I did not really try to make such thing as a cross-validation, so this number is purely random
n_data = 10

#Some useful data structures
choices = ['R','P','S']
choices_dic = {'R':0,'P':1,'S':2}
point_dic = {(0,0):0,(1,1):0,(2,2):0, #Same choices
             (0,1):-1,(0,2):1, #me = rock
             (1,0):1,(1,2):-1, #me = paper
             (2,0):-1,(2,1):1} #me = scissor

def compute_points(my_choice,opp_choice,my_load,opp_load):
    """
    Compute points
    @param my_choice My move as an integer
    @param opp_choice Opponent choice as an integer
    @param my_load my_load array
    @param opp_load opp_load array
    @return A signed integer (+ = points earned, - = points losed)
    """
    points = point_dic[(my_choice,opp_choice)] #Get -1, 0 or 1
    if points > 0:
        return points*my_load[my_choice] 
    else:
        return points*opp_load[opp_choice]

#This use to be a decision tree, before I changed it to something else. Nevertheless, I kept the name
class Decision_tree:
    def __init__(self):
        self.dataX = []
        self.dataY = []
        self.clf = MultinomialNB()

    def decide(self,my_load,opp_load,my_history,opp_history):
        """
        Returns the decision as an integer

        Done through a try (if a prediction could be made) except (if not possible)
        """
        try:
            #Let's try to predict the next move
            my_h = list(map(lambda x: choices_dic[x],my_history[-n_data:-1]))
            opp_h = list(map(lambda x: choices_dic[x],opp_history[-n_data:-1]))
            pred = self.clf.predict_proba([my_h+opp_h])
            #We create a points array where keys are the available choices
            pts = []
            for i in range(3):
                #We compute the expected gain/loss for each choice
                tmp = 0
                for j in range(3):
                    tmp += compute_points(i,j,my_load,opp_load)*pred[0][j]
                pts.append(tmp)
            return pts.index(max(pts)) #We return key for the highest expected value
        except:
            return random.choice(range(3))

    def append_data(self,my_history,opp_history):
        if my_history == "":
            self.clf = MultinomialNB()
        elif len(my_history) < n_data:
            pass
        else:
            my_h = list(map(lambda x: choices_dic[x],my_history[-n_data:-1]))
            opp_h = list(map(lambda x: choices_dic[x],opp_history[-n_data:-1]))
            self.dataX = self.dataX + [my_h+opp_h]
            self.dataY = self.dataY + [choices_dic[opp_history[-1:]]]

            if len(self.dataX) >= 10:
                self.clf.partial_fit(self.dataX,self.dataY,classes=[0,1,2])

                self.dataX = []
                self.dataY = []


#Once again, this is not actually a decision tree
dt = Decision_tree()

#There we go:
def expectedbayesfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    dt.append_data(my_history,opp_history)
    choice = choices[dt.decide(my_loaded,opp_loaded,my_history,opp_history)]
    return choice

Witamy w PPCG i miły pierwszy post!
Zacharý

Wielkie dzięki! Długo chciałem uczestniczyć w PPCG. Teraz jest naprawione!
lesibius

0

Cycler

def cycler(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    return "RPS"[len(myhistory)%3]

0


0

Ensemble

from random import *
def f(I):
    if I==0:return "R"
    if I==1:return "P"
    return "S"
def b(I):
    if I=="R":return 0
    if I=="P":return 1
    return 2
def Ensemble(mp,op,ml,ol,mh,oh):
    A=[0]*3
    B=[0]*3
    if(len(oh)):
        k=b(oh[-1])
        A[k-2]+=0.84
        A[k]+=0.29
        for x in range(len(oh)):
            g=b(oh[x])
            B[g-2]+=0.82
            B[g]+=0.22
        s=sum(B)
        for x in range(len(B)):
            A[x]+=(B[x]*1.04/s)
        r=max(A)
    else:
        r=randint(0,3)
    return f(r)

Kilka konkurencyjnych algorytmów głosuje na najlepsze rozwiązanie.

Zamiana

from random import *
def f(I):
    if I==0:return "R"
    if I==1:return "P"
    return "S"
def b(I):
    if I=="R":return 0
    if I=="P":return 1
    return 2
def Swap(mp,op,ml,ol,mh,oh):
    A=[0]*3
    B=[0]*3
    if(len(mh)):
        r=(b(mh[-1])+randint(1,2))%3
    else:
        r=randint(0,3)
    return f(r)

Wykonuje losowy ruch, ale zrobił to bez powtarzania ostatniego ruchu.


0

blodsocer

towarzystwo

Naprawiłem to, więc mam nadzieję, że teraz powinno działać

Znowu coś pomieszałem, więc usunąłem i usunąłem. Robię dużo bałaganu.

def blodsocerfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    import random
    # tuned up an ready to go hopeful
    # s o c e r y
    if len(my_history) > 40 and len(set(opp_history[-30:])) == 1:
        if opp_history[-1] == "S":
            return "R"
        elif opp_history[-1] == "R":
            return "P"
        else:
            return "S"
        # against confused bots that only do one thing most of the time.
    elif len(my_history)>30 and min(opp_history.count(i) for i in "RPS")/max(opp_history.count(i) for i in "RPS") >0.8:
        return "RPS"[my_loaded.index(max(my_loaded))] # This is so if the other bot is acting errratic
                                                      # the max bonus is used for advantage
    elif len(my_history) < 10:
        if len(my_history) > 2 and all(i == "S" for i in opp_history[1:]):
            if len(my_history) > 5: return "S"
            return "P"
        return "S" # Be careful, because scissors are SHARP
    elif len(set(opp_history[1:10])) == 1 and len(my_history) < 20:
        if opp_history[1] == "S":
            return "R"
        elif opp_history[1] == "R":
            return "R"
        else:
            return "P"
    elif len(opp_history) -  max(opp_history.count(i) for i in "RPS") < 4 and len(my_history) < 30:
        if opp_history.count("R") > max(opp_history.count(i) for i in "PS"):
            return "P"
        if opp_history.count("P") > max(opp_history.count(i) for i in "RS"):
            return "S"
        if opp_history.count("S") > max(opp_history.count(i) for i in "RP"):
            return "R"
    elif len(my_history) < 15:
        if max(opp_loaded)<max(my_loaded):
            return "RPS"[len(my_history)%3]
        else:
            return "RPS"[(my_loaded.index(max(my_loaded))+len(my_history)%2)%3]
    elif len(my_history) == 15:
        if max(opp_loaded)<max(my_loaded):
            return "RPS"[(len(my_history)+1)%3]
        else:
            return "RPS"[(my_loaded.index(max(my_loaded))+ (len(my_history)%2)^1)%3]
    else:
        if max(opp_loaded)<max(my_loaded):
            return random.choice("RPS")
        else:
            return "RPS"[(my_loaded.index(max(my_loaded))+ (random.randint(0,1)))%3]

1
if opp_history[1] == "S": return "R" elif opp_history[1] == "R": return "R" else: return "P"jaki to rodzaj towarzystwa?
Robert Fraser

@DestructibleLemon To dzieli przez 0:elif min(opp_history.count(i) for i in "RPS")/max(opp_history.count(i) for i in "RPS") >0.8 and len(my_history)>30:
Masclins

@AlbertMasclans Naprawiłem to.
Zniszczalna cytryna

@RobertFraser, co dokładnie wyróżnia się w tym fragmencie kodu?
Zniszczalna cytryna

@DestructibleLemon Nie jestem do końca pewien, co chciałeś tutaj zrobić: "RPS"[my_loaded.index(max(my_loaded))+len(my_history)%2]ale wygląda to poza zasięgiem (podobnie jak dalsze linie).
Masclins

0

Ważony losowo

Podobnie jak RandomBot, ale wybiera tylko 2, aby rzucać za każdym razem, gdy jest wywoływany. Czasami pokona Rockstar lub Assassin, ale zwiększy wyniki drugiego (np. Jeśli pokonuje Rockstar, daje Assassinowi premię punktową).

import random

selection_set = ["R", "P", "S"]
selection_set.pop(random.randint(0,2))
def weightedrandombotfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    return random.choice(selection_set)

0

Chciwy psycholog

Nazwano to, ponieważ domyślnie jest chciwe, ale jeśli nie może się zdecydować, przeciwstawia się temu, co zrobiłby przeciwnik, gdyby zastosował chciwą strategię. Jeśli nadal nie może się zdecydować, idzie losowo.

from random import choice

def greedypsychologistfunc(my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history):
    greedy = get_my_move(my_loaded)
    combined = list(set(greedy) & set(get_opp_counter(opp_loaded)))

    if len(combined) == 0:
        return choice(greedy)
    return choice(combined)

def get_indexes(lst, value):
    return [i for i,x in enumerate(lst) if x == value]

def get_my_move(my_loaded):
    return ["RPS"[i] for i in get_indexes(my_loaded, max(my_loaded))]

def get_opp_counter(opp_loaded):
    return ["PSR"[i] for i in get_indexes(opp_loaded, max(opp_loaded))]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.