КОТ - RPS с грузом

12

Конкурс открыт постоянно - Обновлено 10 августа 2017

Хотя 5 июня 2017 года я объявил победителя (который останется лучшим ответом), я буду набирать новых ботов и обновлять результаты.

Результаты 5 июня

Поздравляем user1502040

Поскольку связей нет, я показываю только процент выигранных матчей.

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 Sheet с таблицей результатов каждой пары: https://docs.google.com/spreadsheets/d/1KrMvcvWMkK-h1Ee50w0gWLh_L6rCFOgLhTN_QlEXHyk/edit?usp=sharing


Благодаря дилемме Петри я смог справиться с этим королем горы.

Игра

Игра представляет собой простой «камень-ножницы-бумага» с изюминкой: очки, набранные с каждой победой, увеличиваются во время матча (ваши 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 июля я опубликую итоговые результаты (возможно, постараюсь опубликовать предварительную заявку раньше).


Поскольку это мой первый KOTH, я на 100% готов изменить что-либо для улучшения, например, количество матчей против каждого бота.

Отредактировано до 1000 матчей, так как я вижу, что это действительно случайность.

Masclins
источник
с некоторыми рандомизированными ботами, вы действительно хотите сделать несколько игр из нескольких раундов
Разрушаемый Лимон
@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]

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]

Вычисляет приблизительное равновесие Нэша по градиентному спуску.

user1502040
источник
1
Мне очень нравится этот подход, и я могу понять, почему вы хотите иметь возможность сохранять состояние между раундами. Несмотря на то, что я вижу огромную проблему, чтобы изменить его, учитывая количество представлений. Я приму это во внимание для дальнейших испытаний (которые я ожидаю сделать, когда это закончится).
Масклинз
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

монтер

Этот бот улучшает Pattern и объединяет его с Economist (Pattern и Economist больше не будут участвовать)

Улучшение паттерна заключается в том, что бот теперь ищет два типа паттернов: противник реагирует на его последнюю игру и оппонент реагирует на мою последнюю игру. Затем оценивает оба прогноза, чтобы использовать тот, который подходит лучше всего.

Из этого паттерна у бота теперь есть вероятность для R, P и S. Принимая это во внимание и ожидаемую ценность каждой игры (как это сделал Экономист), бот играет ту, которая дает наибольшую ценность.

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]

Вот два старых кода

Pattern (больше не играет)

Шаблон пытается найти шаблоны на своего противника. Похоже, что противник сыграл после последней игры (придавая больше веса последним играм). Посредством этого он угадывает, что будет играть противник, и играет противодействие этому.

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]

Экономист (больше не играет)

The Economist делает следующее: угадывает вероятность каждой игры противника, наблюдая за тем, что он сыграл последние 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"])
Masclins
источник
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]})
PhiNotPi
источник
удалите комментарии, которые не делают код более понятным
отображаемое имя
@SargeBorsch сделано
PhiNotPi
1
@PhiNotPi Я знаю, что я не публиковал ограничения по времени, но Иггдрасиль тратит больше минуты на каждого противника. Можно ли было бы немного его оптимизировать?
Масклинз
да, это невыносимо медленно
отображаемое имя
@AlbertMasclans по минутам на противника, вы имеете в виду 1 минуту для всех игр против оппонента? Также я могу попытаться ускорить его, но я не знаю, как это сделать, он выглядит только на 1 шаг вперед как есть.
PhiNotPi
4

Anti-повторитель

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]

Просто копирует противников последний ход.

Anti-Anti-Жадные

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 (я, не, нужны, эти аргументы): вернуть «P»

рокзвезда

def rockstarfunc (я делаю, не нуждаюсь, эти аргументы): возвращаем "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"
KSmarts
источник
1
Вы можете заменить opp_history[len(opp_history)-1]на opp_history[-1].
CalculatorFeline
3

Вычурный ребенок

Этот бот ведет себя как ребенок, играющий в поделки, начнёт с бумаги и будет использовать бумагу или ножницы случайным образом, но не будет использовать ножницы после камня или ножницы, потому что ей нужно использовать ножницы на бумаге. Бросит камень обратно в любого, кто бросит камень в нее.

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"])
TitusLucretius
источник
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"])

Antigreedy

Предполагается, что противник будет играть жадным и разыгрывает альтернативу победы.

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"])
Masclins
источник
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"])

Это буквально обратное Greedy, оно выбирает вариант с наименьшим количеством баллов.

Чирок пеликан
источник
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

Лучшее обоих миров

Этот бот в основном сочетает в себе Anti-Greedy и Greedy (отсюда и название).

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
clismique
источник
Это Антигрид, уже выложенный в качестве примера.
Масклинз
@AlbertMasclans Поменял его на другого бота.
clismique
findдля строк. my_loadedи opp_loadedоба списка. indexдолжно быть хорошо для того, что вы хотите.
Масклинз
@AlbertMasclans Ой, исправлено. Спасибо за улов! Надеюсь, это не очередная попытка ... Я не хочу снова удалять это сообщение.
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

Expectedbayes

Изменить: Обновленный рейтинг

Это новый топ-рейтинг после включения Expectedbayes:

  • статистика2функция 91,89%
  • Фиттерфунк 85,65%
  • нашфунк 80.40%
  • весовой коэффициент 76,39%
  • Ожидаемый байсфанк 73,33%
  • антирефитерфункция 68,52%
  • ...

Пояснения

(NB: сообщение от 05.06.2017)

Этот бот пытается максимизировать ожидаемую ценность своего следующего хода:

  • Вычисление вероятности для каждого следующего возможного хода противника
  • Используя этот рисунок и нагрузки для расчета ожидаемого значения для каждого из R, P и S
  • Выбор хода с наибольшим ожидаемым значением
  • Случайный выбор значения в случае сбоя прогнозирования

Вероятности обновляются каждые десять ходов. Количество прошлых ходов, использованных для вычисления вероятностей, было установлено равным 10 для каждого бота (таким образом, всего 20 функций). Это, вероятно, перезаписывает данные, но я не пытался проверить дальше.

Он полагается на библиотеку scikit для вычисления вероятностей ходов противника (я говорю это в случае, если я неправильно прочитал правила, и это фактически было запрещено).

Он легко выигрывает у ботов, которые всегда делают один и тот же выбор. Удивительно, но он довольно эффективен против случайного бота с 93% -ной вероятностью выигрыша (я полагаю, это связано с тем, что он ограничивает количество очков, которые может получить его оппонент, при этом максимально увеличивая свое количество возможных очков за каждый раунд).

Я сделал быструю попытку с 100 ходами и ограниченным количеством ботов, и вот что я получил от result_standing:

  • randombotfunc, 35
  • nashbotfunc, 333
  • greedyfunc, 172
  • antigreedyfunc, 491
  • themessengerfunc, 298
  • rockstarfunc, 200
  • statistician2func, +748
  • fitterfunc, 656
  • expectedbayesfunc, 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
lesibius
источник
Добро пожаловать в PPCG и приятного первого поста!
Захари
Большое спасибо! Я давно хотел участвовать в PPCG. Теперь это исправлено!
Лесибий
0

велосипедист

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

0

CalculatorFeline
источник
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

socery

Я дал это исправить, так что, вероятно, теперь должно работать

Я снова что-то испортил, поэтому удалил и восстановил. Я делаю много беспорядков.

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))]
Соломон Уцко
источник