Гра в кубики, але уникайте числа 6 [закрито]


58

Турнір закінчився!

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

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

Кістки

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

Правила

Початок гри

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

То чому б ти коли-небудь переставав кидати штамп? Тому що якщо ви отримаєте 6, ваш бал за весь раунд стає нульовим, а матриця передається далі. Таким чином, початкова мета - максимально швидко збільшити свій рахунок.

Хто переможець?

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

Правила останнього раунду такі ж, як і для будь-якого іншого раунду. Ви вирішили продовжувати кидати або зупинятись. Однак ви знаєте, що у вас немає шансів на перемогу, якщо ви не отримаєте більш високий бал, ніж ті, що були перед вами в останньому турі. Але якщо ви продовжуєте йти занадто далеко, то ви можете отримати 6.

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

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

Додане правило полягає в тому, що гра триває максимум 200 раундів. Це запобігає випадкам, коли декілька ботів в основному продовжують кидати, поки не вдаряють 6, щоб залишитися на їх поточному рахунку. Після того, як пройде 199-й раунд, last_roundбуде встановлено істинно, і ще один раунд буде розіграний. Якщо гра проходить до 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) зроблені для того, щоб виконувати правила, і творці погодилися не брати участі в офіційному турнірі. Однак я все ще думаю, що їхні ідеї є творчими, і вони заслуговують на почесну згадку. 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
Caleb 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

Вітаємо із забезпеченням як першого, так і другого місця!
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! Це дійсно вражаюча відповідь. Коли я вперше запустив його, мене непокоїло те, що він використовував таку ж кількість часу виконання, як і всі інші боти разом. Потім я подивився відсоток виграшу. Дійсно розумний спосіб плінтуса правил. Я дозволю вашому боту брати участь у турнірі, але сподіваюся, що інші утримаються від використання тієї ж тактики, що і ця, оскільки це порушує дух гри.
maxb

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

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

2
Дякую! Я не думаю, що будь-який інший бот наблизиться до вашої оцінки. А для тих, хто думає про реалізацію цієї стратегії, не робіть цього. Відтепер ця стратегія суперечить правилам, і NeoBot є єдиним, кому дозволено використовувати її задля збереження турніру справедливим.
maxb

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

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

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

@maxb Не так, 20 все-таки буде найкращим без "кінцевої гри" в моєму тесті. Можливо, ви протестували його на старій версії контролера.
tsh

Перед тим, як розробити цей виклик, я пройшов окремий тест, де я обчислював середній бал за раунд за дві тактики в моєму дописі ("кидаю х разів" та "кидаю до х балів"), а максимум, який я знайшов, був за 15-16 . Хоча мій зразок міг бути занадто малим, я помітив нестабільність.
maxb

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

1
@maxb Якщо end_score4000, то отримати майже 4000 оборотів майже неможливо. І гра - це просто той, хто отримав найвищий бал за 200 оборотів. І зупинка на 15 повинна спрацювати, оскільки цього разу стратегія найбільшої оцінки за один хід така ж, як і найвища оцінка в 200 оборотів.
tsh

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

Чудове перше подання! Я запускаю його проти своїх ботів, які я написав для тестування, але я оновлю рейтинг, коли буде розміщено більше ботів.
maxb

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

@AKroell: Класно! Я мав намір зробити щось подібне, щоб переконатися, що воно кілька разів котиться наприкінці, але я ще не взяв часу на це. Дивно, однак, здається, що з тими значеннями я поступаюсь гірше, коли я роблю 100 пробіжок. Я протестував лише з 18 ботами. Можливо, я повинен зробити кілька тестів з усіма ботами.
Емінья

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 Виправлено, дякую.
Мнемонічний

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

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


1
Ласкаво просимо до PPCG! Я переглядаю ваше подання, і, здається, що чим більше гравців у грі, тим нижчий відсоток виграшу для вашого бота. Я не можу сказати, чому відразу. Якщо боти відповідають 1vs1, ви отримуєте 10% winrate. Ідея звучить багатообіцяюче, і код виглядає правильно, тому я не можу реально сказати, чому ваш winrate не вище.
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), але Девід видалив їх відповідь. Я заміню цього бота вашим.
maxb

1
Дуже смішно, бачачи розширену статистику цього бота: За винятком очокAreForNerds та StopBot, цей бот має найнижчі середні бали, і все ж він має приємне співвідношення
виграшів

5

Забезпечте голову

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. Він додає поняття, яке завжди враховує (коли в останньому чи середньому 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
Я знаю, хтось повинен був опублікувати цього бота. Виродження ботів для втрати.
Zacharý

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

2
ВИНАГАЛА ГРА ?! Це дивно.
Zacharý

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 разів, що наближається до середнього балу при киданні двох кісток.
maxb

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зайвий - кидає один раз, перш ніж дзвонити вашому боту. Ваш бот завжди кине мінімум двічі.
Шпітемастер

Дякую. Мене ввів в оману назва методу.
Пітер Тейлор

@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ідеальна ідея, але я думаю, вона страждає в кінцевій грі порівняно з деякими більш досконалими ботами. Ще чудове подання!
maxb

2
OneInFiveBotдосить цікаво в тому , що він постійно має найвищий загальний бал досяг.
AKroell

1
Дякуємо, що подарували StopBotмішок для пробивання: P. OneInFiveBot насправді - дуже акуратна, приємна робота!
Zacharý

@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, але ваш бот насправді проходить дуже добре. Я оновлю результат або пізніше сьогодні ввечері або завтра, але ваш winrate приблизно 15%, що вище, ніж в середньому 12,5%.
maxb

Під "важким часом" вони означають, що це неможливо (якщо я дуже не зрозумів)
Zacharý

@maxb Я насправді не думав, що виграш буде таким високим! (Я не перевіряв це локально). Цікаво, що якщо зміна 50 на трохи вище / нижче, збільшить виграш.
lizduadac

3

SlowStart

Цей бот реалізує алгоритм TCP Slow Start. Він регулює свою кількість рулонів ( ні ) відповідно до попередньої черги: якщо вона не скотила 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()).
maxb

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

В поточному рейтингу я взяв на себе сміливість впроваджувати ці зміни. Я думаю, ви могли б експериментувати з початковим значенням self.norі подивитися, як це впливає на продуктивність вашого бота.
maxb

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перейшовши на лідерство, я почав думати про те, як перемогти його ідеальну здатність передбачати майбутнє. Отже ось цитата «Дюна»; це тоді, коли Пол Атрейдс, Kwisatz Haderach, стоїть на зав’язку, від якої може розкручуватися нескінченність різних ф'ючерсів:

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

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

Отож тут була відповідь: передбачити майбутнє - це змінити його; і якщо ви дуже обережні, вибираючи дії чи бездіяльність, ви можете змінити це вигідним чином - принаймні більшу частину часу. Навіть KwisatzHaderachне можна отримати 100% виграш!


Здається, що цей бот змінює стан генератора випадкових чисел, щоб переконатися, що він уникає прокрутки 6 або принаймні передбачує його. Те саме стосується HarkonnenBot. Однак зауважу, що виграш у цих ботів значно вищий, ніж у NeoBot. Чи активно ви маніпулюєте генератором випадкових чисел, щоб не допустити прокрутки 6?
maxb

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

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

@ChristianSievers: на відміну від NeoBot (та HarkonnenBot), KwisatzHaderachпокладається лише на одну деталь реалізації; зокрема, не потрібно знати, як реалізовано random.random (), лише те, що використовує його контролер; D
Dani O

1
Я переглянув усі ваші боти. Я вирішив лікувати KwisatzHaderachі HarkonnenBotтак само, як і NeoBot. Вони отримають свої бали від симуляції з меншою кількістю ігор, і вони не будуть в офіційному моделюванні. Однак вони опиняться в списку рейтингів так само, як NeoBot. Основна причина їх відсутності в офіційному моделюванні полягає в тому, що вони зіпсують інші ботові стратегії. Однак. WisdomOfCrowdsмає бути добре підходить для участі, і мені цікаво про нові зміни, які ви внесли до цього!
maxb

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 (бали [self.index] ніколи не оновлюються) Насправді - чи маєте ви цю нерівність неправильно? макс (бали) завжди будуть> = бали [self.index]
Стюарт Мур

@StuartMoore Ха-ха, так, я думаю, ти маєш рацію. Дякую!
Шпітемастер

Я підозрюю, що ви хочете "і last_round" на 2-му, поки робити те, що ви хочете - інакше буде використовуватися 2-й час, чи правда last_round
Стюарт Мур

3
Це навмисно. Він завжди намагається бути лідируючим, коли закінчує свою чергу.
Шпітемастер

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

ОчікуванняБот

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

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
Я думаю, що це рівнозначно боту "Перейти до 16", але у нас ще немає такого
Стюарт Мур

1
@StuartMoore That ... - це дуже правдивий момент, так
Каїн

Я зіткнувся з вашими проблемами з контролером, коли запустив його на своїй машині Windows. Якось на моїй версії Linux це було чудово. Я оновлюю контролер і оновлюю публікацію, як тільки це буде зроблено.
maxb

@maxb Спасибі, напевно, щось про те, які змінні доступні в різних процесах. FYI також оновив це, я зробив дурну помилку щодо врожайності: /
Каїн

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

Сорока

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.
maxb

На жаль, відредаговано, щоб виправити.
гістократ

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 (а також приніс winrate з 0,291 до 0,259). Тож я не думаю, що цього варто.
Шпітемастер

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

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

[редагувати 1: до останнього раунду додано стратегію "золотого золота"]

[редагувати 2: оновлена ​​логіка, тому що я помилково думав, що бот набере 40 балів, а не лише найвищий показник ботів]

[редагувати 3: зробив погоню трохи захиснішою в кінці гри]


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

Дякую! Спочатку я намагався перевершити попереднього лідера на фіксовану суму (випробувані значення між 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 не може згадати, яка з 2-х кісток була для наступного рулону, тому вона здається.

Цікаво, що зробить краще.

OneStepAhead трохи схожий на OneInFive на мій смак, але я також хочу побачити, як він порівнюється з FutureBot та OneInFive.

Редагувати: Тепер вони зупиняються після удару 45


Ласкаво просимо до PPCG! Ваш бот, безумовно, грає з духом гри! Я запускаю симуляцію пізніше цього вечора.
maxb

Дякую! Мені цікаво, наскільки це буде добре, але я здогадуюсь, що це буде з низької сторони.
william porter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.