Игра в кости, но избегайте № 6 [закрыто]


58

Турнир окончен!

Турнир окончен! Финальная симуляция была проведена ночью, всего игр. Победителем стал Кристиан Сиверс со своим ботом OptFor2X . Кристиану Сиверсу также удалось завоевать второе место с повстанцами . Поздравляем! Ниже вы можете увидеть официальный список рекордов турнира.3108

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

Игральная кость

Меня пригласили сыграть в игру в кости, о которой я никогда не слышал. Правила были просты, но я думаю, что это было бы идеально для вызова KotH.

Правила

Начало игры

Кубик проходит по столу, и каждый раз, когда наступает ваша очередь, вы можете бросать кубик столько раз, сколько захотите. Тем не менее, вы должны бросить его хотя бы один раз. Вы отслеживаете сумму всех бросков за ваш раунд. Если вы решите остановиться, счет за раунд добавляется к вашему общему счету.

Так почему бы тебе перестать бросать кубик? Потому что, если вы получите 6, ваш счет за весь раунд станет нулевым, и кубик будет передан. Таким образом, первоначальная цель - как можно быстрее увеличить ваш счет.

Кто является победителем?

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

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

Однако есть еще одно правило, которое необходимо учитывать. Если ваш текущий общий балл (ваш предыдущий балл + ваш текущий балл за раунд) составляет 40 или более, и вы набрали 6, ваш общий балл устанавливается равным 0. Это означает, что вы должны начинать все сначала. Если вы набрали 6, когда ваш текущий общий счет 40 или больше, игра продолжится как обычно, за исключением того, что вы сейчас на последнем месте. Последний раунд не запускается, когда ваш общий счет сбрасывается. Вы все еще можете выиграть раунд, но он становится более сложным.

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

Дополнительное правило заключается в том, что игра продолжается максимум 200 раундов. Это сделано для предотвращения случаев, когда несколько ботов в основном продолжают бросать, пока не достигнут 6, чтобы остаться на своем текущем счете. После того, как 199-й раунд пройден, last_roundустанавливается значение true и воспроизводится еще один раунд. Если игра идет до 200 раундов, бот (или боты) с наибольшим количеством очков считается победителем, даже если у них нет 40 и более очков.

резюмировать

  • Каждый раунд вы продолжаете бросать кубик, пока не решите остановиться или не получите 6
  • Вы должны бросить кубик один раз (если ваш первый бросок 6, ваш раунд сразу заканчивается)
  • Если вы получите 6, ваш текущий счет будет установлен на 0 (не ваш общий счет)
  • Вы добавляете свой текущий счет к общему количеству очков после каждого раунда.
  • Когда бот заканчивает свой ход, в результате чего общий счет не менее 40, все остальные получают последний ход
  • Если ваш текущий общий балл и вы получаете 6, ваш общий балл будет равен 0 и ваш раунд окончен40
  • Последний раунд не запускается, когда происходит выше
  • Человек с наибольшим общим счетом после последнего тура является победителем
  • В случае, если есть несколько победителей, все будут считаться победителями
  • Игра длится не более 200 раундов

Разъяснение результатов

  • Общая оценка: оценка, которую вы сохранили в предыдущих раундах
  • Текущий счет: счет за текущий раунд
  • Текущий общий балл: сумма двух баллов выше

Как вы участвуете

Чтобы принять участие в этой задаче KotH, вы должны написать класс Python, который наследуется от Bot. Вы должны реализовать функцию: make_throw(self, scores, last_round). Эта функция будет вызываться, как только наступит ваш ход, и ваш первый бросок не был 6. Чтобы продолжать бросать, вы должны это сделать yield True. Чтобы прекратить бросать, вы должны yield False. После каждого броска update_stateвызывается родительская функция . Таким образом, у вас есть доступ к вашим броскам за текущий раунд с использованием переменной self.current_throws. У вас также есть доступ к своему индексу с помощью self.index. Таким образом, чтобы увидеть свой общий счет, вы бы использовали scores[self.index]. Вы также можете получить доступ end_scoreк игре с помощью self.end_score, но вы можете смело предположить, что для этого испытания будет 40.

Вам разрешено создавать вспомогательные функции внутри вашего класса. Вы также можете переопределить функции, существующие в Botродительском классе, например, если вы хотите добавить больше свойств класса. Вам не разрешается изменять состояние игры каким-либо образом, кроме сдачи Trueили False.

Вы можете черпать вдохновение из этого поста и скопировать любого из двух ботов, которые я здесь включил. Тем не менее, я боюсь, что они не особенно эффективны ...

На разрешении других языков

Как в песочнице, так и в «Девятнадцатом байте» мы обсуждали вопрос о разрешении подачи заявок на других языках. Прочитав о таких реализациях и выслушав аргументы обеих сторон, я решил ограничить этот вызов только Python. Это связано с двумя факторами: временем, необходимым для поддержки нескольких языков, и случайностью этой задачи, требующей большого количества итераций для достижения стабильности. Я надеюсь, что вы по-прежнему будете участвовать, и если вы хотите изучить Python для этой задачи, я постараюсь быть доступным в чате как можно чаще.

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

правила

  • Саботаж допускается и поощряется. То есть саботаж против других игроков
  • Любая попытка возиться с контроллером, во время выполнения или другими представлениями будет дисквалифицирована. Все представления должны работать только с входами и хранилищами, которые им предоставляются.
  • Любой бот, который использует более 500 МБ памяти для принятия решения, будет дисквалифицирован (если вам нужно столько памяти, вам следует пересмотреть свой выбор)
  • Бот не должен реализовывать ту же стратегию, что и существующая, намеренно или случайно.
  • Вы можете обновить своего бота во время испытания. Тем не менее, вы также можете разместить другого бота, если ваш подход отличается.

пример

class GoToTenBot(Bot):
    def make_throw(self, scores, last_round):
        while sum(self.current_throws) < 10:
            yield True
        yield False

Этот бот будет продолжать работать до тех пор, пока у него не будет по крайней мере 10 очков за раунд или пока он не выбросит 6. Обратите внимание, что вам не нужна логика для обработки броска 6. Также обратите внимание, что если ваш первый бросок равен 6, make_throwэто никогда не звонил, так как ваш раунд сразу закончился.

Для тех, кто плохо знаком с Python (и новичок в yieldконцепции), но хочет попробовать, yieldключевое слово похоже на возврат в некоторых отношениях, но отличается в других. Вы можете прочитать о концепции здесь . По сути, как только вы yield, ваша функция остановится, и значение, которое вы yieldотредактировали, будет отправлено обратно в контроллер. Там контроллер обрабатывает свою логику до тех пор, пока ваш бот не примет другое решение. Затем контроллер отправляет вам бросок игральных костей, и ваша make_throwфункция будет продолжать выполняться там, где она остановлена ​​раньше, в основном на строке после предыдущего yieldоператора.

Таким образом, игровой контроллер может обновлять состояние, не требуя отдельного вызова функции бота для каждого броска костей.

Спецификация

Вы можете использовать любую библиотеку Python, доступную в pip. Чтобы убедиться, что я смогу получить хорошее среднее значение, у вас есть ограничение в 100 миллисекунд на раунд. Я был бы очень рад, если бы ваш сценарий был намного быстрее, чтобы я мог запустить больше раундов.

оценка

Чтобы найти победителя, я беру всех ботов и запускаю их в случайных группах по 8. Если представлено менее 8 классов, я буду запускать их в случайных группах по 4, чтобы избежать того, чтобы в каждом раунде были все боты. Я буду проводить симуляции около 8 часов, и победителем станет бот с самым высоким процентом побед. Я начну финальные симуляции в начале 2019 года, и у вас будет все Рождество, чтобы кодировать своих ботов! Предварительная окончательная дата - 4 января, но если это слишком мало времени, я могу изменить ее на более позднюю дату.

До этого я постараюсь делать ежедневные симуляции, используя 30-60 минут процессорного времени, и обновлять табло. Это не будет официальным счетом, но он послужит руководством, чтобы увидеть, какие боты показывают лучшие результаты. Однако, с приближением Рождества, я надеюсь, вы понимаете, что я не буду доступен в любое время. Я сделаю все возможное, чтобы проводить симуляции и отвечать на любые вопросы, связанные с задачей.

Проверь это сам

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

контроллер

Вот обновленный контроллер для этой задачи. Он поддерживает выходы ANSI, многопоточность и собирает дополнительную статистику благодаря AKroell ! Когда я внесу изменения в контроллер, я обновлю сообщение, как только документация будет завершена.

Благодаря BMO , контроллер теперь может загружать всех ботов из этого поста, используя -dфлаг. Другие функции не изменились в этой версии. Это должно гарантировать, что все ваши последние изменения будут смоделированы как можно скорее!

#!/usr/bin/env python3
import re
import json
import math
import random
import requests
import sys
import time
from numpy import cumsum

from collections import defaultdict
from html import unescape
from lxml import html
from multiprocessing import Pool
from os import path, rename, remove
from sys import stderr
from time import strftime

# If you want to see what each bot decides, set this to true
# Should only be used with one thread and one game
DEBUG = False
# If your terminal supports ANSI, try setting this to true
ANSI = False
# File to keep base class and own bots
OWN_FILE = 'forty_game_bots.py'
# File where to store the downloaded bots
AUTO_FILE = 'auto_bots.py'
# If you want to use up all your quota & re-download all bots
DOWNLOAD = False
# If you want to ignore a specific user's bots (eg. your own bots): add to list
IGNORE = []
# The API-request to get all the bots
URL = "https://api.stackexchange.com/2.2/questions/177765/answers?page=%s&pagesize=100&order=desc&sort=creation&site=codegolf&filter=!bLf7Wx_BfZlJ7X"


def print_str(x, y, string):
    print("\033["+str(y)+";"+str(x)+"H"+string, end = "", flush = True)

class bcolors:
    WHITE = '\033[0m'
    GREEN = '\033[92m'
    BLUE = '\033[94m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    ENDC = '\033[0m'

# Class for handling the game logic and relaying information to the bots
class Controller:

    def __init__(self, bots_per_game, games, bots, thread_id):
        """Initiates all fields relevant to the simulation

        Keyword arguments:
        bots_per_game -- the number of bots that should be included in a game
        games -- the number of games that should be simulated
        bots -- a list of all available bot classes
        """
        self.bots_per_game = bots_per_game
        self.games = games
        self.bots = bots
        self.number_of_bots = len(self.bots)
        self.wins = defaultdict(int)
        self.played_games = defaultdict(int)
        self.bot_timings = defaultdict(float)
        # self.wins = {bot.__name__: 0 for bot in self.bots}
        # self.played_games = {bot.__name__: 0 for bot in self.bots}
        self.end_score = 40
        self.thread_id = thread_id
        self.max_rounds = 200
        self.timed_out_games = 0
        self.tied_games = 0
        self.total_rounds = 0
        self.highest_round = 0
        #max, avg, avg_win, throws, success, rounds
        self.highscore = defaultdict(lambda:[0, 0, 0, 0, 0, 0])
        self.winning_scores = defaultdict(int)
        # self.highscore = {bot.__name__: [0, 0, 0] for bot in self.bots}

    # Returns a fair dice throw
    def throw_die(self):
        return random.randint(1,6)
    # Print the current game number without newline
    def print_progress(self, progress):
        length = 50
        filled = int(progress*length)
        fill = "="*filled
        space = " "*(length-filled)
        perc = int(100*progress)
        if ANSI:
            col = [
                bcolors.RED, 
                bcolors.YELLOW, 
                bcolors.WHITE, 
                bcolors.BLUE, 
                bcolors.GREEN
            ][int(progress*4)]

            end = bcolors.ENDC
            print_str(5, 8 + self.thread_id, 
                "\t%s[%s%s] %3d%%%s" % (col, fill, space, perc, end)
            )
        else:
            print(
                "\r\t[%s%s] %3d%%" % (fill, space, perc),
                flush = True, 
                end = ""
            )

    # Handles selecting bots for each game, and counting how many times
    # each bot has participated in a game
    def simulate_games(self):
        for game in range(self.games):
            if self.games > 100:
                if game % (self.games // 100) == 0 and not DEBUG:
                    if self.thread_id == 0 or ANSI:
                        progress = (game+1) / self.games
                        self.print_progress(progress)
            game_bot_indices = random.sample(
                range(self.number_of_bots), 
                self.bots_per_game
            )

            game_bots = [None for _ in range(self.bots_per_game)]
            for i, bot_index in enumerate(game_bot_indices):
                self.played_games[self.bots[bot_index].__name__] += 1
                game_bots[i] = self.bots[bot_index](i, self.end_score)

            self.play(game_bots)
        if not DEBUG and (ANSI or self.thread_id == 0):
            self.print_progress(1)

        self.collect_results()

    def play(self, game_bots):
        """Simulates a single game between the bots present in game_bots

        Keyword arguments:
        game_bots -- A list of instantiated bot objects for the game
        """
        last_round = False
        last_round_initiator = -1
        round_number = 0
        game_scores = [0 for _ in range(self.bots_per_game)]


        # continue until one bot has reached end_score points
        while not last_round:
            for index, bot in enumerate(game_bots):
                t0 = time.clock()
                self.single_bot(index, bot, game_scores, last_round)
                t1 = time.clock()
                self.bot_timings[bot.__class__.__name__] += t1-t0

                if game_scores[index] >= self.end_score and not last_round:
                    last_round = True
                    last_round_initiator = index
            round_number += 1

            # maximum of 200 rounds per game
            if round_number > self.max_rounds - 1:
                last_round = True
                self.timed_out_games += 1
                # this ensures that everyone gets their last turn
                last_round_initiator = self.bots_per_game

        # make sure that all bots get their last round
        for index, bot in enumerate(game_bots[:last_round_initiator]):
            t0 = time.clock()
            self.single_bot(index, bot, game_scores, last_round)
            t1 = time.clock()
            self.bot_timings[bot.__class__.__name__] += t1-t0

        # calculate which bots have the highest score
        max_score = max(game_scores)
        nr_of_winners = 0
        for i in range(self.bots_per_game):
            bot_name = game_bots[i].__class__.__name__
            # average score per bot
            self.highscore[bot_name][1] += game_scores[i]
            if self.highscore[bot_name][0] < game_scores[i]:
                # maximum score per bot
                self.highscore[bot_name][0] = game_scores[i]
            if game_scores[i] == max_score:
                # average winning score per bot
                self.highscore[bot_name][2] += game_scores[i]
                nr_of_winners += 1
                self.wins[bot_name] += 1
        if nr_of_winners > 1:
            self.tied_games += 1
        self.total_rounds += round_number
        self.highest_round = max(self.highest_round, round_number)
        self.winning_scores[max_score] += 1

    def single_bot(self, index, bot, game_scores, last_round):
        """Simulates a single round for one bot

        Keyword arguments:
        index -- The player index of the bot (e.g. 0 if the bot goes first)
        bot -- The bot object about to be simulated
        game_scores -- A list of ints containing the scores of all players
        last_round -- Boolean describing whether it is currently the last round
        """

        current_throws = [self.throw_die()]
        if current_throws[-1] != 6:

            bot.update_state(current_throws[:])
            for throw in bot.make_throw(game_scores[:], last_round):
                # send the last die cast to the bot
                if not throw:
                    break
                current_throws.append(self.throw_die())
                if current_throws[-1] == 6:
                    break
                bot.update_state(current_throws[:])

        if current_throws[-1] == 6:
            # reset total score if running total is above end_score
            if game_scores[index] + sum(current_throws) - 6 >= self.end_score:
                game_scores[index] = 0
        else:
            # add to total score if no 6 is cast
            game_scores[index] += sum(current_throws)

        if DEBUG:
            desc = "%d: Bot %24s plays %40s with " + \
            "scores %30s and last round == %5s"
            print(desc % (index, bot.__class__.__name__, 
                current_throws, game_scores, last_round))

        bot_name = bot.__class__.__name__
        # average throws per round
        self.highscore[bot_name][3] += len(current_throws)
        # average success rate per round
        self.highscore[bot_name][4] += int(current_throws[-1] != 6)
        # total number of rounds
        self.highscore[bot_name][5] += 1


    # Collects all stats for the thread, so they can be summed up later
    def collect_results(self):
        self.bot_stats = {
            bot.__name__: [
                self.wins[bot.__name__],
                self.played_games[bot.__name__],
                self.highscore[bot.__name__]
            ]
        for bot in self.bots}


# 
def print_results(total_bot_stats, total_game_stats, elapsed_time):
    """Print the high score after the simulation

    Keyword arguments:
    total_bot_stats -- A list containing the winning stats for each thread
    total_game_stats -- A list containing controller stats for each thread
    elapsed_time -- The number of seconds that it took to run the simulation
    """

    # Find the name of each bot, the number of wins, the number
    # of played games, and the win percentage
    wins = defaultdict(int)
    played_games = defaultdict(int)
    highscores = defaultdict(lambda: [0, 0, 0, 0, 0, 0])
    bots = set()
    timed_out_games = sum(s[0] for s in total_game_stats)
    tied_games = sum(s[1] for s in total_game_stats)
    total_games = sum(s[2] for s in total_game_stats)
    total_rounds = sum(s[4] for s in total_game_stats)
    highest_round = max(s[5] for s in total_game_stats)
    average_rounds = total_rounds / total_games
    winning_scores = defaultdict(int)
    bot_timings = defaultdict(float)

    for stats in total_game_stats:
        for score, count in stats[6].items():
            winning_scores[score] += count
    percentiles = calculate_percentiles(winning_scores, total_games)


    for thread in total_bot_stats:
        for bot, stats in thread.items():
            wins[bot] += stats[0]
            played_games[bot] += stats[1]

            highscores[bot][0] = max(highscores[bot][0], stats[2][0])       
            for i in range(1, 6):
                highscores[bot][i] += stats[2][i]
            bots.add(bot)

    for bot in bots:
        bot_timings[bot] += sum(s[3][bot] for s in total_game_stats)

    bot_stats = [[bot, wins[bot], played_games[bot], 0] for bot in bots]

    for i, bot in enumerate(bot_stats):
        bot[3] = 100 * bot[1] / bot[2] if bot[2] > 0 else 0
        bot_stats[i] = tuple(bot)

    # Sort the bots by their winning percentage
    sorted_scores = sorted(bot_stats, key=lambda x: x[3], reverse=True)
    # Find the longest class name for any bot
    max_len = max([len(b[0]) for b in bot_stats])

    # Print the highscore list
    if ANSI:
        print_str(0, 9 + threads, "")
    else:
        print("\n")


    sim_msg = "\tSimulation or %d games between %d bots " + \
        "completed in %.1f seconds"
    print(sim_msg % (total_games, len(bots), elapsed_time))
    print("\tEach game lasted for an average of %.2f rounds" % average_rounds)
    print("\t%d games were tied between two or more bots" % tied_games)
    print("\t%d games ran until the round limit, highest round was %d\n"
        % (timed_out_games, highest_round))

    print_bot_stats(sorted_scores, max_len, highscores)
    print_score_percentiles(percentiles)
    print_time_stats(bot_timings, max_len)

def calculate_percentiles(winning_scores, total_games):
    percentile_bins = 10000
    percentiles = [0 for _ in range(percentile_bins)]
    sorted_keys = list(sorted(winning_scores.keys()))
    sorted_values = [winning_scores[key] for key in sorted_keys]
    cumsum_values = list(cumsum(sorted_values))
    i = 0

    for perc in range(percentile_bins):
        while cumsum_values[i] < total_games * (perc+1) / percentile_bins:
            i += 1
        percentiles[perc] = sorted_keys[i] 
    return percentiles

def print_score_percentiles(percentiles):
    n = len(percentiles)
    show = [.5, .75, .9, .95, .99, .999, .9999]
    print("\t+----------+-----+")
    print("\t|Percentile|Score|")
    print("\t+----------+-----+")
    for p in show:
        print("\t|%10.2f|%5d|" % (100*p, percentiles[int(p*n)]))
    print("\t+----------+-----+")
    print()


def print_bot_stats(sorted_scores, max_len, highscores):
    """Print the stats for the bots

    Keyword arguments:
    sorted_scores -- A list containing the bots in sorted order
    max_len -- The maximum name length for all bots
    highscores -- A dict with additional stats for each bot
    """
    delimiter_format = "\t+%s%s+%s+%s+%s+%s+%s+%s+%s+%s+"
    delimiter_args = ("-"*(max_len), "", "-"*4, "-"*8, 
        "-"*8, "-"*6, "-"*6, "-"*7, "-"*6, "-"*8)
    delimiter_str = delimiter_format % delimiter_args
    print(delimiter_str)
    print("\t|%s%s|%4s|%8s|%8s|%6s|%6s|%7s|%6s|%8s|" 
        % ("Bot", " "*(max_len-3), "Win%", "Wins", 
            "Played", "Max", "Avg", "Avg win", "Throws", "Success%"))
    print(delimiter_str)

    for bot, wins, played, score in sorted_scores:
        highscore = highscores[bot]
        bot_max_score = highscore[0]
        bot_avg_score = highscore[1] / played
        bot_avg_win_score = highscore[2] / max(1, wins)
        bot_avg_throws = highscore[3] / highscore[5]
        bot_success_rate = 100 * highscore[4] / highscore[5]

        space_fill = " "*(max_len-len(bot))
        format_str = "\t|%s%s|%4.1f|%8d|%8d|%6d|%6.2f|%7.2f|%6.2f|%8.2f|"
        format_arguments = (bot, space_fill, score, wins, 
            played, bot_max_score, bot_avg_score,
            bot_avg_win_score, bot_avg_throws, bot_success_rate)
        print(format_str % format_arguments)

    print(delimiter_str)
    print()

def print_time_stats(bot_timings, max_len):
    """Print the execution time for all bots

    Keyword arguments:
    bot_timings -- A dict containing information about timings for each bot
    max_len -- The maximum name length for all bots
    """
    total_time = sum(bot_timings.values())
    sorted_times = sorted(bot_timings.items(), 
        key=lambda x: x[1], reverse = True)

    delimiter_format = "\t+%s+%s+%s+"
    delimiter_args = ("-"*(max_len), "-"*7, "-"*5)
    delimiter_str = delimiter_format % delimiter_args
    print(delimiter_str)

    print("\t|%s%s|%7s|%5s|" % ("Bot", " "*(max_len-3), "Time", "Time%"))
    print(delimiter_str)
    for bot, bot_time in sorted_times:
        space_fill = " "*(max_len-len(bot))
        perc = 100 * bot_time / total_time
        print("\t|%s%s|%7.2f|%5.1f|" % (bot, space_fill, bot_time, perc))
    print(delimiter_str)
    print() 


def run_simulation(thread_id, bots_per_game, games_per_thread, bots):
    """Used by multithreading to run the simulation in parallel

    Keyword arguments:
    thread_id -- A unique identifier for each thread, starting at 0
    bots_per_game -- How many bots should participate in each game
    games_per_thread -- The number of games to be simulated
    bots -- A list of all bot classes available
    """
    try:
        controller = Controller(bots_per_game, 
            games_per_thread, bots, thread_id)
        controller.simulate_games()
        controller_stats = (
            controller.timed_out_games,
            controller.tied_games,
            controller.games,
            controller.bot_timings,
            controller.total_rounds,
            controller.highest_round,
            controller.winning_scores
        )
        return (controller.bot_stats, controller_stats)
    except KeyboardInterrupt:
        return {}


# Prints the help for the script
def print_help():
    print("\nThis is the controller for the PPCG KotH challenge " + \
        "'A game of dice, but avoid number 6'")
    print("For any question, send a message to maxb\n")
    print("Usage: python %s [OPTIONS]" % sys.argv[0])
    print("\n  -n\t\tthe number of games to simluate")
    print("  -b\t\tthe number of bots per round")
    print("  -t\t\tthe number of threads")
    print("  -d\t--download\tdownload all bots from codegolf.SE")
    print("  -A\t--ansi\trun in ANSI mode, with prettier printing")
    print("  -D\t--debug\trun in debug mode. Sets to 1 thread, 1 game")
    print("  -h\t--help\tshow this help\n")

# Make a stack-API request for the n-th page
def req(n):
    req = requests.get(URL % n)
    req.raise_for_status()
    return req.json()

# Pull all the answers via the stack-API
def get_answers():
    n = 1
    api_ans = req(n)
    answers = api_ans['items']
    while api_ans['has_more']:
        n += 1
        if api_ans['quota_remaining']:
            api_ans = req(n)
            answers += api_ans['items']
        else:
            break

    m, r = api_ans['quota_max'], api_ans['quota_remaining']
    if 0.1 * m > r:
        print(" > [WARN]: only %s/%s API-requests remaining!" % (r,m), file=stderr)

    return answers


def download_players():
    players = {}

    for ans in get_answers():
        name = unescape(ans['owner']['display_name'])
        bots = []

        root = html.fromstring('<body>%s</body>' % ans['body'])
        for el in root.findall('.//code'):
            code = el.text
            if re.search(r'^class \w+\(\w*Bot\):.*$', code, flags=re.MULTILINE):
                bots.append(code)

        if not bots:
            print(" > [WARN] user '%s': couldn't locate any bots" % name, file=stderr)
        elif name in players:
            players[name] += bots
        else:
            players[name] = bots

    return players


# Download all bots from codegolf.stackexchange.com
def download_bots():
    print('pulling bots from the interwebs..', file=stderr)
    try:
        players = download_players()
    except Exception as ex:
        print('FAILED: (%s)' % ex, file=stderr)
        exit(1)

    if path.isfile(AUTO_FILE):
        print(' > move: %s -> %s.old' % (AUTO_FILE,AUTO_FILE), file=stderr)
        if path.exists('%s.old' % AUTO_FILE):
            remove('%s.old' % AUTO_FILE)
        rename(AUTO_FILE, '%s.old' % AUTO_FILE)

    print(' > writing players to %s' % AUTO_FILE, file=stderr)
    f = open(AUTO_FILE, 'w+', encoding='utf8')
    f.write('# -*- coding: utf-8 -*- \n')
    f.write('# Bots downloaded from https://codegolf.stackexchange.com/questions/177765 @ %s\n\n' % strftime('%F %H:%M:%S'))
    with open(OWN_FILE, 'r') as bfile:
        f.write(bfile.read()+'\n\n\n# Auto-pulled bots:\n\n')
    for usr in players:
        if usr not in IGNORE:
            for bot in players[usr]:
                f.write('# User: %s\n' % usr)
                f.write(bot+'\n\n')
    f.close()

    print('OK: pulled %s bots' % sum(len(bs) for bs in players.values()))


if __name__ == "__main__":

    games = 10000
    bots_per_game = 8
    threads = 4

    for i, arg in enumerate(sys.argv):
        if arg == "-n" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
            games = int(sys.argv[i+1])
        if arg == "-b" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
            bots_per_game = int(sys.argv[i+1])
        if arg == "-t" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
            threads = int(sys.argv[i+1])
        if arg == "-d" or arg == "--download":
            DOWNLOAD = True
        if arg == "-A" or arg == "--ansi":
            ANSI = True
        if arg == "-D" or arg == "--debug":
            DEBUG = True
        if arg == "-h" or arg == "--help":
            print_help()
            quit()
    if ANSI:
        print(chr(27) + "[2J", flush =  True)
        print_str(1,3,"")
    else:
        print()

    if DOWNLOAD:
        download_bots()
        exit() # Before running other's code, you might want to inspect it..

    if path.isfile(AUTO_FILE):
        exec('from %s import *' % AUTO_FILE[:-3])
    else:
        exec('from %s import *' % OWN_FILE[:-3])

    bots = get_all_bots()

    if bots_per_game > len(bots):
        bots_per_game = len(bots)
    if bots_per_game < 2:
        print("\tAt least 2 bots per game is needed")
        bots_per_game = 2
    if games <= 0:
        print("\tAt least 1 game is needed")
        games = 1
    if threads <= 0:
        print("\tAt least 1 thread is needed")
        threads = 1
    if DEBUG:
        print("\tRunning in debug mode, with 1 thread and 1 game")
        threads = 1
        games = 1

    games_per_thread = math.ceil(games / threads)

    print("\tStarting simulation with %d bots" % len(bots))
    sim_str = "\tSimulating %d games with %d bots per game"
    print(sim_str % (games, bots_per_game))
    print("\tRunning simulation on %d threads" % threads)
    if len(sys.argv) == 1:
        print("\tFor help running the script, use the -h flag")
    print()

    with Pool(threads) as pool:
        t0 = time.time()
        results = pool.starmap(
            run_simulation, 
            [(i, bots_per_game, games_per_thread, bots) for i in range(threads)]
        )
        t1 = time.time()
        if not DEBUG:
            total_bot_stats = [r[0] for r in results]
            total_game_stats = [r[1] for r in results]
            print_results(total_bot_stats, total_game_stats, t1-t0)

Если вы хотите получить доступ к исходному контроллеру для этой задачи, он доступен в истории редактирования. Новый контроллер имеет ту же логику для запуска игры, единственное отличие - производительность, сбор статистики и более приятная печать.

Боты

На моей машине боты хранятся в файле forty_game_bots.py. Если вы используете любое другое имя для файла, вы должны обновить importоператор в верхней части контроллера.

import sys, inspect
import random
import numpy as np

# Returns a list of all bot classes which inherit from the Bot class
def get_all_bots():
    return Bot.__subclasses__()

# The parent class for all bots
class Bot:

    def __init__(self, index, end_score):
        self.index = index
        self.end_score = end_score

    def update_state(self, current_throws):
        self.current_throws = current_throws

    def make_throw(self, scores, last_round):
        yield False


class ThrowTwiceBot(Bot):

    def make_throw(self, scores, last_round):
        yield True
        yield False

class GoToTenBot(Bot):

    def make_throw(self, scores, last_round):
        while sum(self.current_throws) < 10:
            yield True
        yield False

Запуск симуляции

Чтобы запустить симуляцию, сохраните оба приведенных выше фрагмента кода в двух отдельных файлах. Я сохранил их как forty_game_controller.pyи forty_game_bots.py. Затем вы просто используете python forty_game_controller.pyили в python3 forty_game_controller.pyзависимости от конфигурации Python. Следуйте инструкциям оттуда, если вы хотите настроить симуляцию дальше, или попробуйте поработать с кодом, если хотите.

Статистика игры

Если вы создаете бота, который стремится к определенному количеству очков, не принимая во внимание других ботов, это процентили победных очков:

+----------+-----+
|Percentile|Score|
+----------+-----+
|     50.00|   44|
|     75.00|   48|
|     90.00|   51|
|     95.00|   54|
|     99.00|   58|
|     99.90|   67|
|     99.99|  126|
+----------+-----+

Лучшие результаты

По мере появления новых ответов я постараюсь обновлять этот список. Содержимое списка всегда будет из последней симуляции. Боты ThrowTwiceBotи GoToTenBotботы из кода выше, и используются в качестве ссылки. Я провел симуляцию с 10 ^ 8 играми, что заняло около 1 часа. Затем я увидел, что игра достигла стабильности по сравнению с моими пробежками с 10 ^ 7 играми. Однако, поскольку люди все еще публикуют ботов, я больше не буду делать симуляции, пока частота ответов не уменьшится.

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

Теперь у нас есть больше статистики для каждого бота, благодаря AKroell ! Три новых столбца содержат максимальный балл по всем играм, средний балл за игру и средний балл при победе каждого бота.

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

Simulation or 300000000 games between 49 bots completed in 35628.7 seconds
Each game lasted for an average of 3.73 rounds
29127662 games were tied between two or more bots
0 games ran until the round limit, highest round was 22

+-----------------------+----+--------+--------+------+------+-------+------+--------+
|Bot                    |Win%|    Wins|  Played|   Max|   Avg|Avg win|Throws|Success%|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|OptFor2X               |21.6|10583693|48967616|    99| 20.49|  44.37|  4.02|   33.09|
|Rebel                  |20.7|10151261|48977862|   104| 21.36|  44.25|  3.90|   35.05|
|Hesitate               |20.3| 9940220|48970815|   105| 21.42|  44.23|  3.89|   35.11|
|EnsureLead             |20.3| 9929074|48992362|   101| 20.43|  44.16|  4.50|   25.05|
|StepBot                |20.2| 9901186|48978938|    96| 20.42|  43.47|  4.56|   24.06|
|BinaryBot              |20.1| 9840684|48981088|   115| 21.01|  44.48|  3.85|   35.92|
|Roll6Timesv2           |20.1| 9831713|48982301|   101| 20.83|  43.53|  4.37|   27.15|
|AggressiveStalker      |19.9| 9767637|48979790|   110| 20.46|  44.86|  3.90|   35.04|
|FooBot                 |19.9| 9740900|48980477|   100| 22.03|  43.79|  3.91|   34.79|
|QuotaBot               |19.9| 9726944|48980023|   101| 19.96|  44.95|  4.50|   25.03|
|BePrepared             |19.8| 9715461|48978569|   112| 18.68|  47.58|  4.30|   28.31|
|AdaptiveRoller         |19.7| 9659023|48982819|   107| 20.70|  43.27|  4.51|   24.81|
|GoTo20Bot              |19.6| 9597515|48973425|   108| 21.15|  43.24|  4.44|   25.98|
|Gladiolen              |19.5| 9550368|48970506|   107| 20.16|  45.31|  3.91|   34.81|
|LastRound              |19.4| 9509645|48988860|   100| 20.45|  43.50|  4.20|   29.98|
|BrainBot               |19.4| 9500957|48985984|   105| 19.26|  45.56|  4.46|   25.71|
|GoTo20orBestBot        |19.4| 9487725|48975944|   104| 20.98|  44.09|  4.46|   25.73|
|Stalker                |19.4| 9485631|48969437|   103| 20.20|  45.34|  3.80|   36.62|
|ClunkyChicken          |19.1| 9354294|48972986|   112| 21.14|  45.44|  3.57|   40.48|
|FortyTeen              |18.8| 9185135|48980498|   107| 20.90|  46.77|  3.88|   35.32|
|Crush                  |18.6| 9115418|48985778|    96| 14.82|  43.08|  5.15|   14.15|
|Chaser                 |18.6| 9109636|48986188|   107| 19.52|  45.62|  4.06|   32.39|
|MatchLeaderBot         |16.6| 8122985|48979024|   104| 18.61|  45.00|  3.20|   46.70|
|Ro                     |16.5| 8063156|48972140|   108| 13.74|  48.24|  5.07|   15.44|
|TakeFive               |16.1| 7906552|48994992|   100| 19.38|  44.68|  3.36|   43.96|
|RollForLuckBot         |16.1| 7901601|48983545|   109| 17.30|  50.54|  4.72|   21.30|
|Alpha                  |15.5| 7584770|48985795|   104| 17.45|  46.64|  4.04|   32.67|
|GoHomeBot              |15.1| 7418649|48974928|    44| 13.23|  41.41|  5.49|    8.52|
|LeadBy5Bot             |15.0| 7354458|48987017|   110| 17.15|  46.95|  4.13|   31.16|
|NotTooFarBehindBot     |15.0| 7338828|48965720|   115| 17.75|  45.03|  2.99|   50.23|
|GoToSeventeenRollTenBot|14.1| 6900832|48976440|   104| 10.26|  49.25|  5.68|    5.42|
|LizduadacBot           |14.0| 6833125|48978161|    96|  9.67|  51.35|  5.72|    4.68|
|TleilaxuBot            |13.5| 6603853|48985292|   137| 15.25|  45.05|  4.27|   28.80|
|BringMyOwn_dice        |12.0| 5870328|48974969|    44| 21.27|  41.47|  4.24|   29.30|
|SafetyNet              |11.4| 5600688|48987015|    98| 15.81|  45.03|  2.41|   59.84|
|WhereFourArtThouChicken|10.5| 5157324|48976428|    64| 22.38|  47.39|  3.59|   40.19|
|ExpectationsBot        | 9.0| 4416154|48976485|    44| 24.40|  41.55|  3.58|   40.41|
|OneStepAheadBot        | 8.4| 4132031|48975605|    50| 18.24|  46.02|  3.20|   46.59|
|GoBigEarly             | 6.6| 3218181|48991348|    49| 20.77|  42.95|  3.90|   35.05|
|OneInFiveBot           | 5.8| 2826326|48974364|   155| 17.26|  49.72|  3.00|   50.00|
|ThrowThriceBot         | 4.1| 1994569|48984367|    54| 21.70|  44.55|  2.53|   57.88|
|FutureBot              | 4.0| 1978660|48985814|    50| 17.93|  45.17|  2.36|   60.70|
|GamblersFallacy        | 1.3|  621945|48986528|    44| 22.52|  41.46|  2.82|   53.07|
|FlipCoinRollDice       | 0.7|  345385|48972339|    87| 15.29|  44.55|  1.61|   73.17|
|BlessRNG               | 0.2|   73506|48974185|    49| 14.54|  42.72|  1.42|   76.39|
|StopBot                | 0.0|    1353|48984828|    44| 10.92|  41.57|  1.00|   83.33|
|CooperativeSwarmBot    | 0.0|     991|48970284|    44| 10.13|  41.51|  1.36|   77.30|
|PointsAreForNerdsBot   | 0.0|       0|48986508|     0|  0.00|   0.00|  6.00|    0.00|
|SlowStart              | 0.0|       0|48973613|    35|  5.22|   0.00|  3.16|   47.39|
+-----------------------+----+--------+--------+------+------+-------+------+--------+

Следующие боты (кроме Rebel) сделаны, чтобы изменить правила, и создатели согласились не принимать участие в официальном турнире. Тем не менее, я все еще думаю, что их идеи являются творческими, и они заслуживают достойного упоминания. Мятежник также в этом списке, потому что он использует умную стратегию, чтобы избежать саботажа, и на самом деле лучше работает с саботирующим ботом в игре.

Боты NeoBotи KwisatzHaderachправда следуют правилам, но используют лазейки, предсказывая случайный генератор. Поскольку эти боты требуют много ресурсов для симуляции, я добавил их статистику из симуляции с меньшим количеством игр. Бот HarkonnenBotдобивается победы, отключая всех других ботов, что строго противоречит правилам.

    Simulation or 300000 games between 52 bots completed in 66.2 seconds
    Each game lasted for an average of 4.82 rounds
    20709 games were tied between two or more bots
    0 games ran until the round limit, highest round was 31

    +-----------------------+----+--------+--------+------+------+-------+------+--------+
    |Bot                    |Win%|    Wins|  Played|   Max|   Avg|Avg win|Throws|Success%|
    +-----------------------+----+--------+--------+------+------+-------+------+--------+
    |KwisatzHaderach        |80.4|   36986|   46015|   214| 58.19|  64.89| 11.90|   42.09|
    |HarkonnenBot           |76.0|   35152|   46264|    44| 34.04|  41.34|  1.00|   83.20|
    |NeoBot                 |39.0|   17980|   46143|   214| 37.82|  59.55|  5.44|   50.21|
    |Rebel                  |26.8|   12410|   46306|    92| 20.82|  43.39|  3.80|   35.84|
    +-----------------------+----+--------+--------+------+------+-------+------+--------+

    +----------+-----+
    |Percentile|Score|
    +----------+-----+
    |     50.00|   45|
    |     75.00|   50|
    |     90.00|   59|
    |     95.00|   70|
    |     99.00|   97|
    |     99.90|  138|
    |     99.99|  214|
    +----------+-----+

2
Так что, возможно, правила были бы немного более понятными, если бы они сказали, что «когда игрок заканчивает свой ход со счетом не менее 40, все остальные получают последний ход». Это позволяет избежать очевидного конфликта, указывая, что он не достигает 40, что действительно запускает последний раунд, он останавливается как минимум с 40.
aschepler

1
@aschepler, это хорошая формулировка, я буду редактировать пост, когда я на моем компьютере
maxb

2
@maxb Я расширил контроллер, добавив больше статистических данных, имеющих отношение к моему процессу разработки: достигнут высокий балл, достигнут средний балл и средний балл победы gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell

2
Это звучит очень похоже на самой игре весело кости называется Farkled en.wikipedia.org/wiki/Farkle
Калеб Jay

5
Я голосую, чтобы закрыть этот вопрос, потому что он уже де-факто закрыт для новых ответов («Турнир завершен! Финальный симулятор проводился ночью, всего 3 * 108 игр»)
pppery

Ответы:


6

OptFor2X

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

class OptFor2X(Bot):

    _r = []
    _p = []

    def _u(self,l):
        res = []
        for x in l:
            if isinstance(x,int):
                if x>0:
                    a=b=x
                else:
                    a,b=-2,-x
            else:
                if len(x)==1:
                    a = x[0]
                    if a<0:
                        a,b=-3,-a
                    else:
                        b=a+2
                else:
                    a,b=x
            if a<0:
                res.extend((b for _ in range(-a)))
            else:
                res.extend(range(a,b+1))
        res.extend((res[-1] for _ in range(40-len(res))))
        return res


    def __init__(self,*args):
        super().__init__(*args)
        if self._r:
            return
        self._r.append(self._u([[-8, 14], -15, [-6, 17], [18, 21], [21],
                                 -23, -24, 25, [-3, 21], [22, 29]]))
        self._r.extend((None for _ in range(13)))
        self._r.extend((self._u(x) for x in
                   ([[-19, 13], [-4, 12], -13, [-14], [-5, 15], [-4, 16],
                     -17, 18],
                    [[-6, 12], [-11, 13], [-4, 12], -11, -12, [-13], [-14],
                     [-5, 15], -16, 17],
                    [11, 11, [-10, 12], -13, [-24], 13, 12, [-6, 11], -12,
                     [-13], [-6, 14], -15, 16],
                    [[-8, 11], -12, 13, [-9, 23], 11, [-10], [-11], [-12],
                     [-5, 13], -14, [14]],
                    [[-4, 10], [-11], 12, [-14, 22], 10, 9, -10, [-4, 11],
                     [-5, 12], -13, -14, 15],
                    [[-4, 10], 11, [-18, 21], [-9], [-10], [-5, 11], [-12],
                     -13, 14],
                    [[-24, 20], [-5, 9], [-4, 10], [-4, 11], -12, 13],
                    [[-25, 19], [-8], [-4, 9], [-4, 10], -11, 12],
                    [[-26, 18], [-5, 8], [-5, 9], 10, [10]],
                    [[-27, 17], [-4, 7], [-5, 8], 9, [9]],
                    [[-28, 16], -6, [-5, 7], -8, -9, 10],
                    [[-29, 15], [-5, 6], [-7], -8, 9],
                    [[-29, 14], [-4, 5], [-4, 6], [7]],
                    [[-30, 13], -4, [-4, 5], 6, [6]], 
                    [[-31, 12], [-5, 4], 5, [5]],
                    [[-31, 11], [-4, 3], [3], 5, 6],
                    [[-31, 10], 11, [-2], 3, [3]],
                    [[-31, 9], 10, 2, -1, 2, [2]],
                    [[-31, 8], 9, [-4, 1], [1]],
                    [[-30, 7], [7], [-5, 1], 2],
                    [[-30, 6], [6], 1],
                    [[-31, 5], [6], 1],
                    [[-31, 4], [5, 8], 1],
                    [[-31, 3], [4, 7], 1],
                    [[-31, 2], [3, 6], 1],
                    [[-31, 1], [2, 10]] ) ))
        l=[0.0,0.0,0.0,0.0,1.0]
        for i in range(300):
            l.append(sum([a/6 for a in l[i:]]))
        m=[i/6 for i in range(1,5)]
        self._p.extend((1-sum([a*b for a,b in zip(m,l[i:])])
                                           for i in range(300)))

    def update_state(self,*args):
        super().update_state(*args)
        self.current_sum = sum(self.current_throws)

    def expect(self,mts,ops):
        p = 1.0
        for s in ops:
            p *= self._p[mts-s]
        return p

    def throw_again(self,mts,ops):
        ps = self.expect(mts,ops)
        pr = sum((self.expect(mts+d,ops) for d in range(1,6)))/6
        return pr>ps

    def make_throw(self,scores,last_round):
        myscore=scores[self.index]
        if last_round:
            target=max(scores)-myscore
            if max(scores)<40:
                opscores = scores[self.index+1:]
            else:
                opscores = []
                i = (self.index + 1) % len(scores)
                while scores[i] < 40:
                    opscores.append(scores[i])
                    i = (i+1) % len(scores)
        else:
            opscores = [s for i,s in enumerate(scores) if i!=self.index]
            bestop = max(opscores)
            target = min(self._r[myscore][bestop],40-myscore)
            # (could change the table instead of using min)
        while self.current_sum < target:
            yield True
        lr = last_round or myscore+self.current_sum >= 40
        while lr and self.throw_again(myscore+self.current_sum,opscores):
            yield True
        yield False

Я посмотрю реализацию, как только смогу. С празднованием Рождества, это может быть не до 25-го
максимум

Ваш бот лидирует! Кроме того, нет необходимости заставлять его работать быстрее, это примерно так же быстро, как и все другие боты при принятии решений.
maxb

Я не хотел делать это быстрее. Я уже сделал то, что хотел - инициализировать только один раз - но искал более хороший способ сделать это, особенно без определения функций вне класса. Я думаю, что теперь лучше.
Кристиан Сиверс

Сейчас это выглядит намного лучше, хорошая работа!
maxb

Поздравляем с получением как первого, так и второго места!
максимум

20

NeoBot

Вместо этого только попытайтесь осознать истину - ложки нет

NeoBot заглядывает в матрицу (она же случайная) и предсказывает, будет ли следующий бросок 6 или нет - он ничего не может поделать с получением 6 для начала, но более чем рад уклониться от конца серии.

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

class NeoBot(Bot):
    def __init__(self, index, end_score):
        self.random = None
        self.last_scores = None
        self.last_state = None
        super().__init__(index,end_score)

    def make_throw(self, scores, last_round):
        while True:
            if self.random is None:
                self.random = inspect.stack()[1][0].f_globals['random']
            tscores = scores[:self.index] + scores[self.index+1:]
            if self.last_scores != tscores:
                self.last_state = None
                self.last_scores = tscores
            future = self.predictnext_randint(self.random)
            if future == 6:
                yield False
            else:
                yield True

    def genrand_int32(self,base):
        base ^= (base >> 11)
        base ^= (base << 7) & 0x9d2c5680
        base ^= (base << 15) & 0xefc60000
        return base ^ (base >> 18)

    def predictnext_randint(self,cls):
        if self.last_state is None:
            self.last_state = list(cls.getstate()[1])
        ind = self.last_state[-1]
        width = 6
        res = width + 1
        while res >= width:
            y = self.last_state[ind]
            r = self.genrand_int32(y)
            res = r >> 29
            ind += 1
            self.last_state[-1] = (self.last_state[-1] + 1) % (len(self.last_state))
        return 1 + res

1
Добро пожаловать в PPCG! Это действительно впечатляющий ответ. Когда я впервые запустил его, я был обеспокоен тем фактом, что он использовал то же количество времени выполнения, что и все остальные боты вместе взятые. Затем я посмотрел на процент выигрыша. Действительно умный способ обхода правил. Я позволю вашему боту участвовать в турнире, но я надеюсь, что другие воздержатся от использования той же тактики, что и эта, поскольку это нарушает дух игры.
максимум

2
Поскольку между этим ботом и вторым местом существует такой огромный разрыв, в сочетании с тем фактом, что вашему боту требуется много вычислений, вы согласитесь с тем, что я запускаю симуляцию с меньшим количеством итераций, чтобы определить ваш выигрыш, а затем запускаю официальный симуляция без вашего бота?
максимум

3
Прекрасно для меня, я подумал, что это может быть дисквалифицируемо и определенно не совсем в духе игры. Тем не менее, это был взрыв, чтобы получить работу и забавное оправдание, чтобы покопаться в исходном коде Python.
В основном безвредный

2
Спасибо! Я не думаю, что любой другой бот приблизится к вашему счету. И для тех, кто думает о реализации этой стратегии, не надо. Отныне эта стратегия противоречит правилам, и NeoBot - единственный, кому разрешено использовать ее для сохранения честности турнира.
максимум

1
Что ж, myBot превосходит всех, но это намного лучше - хотя, если бы я размещал бота таким образом, я бы получил -100 и не лучший результат.
Ян Иван

15

Кооперативный Рой

стратегия

Я не думаю, что кто-то еще заметил значение этого правила:

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

Если бы каждый бот всегда катался до тех пор, пока он не рухнул, у каждого был бы нулевой счет в конце раунда 200, и все выиграли бы! Таким образом, стратегия Cooperative Swarm состоит в том, чтобы сотрудничать до тех пор, пока все игроки имеют нулевой счет, но играть нормально, если кто-либо набрал какие-либо очки.

В этом посте я отправляю двух ботов: первый - CooperativeSwarmBot, а второй - CooperativeThrowTwice. CooperativeSwarmBot служит базовым классом для всех ботов, которые формально являются частью кооперативного роя, и имеет поведение заполнителя, просто принимая свой первый успешный бросок, когда сотрудничество не удается. CooperativeSwarmBot имеет CooperativeSwarmBot в качестве родителя и идентичен ему во всех отношениях, за исключением того, что его некооперативное поведение состоит в том, чтобы сделать два броска вместо одного. В следующие несколько дней я буду пересматривать этот пост, чтобы добавить новых ботов, которые используют гораздо более интеллектуальное поведение, играя против не-кооперативных ботов.

Код

class CooperativeSwarmBot(Bot):
    def defection_strategy(self, scores, last_round):
        yield False

    def make_throw(self, scores, last_round):
        cooperate = max(scores) == 0
        if (cooperate):
            while True:
                yield True
        else:
            yield from self.defection_strategy(scores, last_round)

class CooperativeThrowTwice(CooperativeSwarmBot):
    def defection_strategy(self, scores, last_round):
        yield True
        yield False

Анализ

осуществимость

В этой игре очень сложно сотрудничать, потому что для ее работы нам нужна поддержка всех восьми игроков. Поскольку каждый класс ботов ограничен одним экземпляром на игру, это трудная цель для достижения. Например, вероятность выбора восьми кооперативных ботов из пула из 100 кооперативных ботов и 30 неработающих ботов:

100130991299812897127961269512594124931230.115

icn

c!÷(ci)!(c+n)!÷(c+ni)!

i=8n=38

Тематическое исследование

По ряду причин (см. Сноски 1 и 2) надлежащий кооперативный рой никогда не будет соревноваться в официальных играх. Поэтому я подведу итоги одного из моих собственных симуляций в этом разделе.

Этот симулятор запустил 10000 игр, используя 38 других ботов, которые были опубликованы здесь в прошлый раз, когда я проверял, и 2900 ботов, у которых CooperativeSwarmBot был их родительским классом. Контролер сообщил, что 9051 из 10000 игр (90,51%) закончились за 200 раундов, что довольно близко к прогнозу, что 90% игр будут кооперативными. Реализация этих ботов была тривиальной; кроме CooperativeSwarmBot они все приняли эту форму:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Менее 3% ботов имели процент выигрышей ниже 80%, и чуть более 11% ботов выигрывали каждую игру, в которую они играли. Средний процент выигрыша у 2900 ботов в рое составляет около 86%, что невероятно хорошо. Для сравнения, лучшие игроки в текущем официальном списке лидеров выигрывают менее чем в 22% своих игр. Я не могу уместить полный список кооперативного роя в пределах максимально допустимой длины ответа, поэтому, если вы хотите увидеть, что вам придется вместо этого перейти сюда: https://pastebin.com/3Zc8m1Ex

Так как каждый бот играл в среднем около 27 игр, удача играет относительно большой бросок, когда вы смотрите на результаты для отдельных ботов. Поскольку я еще не реализовал продвинутую стратегию для некооперативных игр, большинство других ботов сильно выиграли от игры против кооперативного роя, выполняя даже средний показатель выигрыша кооперативного роя в 86%.

Полные результаты для ботов, которых нет в рое, перечислены ниже; Есть два бота, результаты которых, я думаю, заслуживают особого внимания. Во-первых, StopBot не смог выиграть ни одной игры. Это особенно трагично, потому что кооперативный рой фактически использовал ту же стратегию, что и StopBot; Вы бы ожидали, что StopBot выиграет восемь своих игр случайно, и чуть больше, потому что кооперативный рой вынужден дать своим оппонентам первый ход. Второй интересный результат, однако, заключается в том, что кропотливая работа PointsAreForNerdsBot наконец-то окупилась: он сотрудничал с роем и сумел выиграть каждую игру, в которую играл!

+---------------------+----+--------+--------+------+------+-------+------+--------+
|Bot                  |Win%|    Wins|  Played|   Max|   Avg|Avg win|Throws|Success%|
+---------------------+----+--------+--------+------+------+-------+------+--------+
|AggressiveStalker    |100.0|      21|      21|    42| 40.71|  40.71|  3.48|   46.32|
|PointsAreForNerdsBot |100.0|      31|      31|     0|  0.00|   0.00|  6.02|    0.00|
|TakeFive             |100.0|      18|      18|    44| 41.94|  41.94|  2.61|   50.93|
|Hesitate             |100.0|      26|      26|    44| 41.27|  41.27|  3.32|   41.89|
|Crush                |100.0|      34|      34|    44| 41.15|  41.15|  5.38|    6.73|
|StepBot              |97.0|      32|      33|    46| 41.15|  42.44|  4.51|   24.54|
|LastRound            |96.8|      30|      31|    44| 40.32|  41.17|  3.54|   45.05|
|Chaser               |96.8|      30|      31|    47| 42.90|  44.33|  3.04|   52.16|
|GoHomeBot            |96.8|      30|      31|    44| 40.32|  41.67|  5.60|    9.71|
|Stalker              |96.4|      27|      28|    44| 41.18|  41.44|  2.88|   57.53|
|ClunkyChicken        |96.2|      25|      26|    44| 40.96|  41.88|  2.32|   61.23|
|AdaptiveRoller       |96.0|      24|      25|    44| 39.32|  40.96|  4.49|   27.43|
|GoTo20Bot            |95.5|      21|      22|    44| 40.36|  41.33|  4.60|   30.50|
|FortyTeen            |95.0|      19|      20|    48| 44.15|  45.68|  3.71|   43.97|
|BinaryBot            |94.3|      33|      35|    44| 41.29|  41.42|  2.87|   53.07|
|EnsureLead           |93.8|      15|      16|    55| 42.56|  42.60|  4.04|   26.61|
|Roll6Timesv2         |92.9|      26|      28|    45| 40.71|  42.27|  4.07|   29.63|
|BringMyOwn_dice      |92.1|      35|      38|    44| 40.32|  41.17|  4.09|   28.40|
|LizduadacBot         |92.0|      23|      25|    54| 47.32|  51.43|  5.70|    5.18|
|FooBot               |91.7|      22|      24|    44| 39.67|  41.45|  3.68|   51.80|
|Alpha                |91.7|      33|      36|    48| 38.89|  42.42|  2.16|   65.34|
|QuotaBot             |90.5|      19|      21|    53| 38.38|  42.42|  3.88|   24.65|
|GoBigEarly           |88.5|      23|      26|    47| 41.35|  42.87|  3.33|   46.38|
|ExpectationsBot      |88.0|      22|      25|    44| 39.08|  41.55|  3.57|   45.34|
|LeadBy5Bot           |87.5|      21|      24|    50| 37.46|  42.81|  2.20|   63.88|
|GamblersFallacy      |86.4|      19|      22|    44| 41.32|  41.58|  2.05|   63.11|
|BePrepared           |86.4|      19|      22|    59| 39.59|  44.79|  3.81|   35.96|
|RollForLuckBot       |85.7|      18|      21|    54| 41.95|  47.67|  4.68|   25.29|
|OneStepAheadBot      |84.6|      22|      26|    50| 41.35|  46.00|  3.34|   42.97|
|FlipCoinRollDice     |78.3|      18|      23|    51| 37.61|  44.72|  1.67|   75.42|
|BlessRNG             |77.8|      28|      36|    47| 40.69|  41.89|  1.43|   83.66|
|FutureBot            |77.4|      24|      31|    49| 40.16|  44.38|  2.41|   63.99|
|SlowStart            |68.4|      26|      38|    57| 38.53|  45.31|  1.99|   66.15|
|NotTooFarBehindBot   |66.7|      20|      30|    50| 37.27|  42.00|  1.29|   77.61|
|ThrowThriceBot       |63.0|      17|      27|    51| 39.63|  44.76|  2.50|   55.67|
|OneInFiveBot         |58.3|      14|      24|    54| 33.54|  44.86|  2.91|   50.19|
|MatchLeaderBot       |48.1|      13|      27|    49| 40.15|  44.15|  1.22|   82.26|
|StopBot              | 0.0|       0|      27|    43| 30.26|   0.00|  1.00|   82.77|
+---------------------+----+--------+--------+------+------+-------+------+--------+

Дефекты

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

Сотрудничая, все боты могут достичь оптимального решения при 100% выигрыше. Таким образом, если бы выигрыш был единственным, что имело значение, тогда сотрудничество было бы устойчивым равновесием, и не о чем беспокоиться. Тем не менее, некоторые боты могут расставлять приоритеты перед другими целями, такими как достижение вершины списка лидеров. Это означает, что существует риск того, что другой бот может выйти из строя после вашего последнего хода, что создает стимул для вас в первую очередь. Поскольку установка этого соревнования не позволяет нам видеть то, что делали наши оппоненты в их предыдущих играх, мы не можем наказать тех, кто бежал. Таким образом, сотрудничество в конечном итоге является неустойчивым равновесием, обреченным на провал.

Сноски

[1]: Главные причины, по которым я не хочу отправлять тысячи ботов вместо двух, заключаются в том, что это замедлит симуляцию в 1000 раз [2], и это может существенно испортить ситуацию. процент выигрышей, так как другие боты будут играть исключительно против роя, а не друг против друга. Более важным, однако, является тот факт, что даже если бы я захотел, я не смог бы создать столько ботов в разумные сроки, не нарушая дух правила: «Бот не должен реализовывать ту же стратегию, что и существующий, намеренно или случайно ".

[2]: Я думаю, что есть две основные причины, по которым симуляция замедляется при запуске кооперативного роя. Во-первых, чем больше ботов, тем больше игр, если вы хотите, чтобы каждый бот играл в одинаковом количестве игр (в данном примере количество игр будет отличаться примерно в 77 раз). Во-вторых, совместные игры занимают больше времени, потому что они длятся целых 200 раундов, и в течение раунда игроки должны продолжать катиться бесконечно. Для моей установки игры имитировали примерно в 40 раз больше: для запуска 10000 игр на практическом примере потребовалось чуть более трех минут, но после удаления кооперативного роя 10000 игр были бы завершены всего за 4,5 секунды. Я полагаю, что между этими двумя причинами потребуется приблизительно 3100 раз больше времени, чтобы точно измерить производительность ботов, когда конкурирует рой по сравнению с тем, когда их нет.


4
Ух ты. И добро пожаловать в PPCG. Это довольно первый ответ. Я на самом деле не планировал такую ​​ситуацию. Вы, конечно, нашли лазейку в правилах. Я не совсем уверен, как мне это оценить, так как ваш ответ - это набор ботов, а не один бот. Однако единственное, что я сейчас скажу, это то, что нечестно, если один участник будет контролировать 98,7% всех ботов.
maxb

2
Я на самом деле не хочу, чтобы дублирующие боты участвовали в официальном соревновании; Вот почему я запустил симуляцию самостоятельно, вместо того, чтобы отправлять тысячи почти одинаковых ботов. Я пересмотрю свое представление, чтобы сделать это более ясным.
Эйнхендер

1
Если бы я ожидал такого ответа, я бы изменил игры, которые идут на 200 раундов, чтобы они не давали очки игрокам. Однако, как вы заметили, существует правило о создании идентичных ботов, которые делают эту стратегию противоречащей правилам. Я не собираюсь менять правила, так как это было бы несправедливо по отношению ко всем, кто сделал бот. Тем не менее, концепция сотрудничества очень интересна, и я надеюсь, что будут представлены другие боты, которые реализуют стратегию сотрудничества в сочетании со своей собственной уникальной стратегией.
maxb

1
Я думаю, что ваш пост понятен после прочтения.
maxb

Сколько существующих ботов должно было бы обернуть свой код в эту структуру сотрудничества, чтобы большинство из них увидели чистый выигрыш в размещении в таблице лидеров? Мое наивное предположение 50%.
Спарр

10

GoTo20Bot

class GoTo20Bot(Bot):

    def make_throw(self, scores, last_round):
        target = min(20, 40 - scores[self.index])
        if last_round:
            target = max(scores) - scores[self.index] + 1
        while sum(self.current_throws) < target:
            yield True
        yield False

Просто попробуйте все GoToNBot, А 20, 22, 24 играет лучше всего. Я не знаю почему.


Обновление: всегда останавливайте бросок, если получите счет 40 или больше.


Я также экспериментировал с такими ботами. Наибольший средний балл за раунд достигается, когда бот достигает 16, но я предполагаю, что «конечная игра» заставляет 20-бот выигрывать чаще.
Макс

@ maxb Не так, 20 все равно будет лучшим без "конечной игры" в моем тесте. Может быть, вы проверили это на старой версии контроллера.
18:00

Я выполнил отдельный тест перед разработкой этого испытания, где я вычислил средний балл за раунд для двух тактик в моем посте («брось х раз» и «брось до х счет»), и максимум, который я нашел, был для 15-16 , Хотя размер моей выборки мог быть слишком мал, я заметил нестабильность.
Макс

2
Я провел некоторое тестирование с этим, и я пришел к выводу, что 20 хорошо работает, потому что это 40/2. Хотя я не совсем уверен. Когда я установил end_score4000 (и изменил своего бота, чтобы использовать это в targetрасчете), 15-16 ботов были намного лучше. Но если бы игра была только для увеличения вашего счета, это было бы тривиально.
maxb

1
@maxb Если end_score4000, то почти 200 нельзя получить до 200 ходов. А игра просто в том, кто набрал наибольшее количество очков за 200 ходов. И остановка на 15 должна сработать, так как на этот раз стратегия наивысшего результата за один ход такая же, как наивысшая оценка за 200 ходов.
ч. В

10

Адаптивный ролик

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

class AdaptiveRoller(Bot):

    def make_throw(self, scores, last_round):
        lim = min(self.end_score - scores[self.index], 22)
        while sum(self.current_throws) < lim:
            yield True
        if max(scores) == scores[self.index] and max(scores) >= self.end_score:
            yield True
        while last_round and scores[self.index] + sum(self.current_throws) <= max(scores):
            yield True
        yield False

Отличная первая подача! Я использую его для своих ботов, которые я написал для тестирования, но обновлю рекорд, когда будет опубликовано больше ботов.
Макс

Я провел несколько тестов с небольшими изменениями в вашем боте. lim = max(min(self.end_score - scores[self.index], 24), 6)Увеличение максимального значения до 24 и добавление минимального значения 6 увеличивает процент выигрыша сам по себе и, тем более, в совокупности.
AKroell

@AKroell: Круто! Я намеревался сделать что-то подобное, чтобы убедиться, что в конце оно катится несколько раз, но я еще не удосужился сделать это. Странно, хотя, кажется, работает хуже с этими значениями, когда я делаю 100 000 пробежек. Я только проверил с 18 ботами, хотя. Может быть, я должен сделать несколько тестов со всеми ботами.
Emigna

5

Альфа

class Alpha(Bot):
    def make_throw(self, scores, last_round):
        # Throw until we're the best.
        while scores[self.index] + sum(self.current_throws) <= max(scores):
            yield True

        # Throw once more to assert dominance.
        yield True
        yield False

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


Из-за того yield, как работает, если он начинает катиться, он никогда не остановится. Вы хотите обновить my_scoreв цикле.
Spitemaster

@Spitemaster Исправлено, спасибо.
Мнемоник

5

NotTooFarBehindBot

class NotTooFarBehindBot(Bot):
    def make_throw(self, scores, last_round):
        while True:
            current_score = scores[self.index] + sum(self.current_throws)
            number_of_bots_ahead = sum(1 for x in scores if x > current_score)
            if number_of_bots_ahead > 1:
                yield True
                continue
            if number_of_bots_ahead != 0 and last_round:
                yield True
                continue
            break
        yield False

Идея состоит в том, что другие боты могут потерять очки, поэтому быть вторым неплохо - но если вы сильно отстали, вы могли бы пойти ва-банк.


1
Добро пожаловать в PPCG! Я просматриваю ваши материалы, и кажется, что чем больше игроков в игре, тем ниже процент выигрышей для вашего бота. Я не могу сказать, почему сразу. Когда боты подбираются 1vs1, вы получаете 10% выигрыша. Идея звучит многообещающе, а код выглядит правильно, поэтому я не могу точно сказать, почему ваш выигрыш не выше.
maxb

6
Я изучил поведение, и эта строка меня смутила 6: Bot NotTooFarBehindBot plays [4, 2, 4, 2, 3, 3, 5, 5, 1, 4, 1, 4, 2, 4, 3, 6] with scores [0, 9, 0, 20, 0, 0, 0] and last round == False. Несмотря на то, что ваш бот лидирует после 7 бросков, он продолжается до тех пор, пока не достигнет 6 баллов. Когда я набирал это, я понял проблему! scoresСодержат только общее количество баллов, а не штампованные случаев для текущего раунда. Вы должны изменить это, чтобы быть current_score = scores[self.index] + sum(self.current_throws).
maxb

Спасибо - сделаю это изменение!
Стюарт Мур

5

GoHomeBot

class GoHomeBot(Bot):
    def make_throw(self, scores, last_round):
        while scores[self.index] + sum(self.current_throws) < 40:
            yield True
        yield False

Мы хотим стать большими или пойти домой, верно? GoHomeBot в основном просто идет домой. (Но на удивление хорошо!)


Так как этот бот всегда идет за 40 очков, у него никогда не будет никаких пунктов в scoresсписке. Раньше был такой бот (бот GoToEnd), но Дэвид удалил свой ответ. Я заменю этого бота вашим.
более

1
Это довольно забавно, видеть расширенную статистику этого бота: за исключением pointsAreForNerds и StopBot, у этого бота самые низкие средние баллы, и все же у него хорошее соотношение выигрышей
Belhenix

5

EnsureLead

class EnsureLead(Bot):

    def make_throw(self, scores, last_round):
        otherScores = scores[self.index+1:] + scores[:self.index]
        maxOtherScore = max(otherScores)
        maxOthersToCome = 0
        for i in otherScores:
            if (i >= 40): break
            else: maxOthersToCome = max(maxOthersToCome, i)
        while True:
            currentScore = sum(self.current_throws)
            totalScore = scores[self.index] + currentScore
            if not last_round:
                if totalScore >= 40:
                    if totalScore < maxOtherScore + 10:
                        yield True
                    else:
                        yield False
                elif currentScore < 20:
                    yield True
                else:
                    yield False
            else:
                if totalScore < maxOtherScore + 1:
                    yield True
                elif totalScore < maxOthersToCome + 10:
                    yield True
                else:
                    yield False

EnsureLead заимствует идеи из GoTo20Bot. Он добавляет концепцию, которая всегда учитывает (когда в last_round или достижении 40), что есть другие, которые будут иметь по крайней мере еще один бросок. Таким образом, бот пытается опередить их, так что им нужно наверстать упущенное.


4

Roll6TimesV2

Не побеждает текущий лучше, но я думаю, что будет лучше, если в игре будет больше ботов.

class Roll6Timesv2(Bot):
    def make_throw(self, scores, last_round):

        if not last_round:
            i = 0
            maximum=6
            while ((i<maximum) and sum(self.current_throws)+scores[self.index]<=40 ):
                yield True
                i=i+1

        if last_round:
            while scores[self.index] + sum(self.current_throws) < max(scores):
                yield True
        yield False

Кстати, классная игра.


Добро пожаловать в PPCG! Очень впечатляет не только ваш первый вызов KotH, но и ваш первый ответ. Рад, что вам понравилась игра! У меня было много дискуссий о лучшей тактике для игры после того вечера, когда я играл в нее, так что это казалось идеальным для испытания. В настоящее время вы находитесь на третьем месте из 18.
Maxb

4

StopBot

class StopBot(Bot):
    def make_throw(self, scores, last_round):
        yield False

Буквально только один бросок.

Это эквивалентно базовому Botклассу.


1
Не жалей! Вы следуете всем правилам, хотя я боюсь, что ваш бот не очень эффективен - в среднем 2,5 очка за раунд.
maxb

1
Я знаю, кто-то должен был опубликовать этот бот, хотя. Вырожденные боты за утерю.
Захари

5
Я бы сказал, что я впечатлен тем, что ваш бот обеспечил ровно одну победу в последней симуляции, доказав, что это не совсем бесполезно.
максимум

2
ЭТО ВЫИГРАЛ ИГРУ ?! Это удивительно.
Захари

3

BringMyOwn_dice (BMO_d)

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

class BringMyOwn_dice(Bot):

    def __init__(self, *args):
        import random as rnd
        self.die = lambda: rnd.randint(1,6)
        super().__init__(*args)

    def make_throw(self, scores, last_round):

        nfaces = self.die() + self.die()

        s = scores[self.index]
        max_scores = max(scores)

        for _ in range(nfaces):
            if s + sum(self.current_throws) > 39:
                break
            yield True

        yield False

2
Я думал о случайном боте, использующем бросок монеты, но это больше соответствует духу задачи! Я думаю, что две кости показывают лучшие результаты, так как вы получаете наибольшее количество очков за раунд, когда вы бросаете кубик 5-6 раз, что близко к среднему показателю при броске двух кубиков.
Макс

3

FooBot

class FooBot(Bot):
    def make_throw(self, scores, last_round):
        max_score = max(scores)

        while True:
            round_score = sum(self.current_throws)
            my_score = scores[self.index] + round_score

            if last_round:
                if my_score >= max_score:
                    break
            else:
                if my_score >= self.end_score or round_score >= 16:
                    break

            yield True

        yield False

# Must throw at least onceне нужен - он бросает один раз перед вызовом вашего бота. Ваш бот всегда будет бросать минимум два раза.
Spitemaster

Благодарю. Я был введен в заблуждение названием метода.
Питер Тейлор

@PeterTaylor Спасибо за ваше представление! Я назвал make_throwметод рано, когда хотел, чтобы игроки могли пропустить свой ход. Я думаю, что более подходящее имя будет keep_throwing. Спасибо за отзыв в песочнице, это действительно помогло сделать это правильным испытанием!
maxb

3

Идти пораньше

class GoBigEarly(Bot):
    def make_throw(self, scores, last_round):
        yield True  # always do a 2nd roll
        while scores[self.index] + sum(self.current_throws) < 25:
            yield True
        yield False

Концепция: Постарайтесь выиграть крупно на раннем броске (до 25), затем выползайте оттуда по 2 броска за раз.


3

BinaryBot

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

class BinaryBot(Bot):

    def make_throw(self, scores, last_round):
        target = (self.end_score + scores[self.index]) / 2
        if last_round:
            target = max(scores)

        while scores[self.index] + sum(self.current_throws) < target:
            yield True

        yield False

Интересно, что Hesitateтакже отказывается пересекать черту первым. Вы должны окружить свою функцию classматериалом.
Кристиан Сиверс

3

PointsAreForNerdsBot

class PointsAreForNerdsBot(Bot):
    def make_throw(self, scores, last_round):
        while True:
            yield True

Это не нуждается в объяснении.

OneInFiveBot

class OneInFiveBot(Bot):
    def make_throw(self, scores, last_round):
        while random.randint(1,5) < 5:
            yield True
        yield False

Продолжает катиться, пока не выпадет пятерка на своем собственном 5-ти стороннем кубике. Пять меньше шести, так что ВЫ ДОЛЖНЫ ВЫИГРАТЬ!


2
Добро пожаловать в PPCG! Я уверен, что вы знаете, но ваш первый бот в буквальном смысле худший бот в этом соревновании! OneInFiveBotЭто отличная идея, но я думаю , что она страдает в конце игры по сравнению с некоторыми из более продвинутых ботов. Все еще отличная подача!
максимум

2
OneInFiveBotдовольно интересно в том , что он постоянно имеет самый высокий общий балл достиг.
AKroell

1
Спасибо, что дали StopBotбоксерскую грушу: P. OneInFiveBot на самом деле довольно аккуратный, хорошая работа!
Захари

@maxb Да, вот где я получил имя. Я, честно говоря, не тестировал, OneInFiveBotи у меня все
получается


3

LizduadacBot

Пытается выиграть в 1 шаг. Конечное состояние несколько произвольно.

Это также мой первый пост (и я новичок в Python), поэтому, если я побью "PointsAreForNerdsBot", я буду счастлив!

class LizduadacBot(Bot):

    def make_throw(self, scores, last_round):
        while scores[self.index] + sum(self.current_throws) < 50 or scores[self.index] + sum(self.current_throws) < max(scores):
            yield True
        yield False

Добро пожаловать в PPCG (и добро пожаловать в Python)! Вы бы с трудом проиграли PointsAreForNerdsBot, но ваш бот на самом деле неплохо держится. Я обновлю счет позже вечером или завтра, но ваш выигрыш составляет около 15%, что выше, чем в среднем на 12,5%.
максимум

Под «трудным временем» они подразумевают, что это невозможно (если я не очень неправильно понял)
Захари

@ maxb На самом деле я не думал, что выигрыш будет таким высоким! (Я не проверял это локально). Интересно, если изменение 50 на чуть выше / ниже увеличит выигрыш.
lizduadac

3

SlowStart

Этот бот реализует алгоритм медленного старта TCP. Он корректирует количество бросков ( ни ) в соответствии с предыдущим ходом: если он не бросил 6 в предыдущем ходу, увеличивает ни для этого хода; в то время как это уменьшает, или если это сделало.

class SlowStart(Bot):
    def __init__(self, *args):
        super().__init__(*args)
        self.completeLastRound = False
        self.nor = 1
        self.threshold = 8

    def updateValues(self):
        if self.completeLastRound:
            if self.nor < self.threshold:
                self.nor *= 2
            else:
                self.nor += 1
        else:
            self.threshold = self.nor // 2
            self.nor = 1


    def make_throw(self, scores, last_round):

        self.updateValues()
        self.completeLastRound = False

        i = 1
        while i < self.nor:
            yield True

        self.completeLastRound = True        
        yield False

Добро пожаловать в PPCG! Интересный подход, я не знаю, насколько он чувствителен к случайным колебаниям. Две вещи, которые необходимы для этого запуска: def updateValues():должны быть def updateValues(self):(или def update_values(self):если вы хотите следовать PEP8). Во-вторых, вызов updateValues()должен быть вместо self.updateValues()(или self.update_vales()).
максимум

2
Кроме того, я думаю, вам нужно обновить iпеременную в цикле while. Прямо сейчас ваш бот либо полностью проходит цикл while, либо застревает в цикле while, пока не достигнет 6.
maxb

В текущем рекорде я позволил себе внести эти изменения. Я думаю, вы могли бы поэкспериментировать с начальным значением для self.norи посмотреть, как оно влияет на производительность вашего бота.
максимум

3

KwisatzHaderach

import itertools
class KwisatzHaderach(Bot):
    """
    The Kwisatz Haderach foresees the time until the coming
    of Shai-Hulud, and yields True until it is immanent.
    """
    def __init__(self, *args):
        super().__init__(*args)
        self.roller = random.Random()
        self.roll = lambda: self.roller.randint(1, 6)
        self.ShaiHulud = 6

    def wormsign(self):
        self.roller.setstate(random.getstate())
        for i in itertools.count(0):
            if self.roll() == self.ShaiHulud:
                return i

    def make_throw(self, scores, last_round):
        target = max(scores) if last_round else self.end_score
        while True:
            for _ in range(self.wormsign()):
                yield True
            if sum(self.current_throws) > target + random.randint(1, 6):
                yield False                                               

Предсказание обычно побеждает - но не всегда можно избежать судьбы.
Велики и таинственны пути Шай-Хулуда!


Еще в первые дни этого испытания (то есть до того, как NeoBotбыл опубликован) я написал почти тривиальный Oracleбот:

    class Oracle(Bot):
        def make_throw(self, scores, last_round):
        randơm = random.Random()
        randơm.setstate(random.getstate())
        while True:
            yield randơm.randint(1, 6) != 6

но не опубликовал его, поскольку я не думал, что это было достаточно интересно;) Но как только я NeoBotвышел в лидеры, я начал думать о том, как превзойти его совершенную способность предсказывать будущее. Итак, вот цитата из Дюны; это когда Пол Атрейдес, Квадзац Хадерах, стоит на стыке, из которого может развернуться бесконечность различных вариантов будущего:

Он осознал, что предвидение - это иллюзия, которая включает в себя пределы того, что она обнаружила, - одновременно источник точности и осмысленной ошибки. Вмешалась некая неопределенность Гейзенберга: расход энергии, который раскрыл то, что он увидел, изменил то, что он увидел ... ... самое мельчайшее действие - моргание глаза, неосторожное слово, неуместная песчинка - переместило гигантский рычаг через известная вселенная. Он видел насилие с результатом, подверженным такому количеству переменных, что его малейшее движение создало огромные изменения в шаблонах.

Видение заставило его захотеть замёрзнуть в неподвижности, но это тоже было действием с его последствиями.

Итак, вот ответ: предвидеть будущее - значит менять его; и если вы очень осторожны, то с помощью избирательных действий или бездействия вы можете изменить это выгодным способом - по крайней мере, большую часть времени. Даже KwisatzHaderachне может получить 100% выигрыш!


Кажется, что этот бот изменяет состояние генератора случайных чисел, чтобы он избегал броска 6 или, по крайней мере, предвосхищал его. То же самое касается HarkonnenBot. Тем не менее, я отмечаю, что выигрыш этих ботов намного выше, чем у NeoBot. Вы активно манипулируете генератором случайных чисел, чтобы предотвратить его вращение 6?
максимум

О, при первом чтении я не заметил, что это не только лучше, NeoBotно и лучше! Мне также нравится, как вы приводите пример того, что все, что использует случайность (особенно контроллер), здесь должны делать: использовать свой собственный random.Randomэкземпляр. Мол NeoBot, это кажется немного чувствительным к изменениям неуказанных деталей реализации контроллера.
Кристиан Сиверс

@maxb: HarkonnenBotне касается RNG; его вообще не волнуют случайные числа. Он просто отравляет всех остальных ботов, а затем медленно доходит до финиша. Как и многие кулинарные деликатесы, месть - это блюдо, которое лучше всего наслаждаться медленно, после долгой и деликатной подготовки.
Дани О

@ChristianSievers: в отличие NeoBotHarkonnenBot), KwisatzHaderachопирается только на одну деталь реализации; в частности, ему не нужно знать, как реализован random.random (), только то, что его использует контроллер; D
Дани О

1
Я просмотрел всех ваших ботов. Я решил лечить KwisatzHaderachи так HarkonnenBotже, как NeoBot. Они получат свои результаты в симуляции с меньшим количеством игр, и не будут в официальной симуляции. Тем не менее, они окажутся в списке рекордов так же, как NeoBot. Основная причина, по которой они не участвуют в официальной симуляции, заключается в том, что они испортят другие стратегии ботов. Тем не мение. WisdomOfCrowdsдолжен хорошо подходить для участия, и мне любопытно, какие новые изменения вы внесли в это!
максимум

2
class ThrowThriceBot(Bot):

    def make_throw(self, scores, last_round):
        yield True
        yield True
        yield False 

Ну, это очевидно


Я провел несколько экспериментов с этим классом ботов (именно эту тактику я использовал, когда впервые играл в игру). Тогда у меня было 4 броска, хотя у 5-6 средний балл за раунд.
maxb

Кроме того, поздравляю с первым ответом KotH!
maxb

2
class LastRound(Bot):
    def make_throw(self, scores, last_round):
        while sum(self.current_throws) < 15 and not last_round and scores[self.index] + sum(self.current_throws) < 40:
            yield True
        while max(scores) > scores[self.index] + sum(self.current_throws):
            yield True
        yield False

LastRound действует так, как будто это всегда последний раунд и последний бот: он продолжает катиться до тех пор, пока не выйдет в лидеры. Он также не хочет соглашаться на менее чем 15 очков, если он не является последним раундом или не достигает 40 очков.


Интересный подход. Я думаю, что ваш бот страдает, если он начинает отставать. Поскольку шансы получить> 30 очков в одном раунде невелики, ваш бот, скорее всего, останется на текущем уровне.
maxb

1
Я подозреваю, что это страдает от той же самой ошибки, которую я сделал (см. Комментарии NotTooFarBehindBot) - как и в последнем раунде, если вы не выигрываете, вы будете продолжать бросать, пока не получите 6 (Score [self.index] никогда не обновляется) На самом деле - у вас это неравенство неправильно? max (баллы) всегда будут> = баллы [self.index]
Стюарт Мур

@StuartMoore Ха-ха, да, я думаю, ты прав. Спасибо!
Spitemaster

Я подозреваю, что вы хотите, чтобы "and last_round" 2-го числа делало то, что вы хотите - иначе будет использоваться 2-е время, независимо от того, истинно ли last_round
Стюарт Мур

3
Это намеренно. Он всегда пытается быть в лидерах, когда заканчивает свой ход.
Spitemaster

2

QuotaBot

Я реализовал наивную систему «квот», которая в целом казалась довольно высокой.

class QuotaBot(Bot):
    def __init__(self, *args):
        super().__init__(*args)
        self.quota = 20
        self.minquota = 15
        self.maxquota = 35

    def make_throw(self, scores, last_round):
        # Reduce quota if ahead, increase if behind
        mean = sum(scores) / len(scores)
        own_score = scores[self.index]

        if own_score < mean - 5:
            self.quota += 1.5
        if own_score > mean + 5:
            self.quota -= 1.5

        self.quota = max(min(self.quota, self.maxquota), self.minquota)

        if last_round:
            self.quota = max(scores) - own_score + 1

        while sum(self.current_throws) < self.quota:
            yield True

        yield False


if own_score mean + 5:выдает ошибку для меня. Такжеwhile sum(self.current_throws)
Spitemaster

@Spitemaster был ошибкой при вставке в стек, теперь должен работать.
FlipTack

@Spitemaster это потому, что были <и >символы, которые мешали <pre>тегам, которые я использовал
FlipTack

2

ExpectationsBot

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

class ExpectationsBot(Bot):

    def make_throw(self, scores, last_round):
        #Positive average gain is 2.5, is the chance of loss greater than that?
        costOf6 = sum(self.current_throws) if scores[self.index] + sum(self.current_throws) < 40  else scores[self.index] + sum(self.current_throws)
        while 2.5 > (costOf6 / 6.0):
            yield True
            costOf6 = sum(self.current_throws) if scores[self.index] + sum(self.current_throws) < 40  else scores[self.index] + sum(self.current_throws)
        yield False

У меня были проблемы с запуском контроллера, я получил «NameError: имя« bots_per_game »не определено» на многопоточном, так что на самом деле не знаю, как это работает.


1
Я думаю, что это в конечном итоге эквивалентно боту "Go to 16", но у нас пока нет ни одного из них
Стюарт Мур

1
@StuartMoore Это ... это очень верная точка зрения, да
Каин

Я столкнулся с вашими проблемами с контроллером, когда я запускал его на моей машине с Windows. Каким-то образом это работало нормально на моей машине с Linux. Я обновляю контроллер и обновлю сообщение, как только это будет сделано.
maxb

@maxb Спасибо, наверное, кое-что о том, какие переменные доступны в другом процессе. К вашему сведению также обновил это, я сделал глупую ошибку, уступив: /
Каин

2

BlessRNG

class BlessRNG(Bot):
    def make_throw(self, scores, last_round):
        if random.randint(1,2) == 1 :
            yield True
        yield False

BlessRNG FrankerZ GabeN BlessRNG


2

FortyTeen

class FortyTeen(Bot):
    def make_throw(self, scores, last_round):
        if last_round:
            max_projected_score = max([score+14 if score<self.end_score else score for score in scores])
            target = max_projected_score - scores[self.index]
        else:
            target = 14

        while sum(self.current_throws) < target:
            yield True
        yield False

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


Я получил TypeError: unsupported operand type(s) for -: 'list' and 'int'с твоим ботом.
TSH

Я предполагаю, что вы max_projected_scoreдолжны быть максимумом списка, а не всего списка, я прав? В противном случае я получаю ту же проблему, что и Tsh.
максимум

Ой, отредактировано, чтобы исправить.
гистократ

2

Колебаться

Делает два скромных шага, затем ждет, пока кто-нибудь еще пересечет черту. Обновленная версия больше не пытается побить рекорд, а только хочет достичь ее - улучшая производительность, удаляя два байта исходного кода!

class Hesitate(Bot):
    def make_throw(self, scores, last_round):
        myscore = scores[self.index]
        if last_round:
            target = max(scores)
        elif myscore==0:
            target = 17
        else:
            target = 35
        while myscore+sum(self.current_throws) < target:
            yield True
        yield False

2

Бунтарь

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

class Rebel(Bot):

    p = []

    def __init__(self,*args):
        super().__init__(*args)
        self.hide_from_harkonnen=self.make_throw
        if self.p:
            return
        l = [0]*5+[1]
        for i in range(300):
            l.append(sum(l[i:])/6)
        m=[i/6 for i in range(1,5)]
        self.p.extend((1-sum([a*b for a,b in zip(m,l[i:])])
                                          for i in range(300) ))

    def update_state(self,*args):
        super().update_state(*args)
        self.current_sum = sum(self.current_throws)
        # remember who we are:
        self.make_throw=self.hide_from_harkonnen

    def expect(self,mts,ops):
        p = 1
        for s in ops:
            p *= self.p[mts-s]
        return p

    def throw_again(self,mts,ops):
        ps = self.expect(mts,ops)
        pr = sum((self.expect(mts+d,ops) for d in range(1,6)))/6
        return pr>ps

    def make_throw(self, scores, last_round):
        myscore = scores[self.index]
        if len(self.current_throws)>1:
            # hello Tleilaxu!
            target = 666
        elif last_round:
            target = max(scores)
        elif myscore==0:
            target = 17
        else:
            target = 35
        while myscore+self.current_sum < target:
            yield True
        if myscore+self.current_sum < 40:
            yield False
        opscores = scores[self.index+1:] + scores[:self.index]
        for i in range(len(opscores)):
            if opscores[i]>=40:
                opscores = opscores[:i]
                break
        while True:
            yield self.throw_again(myscore+self.current_sum,opscores)

Ну, это довольно элегантно :) Кроме того, поздравляю с получением как первого, так и второго мест в главном конкурсе!
Дани О

Естественно, я настроил HarkonnenBotтак, что Rebelбольше не могу отменить самоочищение;), и я также настроил TleilaxuBotтак, чтобы он Rebelбольше не обнаруживал!
Дани О

1

Дай пять

class TakeFive(Bot):
    def make_throw(self, scores, last_round):
        # Throw until we hit a 5.
        while self.current_throws[-1] != 5:
            # Don't get greedy.
            if scores[self.index] + sum(self.current_throws) >= self.end_score:
                break
            yield True

        # Go for the win on the last round.
        if last_round:
            while scores[self.index] + sum(self.current_throws) <= max(scores):
                yield True

        yield False

В половине случаев мы бросим 5 до 6. Когда мы сделаем, обналичить.


Если вместо этого мы остановимся на 1, он будет продвигаться медленнее, но с большей вероятностью он достигнет 40 за один рубеж.
Мнемоник

В моих тестах TakeOne набирал 20,868 баллов за раунд по сравнению с 24,262 баллами TakeFive за раунд (а также приводил к увеличению выигрыша с 0,291 до 0,259). Так что я не думаю, что это того стоит.
Spitemaster

1

преследователь

class Chaser(Bot):
    def make_throw(self, scores, last_round):
        while max(scores) > (scores[self.index] + sum(self.current_throws)):
            yield True
        while last_round and (scores[self.index] + sum(self.current_throws)) < 44:
            yield True
        while self.not_thrown_firce() and sum(self.current_throws, scores[self.index]) < 44:
            yield True
        yield False

    def not_thrown_firce(self):
        return len(self.current_throws) < 4

Chaser пытается догнать первую позицию. Если это последний раунд, он отчаянно пытается набрать, по крайней мере, 50 очков. Просто для хорошей меры он бросает, по крайней мере, четыре раза, несмотря ни на что

[править 1: добавлена ​​стратегия получения золота в последнем раунде]

[править 2: обновленная логика, потому что я ошибочно думал, что бот получит 40 баллов, а не только самый высокий рейтинг ботов]

[править 3: в конце игры чейзер стал немного более оборонительным]


Добро пожаловать в PPCG! Аккуратная идея не только попытаться догнать, но и пройти первое место. Я сейчас запускаю симуляцию и желаю вам удачи!
максимум

Спасибо! Первоначально я пытался превзойти предыдущего лидера на фиксированную величину (пробовал значения в диапазоне от 6 до 20), но оказалось, что я просто организовал в два раза больше выставок.
AKroell

@JonathanFrech спасибо, исправлено
AKroell

1

FutureBot

class FutureBot(Bot):
    def make_throw(self, scores, last_round):
        while (random.randint(1,6) != 6) and (random.randint(1,6) != 6):
            current_score = scores[self.index] + sum(self.current_throws)
            if current_score > (self.end_score+5):
                break
            yield True
        yield False

OneStepAheadBot

class OneStepAheadBot(Bot):
    def make_throw(self, scores, last_round):
        while random.randint(1,6) != 6:
            current_score = scores[self.index] + sum(self.current_throws)
            if current_score > (self.end_score+5):
                break
            yield True
        yield False

Пара ботов, они приносят свои собственные наборы игральных костей и бросают их, чтобы предсказать будущее. Если один из них 6, они останавливаются, FutureBot не может вспомнить, какой из двух кубиков был для следующего броска, поэтому он сдается.

Интересно, что будет лучше.

OneStepAhead немного похож на OneInFive на мой вкус, но я также хочу увидеть, как он сравнивается с FutureBot и OneInFive.

Изменить: теперь они останавливаются после удара 45


Добро пожаловать в PPCG! Ваш бот определенно играет с духом игры! Я проведу симуляцию позже этим вечером.
максимум

Спасибо! Мне любопытно, насколько хорошо это будет сделано, но я предполагаю, что это будет на нижней стороне.
Уильям Портер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.