KOTH - завантажений RPS


12

Конкурс постійно відкрився - оновлено 10 серпня 2017 року

Навіть незважаючи на те, що 5 червня 2017 року я оголосив переможця (який буде вважатися найкращою відповіддю), я буду реннувати нові боти та оновлювати результати.

Результати 5 червня

Вітаємо користувача1502040

Оскільки зв’язків немає, я показую лише% виграних матчів.

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%

Я створив Google Таблицю з сіткою результатів кожного пари: https://docs.google.com/spreadsheets/d/1KrMvcvWMkK-h1Ee50w0gWLh_L6rCFOgLhTN_QlEXHyk/edit?usp=sharing


Завдяки Ділемі Петрі я виявив, що я в змозі впоратися з цим Королем Гірки.

Гра

Гра - це проста "Rock-Paper-Scissors" із поворотом: очки, набрані з кожним збільшенням перемоги під час матчу (ваші R, P або S завантажуються).

  • Папір виграє Рок
  • Ножиці виграє Папір
  • Рок виграє Ножиці

Переможець отримує стільки очок, скільки навантаження на свою гру.

Невдаха збільшує на 1 навантаження на його гру.

У разі нічиїх кожен гравець збільшує навантаження на свою гру на 0,5.

Після 100 п'єс переможець той, у кого більше очок.

наприклад: P1 має навантаження [10,11,12] (скеля, папір, ножиці) та P2 [7,8,9]. P1 грає R, P2 грає P. P2 виграє і отримує 8 очок. Навантаження P1 стають [11,11,12], навантаження P2 залишаються колишніми.

Технічні характеристики

Ваша програма повинна бути написана на Python (вибачте, я не знаю, як з цим впоратися). Ви повинні створити функцію, яка приймає кожну з цих змінних як аргумент для кожного виконання:

my_points, opp_points, my_loaded, opp_loaded, my_history, opp_history

points - Поточні бали (ваші та ваші опп)

loaded- Масив із навантаженнями (для порядку RPS) (ваші та ваші опп)

history- Рядок із усіма п'єсами, останній персонаж - це остання гра (ваша та ваша опп)

Ви повинні повернутися "R", "P"або "S". Якщо ви повернете щось інше, це був би автоматичний програш матчу.

Правила

Ви не можете змінити вбудовані функції.

Тестування

Я буду постійно оновлювати Git разом з кодом та всіма ботами, які відповідають: https://github.com/Masclins/LoadedRPS

Судження

Переможець визначатиметься шляхом вибору особи з найбільшою кількістю виграшних матчів після 1000 повних раундів. Краватки будуть розірвані зв'язаними сірниками. Проводиться 1000 матчів, а не один, тому що я очікую багато випадковості, і таким чином випадковість буде менш актуальною.

Ви можете подати до 5 ботів.

Конкурс закінчується 4 липня (це буде останній день, коли я прийму будь-яку відповідь), а 5 липня я опублікую остаточну таблицю (можливо, спробую опублікувати аванс раніше).


Оскільки це мій перший КОТ, я на 100% відкритий для того, щоб змінити що-небудь для покращення, наприклад, кількість матчів, зіграних проти кожного бота.

Відредаговано на 1000 матчів, оскільки я бачу, що тут дійсно багато випадкових випадків.


за допомогою декількох рандомізованих ботів ви дійсно хочете зробити кілька ігор з декількох раундів
Destructible Lemon

@DestructibleLemon Я думав про те, щоб змусити кожного бота грати три рази проти одного бота, а не один раз. Бачачи, що ти думаєш аналогічно, я зроблю так.
Masclins

1
(насправді вам потрібна досить велика кількість, оскільки деякі ймовірності дійсно поширюються на кілька матчів. Побачте мого бота, де він може занепокоїтись, але, швидше за все, не буде з великою кількістю матчів)
Руйнуючий лимон

1
Я радий, що моє запитання допомогло вам запустити це, @AlbertMasclans!
Грифон

2
@AlbertMasclans Чи можете ви опублікувати повний тестовий сценарій (включаючи runcodeта bots)?
CalculatorFeline

Відповіді:


8

Статистик (більше не грає)

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)]

Перемикається між декількома простими стратегіями на основі очікуваної минулої ефективності

Статистик 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]

Неш

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]

Обчислює приблизну рівновагу Неша за градієнтним спуском.


1
Мені дуже подобається такий підхід, і я можу зрозуміти, чому ви хочете мати можливість зберігати стан між раундами. Незважаючи на те, що я бачу величезну проблему змінити його, враховуючи кількість заявок. Я буду враховувати це для подальших викликів (які я очікую зробити, коли це закінчиться).
Masclins

5

Ваг

Я програвав міркування, експериментуючи з кодом, але основна ідея полягає в тому, щоб оцінити ймовірність руху противника на останніх 3 ходах, використовуючи деякі ваги, і помножити їх на іншу вагу, що залежить від навантажень. Я думав, що я my_loadedтеж якось можу використовуватись , але не міг вирішити, як це зробити, тому покинув це.

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))]

Сатана

Ймовірно, буде дискваліфіковано, тому що це свого роду обман і це робить певні припущення щодо функції тестування (він повинен мати функцію опонента в змінній на його стеці фрейму), але технічно це не порушує жодних поточних правил - він не робить переосмислити або переписати що-небудь. Він просто використовує чорну магію для виконання функції противника, щоб побачити, який поворот зробив / зробить вони. Він не може впоратися з випадковістю, але детерміновані боти не мають шансу перемогти сатану.

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

Без сумніву, найкращий з точки зору простоти-результатів
Masclins

До речі, для використання my_loadedви можете додати вагу, яка оцінює хід, який ви втратите в порівнянні з вашим останнім кроком. Це як припускати, що ваш опонент зробить щось подібне до того, що ви зробили, а тому покарає його за те, що ви будете продовжувати грати так само. Щось на кшталт:for i, m in enumerate(reversed(my_history[-3:])): sc[(idx[m]+1)%3] += (K / (1 + i))
Masclins

@AlbertMasclans додав ще одне рішення
Відображати ім'я

1
Мені дуже подобається сатана. Але, як ви сказали, я вважаю, що це не повинно кваліфікуватися: навіть якщо це не порушує явне правило, це явно проти духу гри. Все-таки вітаю з ідеєю!
Masclins

4

Фітер

Цей бот покращує шаблон і зливає його з Economist (Pattern and Economist більше не братиме участь)

Удосконалення Шаблону полягає в тому, що тепер Бот шукає два два види шаблону: Суперник реагує на свою останню гру і опонент реагує на мою останню гру. Потім оцінюються обидва прогнози, щоб використовувати той, який відповідає найкращим.

З цього шаблону у "Бота" зараз є ймовірність для R, P і S. Враховуючи це і очікуване значення кожної гри (як це робив Economist), Бот грає ту, яка дає найбільшу цінність.

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]

Ось два старі коди

Шаблон (більше не грає)

Шаблон намагається знайти зразки свого опонента. Схоже, що грав суперник після останньої гри, яку він зробив (надаючи більшої ваги останнім п'єсам). Завдяки цьому він здогадується, що гратиме опонент, і грає в контрмат.

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]

Економіст (більше не грає)

Економіст робить наступне: здогадується про ймовірність кожної гри суперника, спостерігаючи, що він грав за останні 9 витків. Виходячи з цього, обчислюється очікувана вигода кожної гри та йде з тією, яка має найкраще очікуване значення.

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", оскільки воно дивиться вперед у ігровому дереві. Цей бот не виконує жодних прогнозів опонента, він просто намагається зберегти статистичну перевагу, якщо йому надається (шляхом балансування поточного та майбутнього прибутку). Він обчислює приблизно ідеальну змішану стратегію і повертає рух, вибраний випадковим чином з цими вагами. Якби цей бот був ідеальним (чого це не так, оскільки функція оцінювання штату є досить поганою, і це виглядає не дуже далеко вперед), тоді би неможливо було бити цього бота більше 50% часу. Я не знаю, як добре цей бот зробить на практиці.

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]})

видаліть коментарі, які не роблять код зрозумілішим
Відображайте ім'я

@SargeBorsch зроблено
PhiNotPi

1
@PhiNotPi Я знаю, що я не розміщував обмежень у часі, але Yggdrasil займає більше ніж хвилину проти кожного суперника. Чи можна було б її трохи оптимізувати?
Masclins

так, це нестерпно повільно
Відображайте ім'я

@AlbertMasclans за хвилиною на противника, ти маєш на увазі загальну хвилину для всіх ігор проти гравця? Також я можу спробувати пришвидшити це, але я не знаю, як це зробити, він виглядає лише на 1 крок вперед, як є.
PhiNotPi

4

Анти повторювач

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"

Перший оборот збирає папір, після чого повертає все, що б'є те, що найбільше зробив опонент, підбираючи папір у разі краватки.

Копікат

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]

Просто скопіюйте останніх кроків супротивників.

Анти-анти-жадібний

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"])

Вибирає все, що програє, найбільш вагомим вибором суперника.

Дещо голодний

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

Посланець

def themessengerfunc (I, do, not, need, цих, аргументи): return "P"

Рок-зірка

def rockstarfunc (я, робити, не, потрібно, ці, аргументи): return "R"

Вбивця

def assassinfunc (я, робити, не, потрібно, ці, аргументи): повернути "S"

Пояснення

Тепер ви можете подумати, що ці боти зовсім дурні.

Не зовсім правда, вони насправді засновані на ідеї, накопичивши величезний бонус, а противник зробить помилковий крок і переграє його.

Зараз ці боти грають дуже схоже на жадібних, однак вони простіші, і не випадково вибирають, поки не отримають навантаження на одну зброю, вони дотримуються своєї зброї на вибір.

Ще одна річ, яку слід зазначити: кожен з них виграє жадібну приблизно половину часу, зайнявши третину часу і програвши одну шосту частину часу. коли вони виграють, вони, як правило, виграють багато. чому це?

Жадібний, поки він не програє раунд, випадково вибере зброю. це означає, що коли він не виграє раунд, він знову вибере зброю, яка знову може стати виграшною. якщо жадібний малює або програє, він прилипає до цієї зброї. якщо жадібний виграє хоча б один раунд, то вибирає таку ж зброю, що і бот, жадібний виграє. якщо жадібний вибирає програючу зброю в якийсь момент, наш бот виграє, тому що навантаження на нашу зброю було б вищою за показник жадібного.

Якщо припустити, що жадібний не завжди обирає зброю-виграш через велику шанс, це означає, що шанси такі:

1/3: {1/2 виграш (1/6 всього). 1/2 програють (1/6 всього). }

1/3 розіграшу

1/3 виграш

так: 1/3 шанс нічиї, 1/6 шанс програти, 1/2 шанс виграти.

це, мабуть, показує, що вам потрібно зробити кілька ігор з декількох раундів

це в основному, щоб отримати прокрутку викликів


3

Реактор

Створює гру, яка виграла б попередній раунд.

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
Ви можете замінити opp_history[len(opp_history)-1]на opp_history[-1].
CalculatorFeline

3

Arty Child

Цей бот діє як дитина, що грає в декоративно-прикладне мистецтво, розпочнеться з паперу і буде використовувати папір або ножиці випадковим чином, але не буде використовувати ножиці після скелі або ножиць, оскільки їй потрібно використовувати ножиці на папері. Відкине скелю назад у кожного, хто кине скелю на неї.

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

Ось три боти, які я створив для тестування:


RandomBot

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

Жадібний

Просто вибирає найбільш завантажений варіант.

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"])

Антизема

Передбачає, що опонент буде грати жадібно і грає переможну альтернативу.

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

Не голодний

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"])

Це буквально зворотна жадібна, вона вибирає найнижчий доступний варіант.


1

Використовуйте улюбленого опонента

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"])

Для першої черги вибирає випадковий предмет. Для кожного іншого ходу використовується найпоширеніший вибір опонента. Якщо є краватка, вона за замовчуванням ставиться до найчастішого вибору.

// Я вкрав звідси код


Перемога - це добре

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"])

Повертає вибір переможця попереднього туру. Якщо попередній раунд був равним, рекурсивно перевіряє раунд перед цим. Якщо це були лише зв'язки, або це перший тур, повертається випадковий вибір.


1

Найкраще з обох світів

Цей бот в основному поєднує в собі Анти-Жадібний та Жадібний (звідси і назва).

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

Це Антигрида, вже розміщена як приклад.
Масклін

@AlbertMasclans Змінив його на іншого бота.
clismique

findпризначено для струн. my_loadedі opp_loadedобидва списки. indexмає бути добре для того, що ви хочете.
Masclins

@AlbertMasclans Whoops, виправлено зараз. Дякую за улов! Я сподіваюся, що це ще не один копій ... Я не хочу видаляти цю публікацію знову.
clismique

Це нормально, дякую за гру
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"

Випадково вибирає між трьома варіантами таким чином, щоб опонент статистично не мав переваги між ходами щодо того, наскільки він набрав; Іншими словами, і жадібний, і не голодний повинні мати однаковий середній очікуваний бал проти нього.


1

Очікуванібаї

Редагувати: оновлення рейтингу

Це новий топ-рейтинг після включення в очікуванібаї:

  • statistician2func 91,89%
  • fitterfunc 85,65%
  • нашфунк 80,40%
  • важить 76,39%
  • очікуванийбайсфунк 73,33%
  • антирепетиторфункція 68,52%
  • ...

Пояснення

(NB: подання після 06.06.2017)

Цей бот намагається максимізувати очікуване значення свого наступного ходу шляхом:

  • Обчислення ймовірності для наступного можливого ходу опонента
  • Використовуючи цю цифру та навантаження для обчислення очікуваного значення для кожного з R, P і S
  • Вибір ходу, який має найбільше очікуване значення
  • Випадковий вибір значення, якщо прогнозування не вдалося

Ймовірності оновлюються кожні десять кроків. Кількість минулих ходів, які використовувались для обчислення ймовірностей, встановлено на 10 для кожного бота (тобто 20 функцій в цілому). Це, мабуть, перевищує дані, але я не намагався перевіряти далі.

Він покладається на бібліотеку scikit, щоб обчислити ймовірність переміщення опонента (я говорю це, якщо я неправильно прочитав правила, а насправді це не було дозволено).

Він легко перемагає проти ботів, які завжди роблять однаковий вибір. Дивно, але він досить ефективний проти випадкового бота зі швидкістю виграшу 93% (я вважаю, що це пов’язано з тим, що він обмежує кількість очок, які може отримати його опонент, максимізуючи власну кількість можливих очок за кожен раунд).

Я швидко зробив спробу з 100 оборотів і лише обмеженою кількістю ботів, і ось що я отримав від результату:

  • randombotfunc, 35
  • nashbotfunc, 333
  • greedyfunc, 172
  • antigreedyfunc, 491
  • темиsengerfunc, 298
  • rockstarfunc, 200
  • statistician2func, 748
  • fitterfunc, 656
  • очікуванийбайсфунк, 601

Що не так вже й погано!

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

Ласкаво просимо в PPCG, і приємний перший пост!
Zacharý

Дуже дякую! Я довго хотів брати участь у PPCG. Тепер це виправлено!
lesibius

0

Цикл

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

0


0

Ансамбль

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)

Кілька конкуруючих алгоритмів голосують за найкраще рішення.

Зміна

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)

Робить випадковий хід, але не повторюючи останній хід.


0

blodsocer

товариство

Я дав це виправити, тож, мабуть, це має працювати зараз, я сподіваюся

Я щось переплутав знову, тому я видалив і не визначив. Я роблю багато безладів.

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"що це за товариство?
Роберт Фрейзер

@DestructibleLemon Це ділиться на 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 Я це виправив.
Зруйнований лимон

@RobertFraser, що саме є видатним у цьому фрагменті коду?
Зруйнований лимон

@DestructibleLemon Я не зовсім впевнений, що ти тут хотів зробити: "RPS"[my_loaded.index(max(my_loaded))+len(my_history)%2]але це виглядає поза межами діапазону (і так будуть подальші рядки).
Masclins

0

Ваговий випадковий

Як і RandomBot, але він вибирає лише 2 для кидання щоразу, коли його викликають. Іноді буде бити Rockstar або Assassin, але накачає бали іншого (наприклад, якщо він бив Rockstar, це дає Assassin очковий приріст).

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

Жадібний психолог

Названо це тому, що воно за замовчуванням є жадібним, але якщо він не може вирішити, він протидіє тому, що зробив би противник, якби вони використовували жадібну стратегію. Якщо він все ще не може вирішити, він іде випадковим чином.

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))]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.