Um jogo de dados, mas evite o número 6 [fechado]


58

Torneio terminado!

O torneio acabou agora! A simulação final foi realizada durante a noite, totalizando jogos. O vencedor é Christian Sievers com seu bot OptFor2X . Christian Sievers também conseguiu garantir o segundo lugar com Rebel . Parabéns! Abaixo você pode ver a lista oficial de recordes do torneio.3108

Se você ainda deseja jogar, é bem-vindo ao usar o controlador postado abaixo e ao usar o código nele para criar seu próprio jogo.

Dados

Fui convidado a jogar um jogo de dados que nunca tinha ouvido falar. As regras eram simples, mas acho que seriam perfeitas para um desafio KotH.

As regras

O começo do jogo

O dado gira em torno da mesa e, toda vez que é a sua vez, você joga o dado quantas vezes quiser. No entanto, você deve jogá-lo pelo menos uma vez. Você acompanha a soma de todos os lançamentos da sua rodada. Se você optar por parar, a pontuação da rodada será adicionada à sua pontuação total.

Então, por que você pararia de jogar o dado? Porque se você obtiver 6, sua pontuação na rodada inteira será zero e o dado será transmitido. Assim, o objetivo inicial é aumentar sua pontuação o mais rápido possível.

Quem é o vencedor?

Quando o primeiro jogador em volta da mesa atinge 40 pontos ou mais, a última rodada começa. Uma vez iniciada a última rodada, todos, exceto a pessoa que iniciou a última rodada, recebem mais um turno.

As regras para a última rodada são as mesmas de qualquer outra rodada. Você escolhe continuar jogando ou parar. No entanto, você sabe que não tem chance de ganhar se não obtiver uma pontuação maior que a anterior na última rodada. Mas se você continuar indo longe demais, poderá receber um 6.

No entanto, há mais uma regra a ser levada em consideração. Se sua pontuação total atual (sua pontuação anterior + sua pontuação atual da rodada) for 40 ou mais e você acertar um 6, sua pontuação total será 0. Isso significa que você deve começar tudo de novo. Se você acertar um 6 quando sua pontuação total atual for 40 ou mais, o jogo continuará normal, exceto que você está no último lugar. A última rodada não é acionada quando sua pontuação total é redefinida. Você ainda pode vencer a rodada, mas ela se torna mais desafiadora.

O vencedor é o jogador com a maior pontuação assim que a última rodada terminar. Se dois ou mais jogadores compartilharem a mesma pontuação, todos serão contados como vencedores.

Uma regra adicional é que o jogo continue por no máximo 200 rodadas. Isso evita os casos em que vários bots basicamente continuam jogando até atingir 6 para permanecer na pontuação atual. Depois que a 199ª rodada é aprovada, last_roundé definido como verdadeiro e mais uma rodada é jogada. Se o jogo for para 200 rodadas, o bot (ou bots) com a maior pontuação será o vencedor, mesmo que eles não tenham 40 pontos ou mais.

Recapitular

  • A cada rodada, você continua jogando o dado até optar por parar ou obter um 6
  • Você deve jogar o dado uma vez (se o seu primeiro lançamento for um 6, sua rodada termina imediatamente)
  • Se você obtiver um 6, sua pontuação atual será definida como 0 (não a pontuação total)
  • Você adiciona sua pontuação atual à sua pontuação total após cada rodada
  • Quando um bot termina seu turno, resultando em uma pontuação total de pelo menos 40, todo mundo recebe o último turno
  • Se sua pontuação total atual é e você obtém um 6, sua pontuação total é definida como 0 e sua rodada termina.40
  • A última rodada não é acionada quando ocorrer o acima
  • A pessoa com a maior pontuação total após a última rodada é o vencedor
  • Caso haja vários vencedores, todos serão contados como vencedores
  • O jogo dura no máximo 200 rodadas

Esclarecimento das pontuações

  • Pontuação total: a pontuação que você salvou das rodadas anteriores
  • Pontuação atual: a pontuação da rodada atual
  • Pontuação total atual: a soma das duas pontuações acima

Como você participa

Para participar deste desafio do KotH, você deve escrever uma classe Python que herda Bot. Você deve implementar a função: make_throw(self, scores, last_round). Essa função será chamada assim que for a sua vez, e seu primeiro lançamento não for 6. Para continuar jogando, você deve yield True. Para parar de jogar, você deveria yield False. Após cada lançamento, a função pai update_stateé chamada. Assim, você tem acesso a sua lança para a rodada atual usando a variável self.current_throws. Você também tem acesso ao seu próprio índice usando self.index. Assim, para ver sua própria pontuação total, você usaria scores[self.index]. Você também pode acessar o end_scorepara o jogo usando self.end_score, mas pode assumir com segurança que serão 40 para esse desafio.

Você tem permissão para criar funções auxiliares dentro da sua classe. Você também pode substituir as funções existentes na Botclasse pai, por exemplo, se desejar adicionar mais propriedades de classe. Você não tem permissão para modificar o estado do jogo de qualquer forma, exceto cedendo Trueou False.

Você pode buscar inspiração nesta postagem e copiar qualquer um dos dois bots que incluí aqui. No entanto, receio que não sejam particularmente eficazes ...

Ao permitir outros idiomas

Tanto na sandbox quanto no The XIX XIX Byte, tivemos discussões sobre como permitir envios em outros idiomas. Depois de ler sobre essas implementações e ouvir argumentos de ambos os lados, decidi restringir esse desafio apenas ao Python. Isso se deve a dois fatores: o tempo necessário para suportar vários idiomas e a aleatoriedade desse desafio, exigindo um número alto de iterações para alcançar a estabilidade. Espero que você ainda participe e, se quiser aprender algum Python para esse desafio, tentarei estar disponível no bate-papo o mais rápido possível.

Para qualquer dúvida que possa ter, escreva na sala de bate-papo para este desafio . Vejo você lá!

Regras

  • A sabotagem é permitida e incentivada. Ou seja, sabotar contra outros jogadores
  • Qualquer tentativa de mexer com o controlador, tempo de execução ou outros envios será desqualificada. Todos os envios devem funcionar apenas com as entradas e armazenamento fornecidos.
  • Qualquer bot que use mais de 500 MB de memória para tomar sua decisão será desqualificado (se você precisar de tanta memória, deve repensar suas escolhas)
  • Um bot não deve implementar exatamente a mesma estratégia que uma existente, intencional ou acidentalmente.
  • Você tem permissão para atualizar seu bot durante o período do desafio. No entanto, você também pode postar outro bot se sua abordagem for diferente.

Exemplo

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

Esse bot continuará até ter uma pontuação de pelo menos 10 na rodada ou lançar um 6. Observe que você não precisa de lógica para lidar com o arremesso 6. Observe também que, se seu primeiro arremesso for 6, make_throwé nunca ligou, já que sua rodada termina imediatamente.

Para quem é novo no Python (e novo no yieldconceito), mas quer experimentar, a yieldpalavra-chave é semelhante a um retorno em alguns aspectos, mas diferente em outros aspectos. Você pode ler sobre o conceito aqui . Basicamente, uma vez que você yield, sua função será interrompida, e o valor yieldeditado será enviado de volta ao controlador. Lá, o controlador lida com sua lógica até a hora de seu bot tomar outra decisão. Em seguida, o controlador envia o lançamento dos dados e sua make_throwfunção continuará sendo executada exatamente onde parou antes, basicamente na linha após a yieldinstrução anterior .

Dessa forma, o controlador do jogo pode atualizar o estado sem exigir uma chamada de função bot separada para cada lançamento de dados.

Especificação

Você pode usar qualquer biblioteca Python disponível em pip. Para garantir que eu seja capaz de obter uma boa média, você tem um limite de tempo de 100 milissegundos por rodada. Eu ficaria muito feliz se o seu script fosse muito mais rápido que isso, para que eu possa rodar mais rodadas.

Avaliação

Para encontrar o vencedor, pegarei todos os bots e os executarei em grupos aleatórios de 8. Se houver menos de 8 classes enviadas, executarei em grupos aleatórios de 4 para evitar sempre ter todos os bots em cada rodada. Vou executar simulações por cerca de 8 horas, e o vencedor será o bot com a maior porcentagem de vitórias. Vou iniciar as simulações finais no início de 2019, dando todo o Natal para codificar seus bots! A data final preliminar é 4 de janeiro, mas, se houver muito pouco tempo, posso alterá-la para uma data posterior.

Até lá, tentarei fazer uma simulação diária usando 30 a 60 minutos de tempo da CPU e atualizando o placar. Essa não será a pontuação oficial, mas servirá como um guia para ver quais robôs têm o melhor desempenho. No entanto, com o Natal chegando, espero que você entenda que eu não estarei disponível o tempo todo. Farei o meu melhor para executar simulações e responder a quaisquer perguntas relacionadas ao desafio.

Teste você mesmo

Se você deseja executar suas próprias simulações, aqui está o código completo do controlador que está executando a simulação, incluindo dois bots de exemplo.

Controlador

Aqui está o controlador atualizado para este desafio. Ele suporta saídas ANSI, multi-threading e coleta estatísticas adicionais graças ao AKroell ! Quando eu fizer alterações no controlador, atualizarei a postagem assim que a documentação estiver concluída.

Graças ao BMO , o controlador agora pode baixar todos os bots desta postagem usando a -dbandeira. Outra funcionalidade é inalterada nesta versão. Isso deve garantir que todas as suas alterações mais recentes sejam simuladas o mais rápido possível!

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

Se você deseja acessar o controlador original para esse desafio, ele está disponível no histórico de edições. O novo controlador possui exatamente a mesma lógica para executar o jogo, a única diferença é desempenho, coleta de estatísticas e impressão mais bonita.

Bots

Na minha máquina, os bots são mantidos no arquivo forty_game_bots.py. Se você usar outro nome para o arquivo, atualize a importinstrução na parte superior do controlador.

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

Executando a simulação

Para executar uma simulação, salve os dois trechos de código publicados acima em dois arquivos separados. Eu os salvei como forty_game_controller.pye forty_game_bots.py. Então você simplesmente usa python forty_game_controller.pyou python3 forty_game_controller.pydepende da sua configuração do Python. Siga as instruções de lá se desejar configurar ainda mais sua simulação ou tente mexer no código, se desejar.

Estatísticas do jogo

Se você está criando um bot que visa uma determinada pontuação sem levar em consideração outros bots, estes são os percentis de pontuação vencedora:

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

Notas altas

À medida que mais respostas forem postadas, tentarei manter essa lista atualizada. O conteúdo da lista sempre será da última simulação. Os bots ThrowTwiceBote GoToTenBotsão os bots do código acima e são usados ​​como referência. Fiz uma simulação com 10 ^ 8 jogos, o que levou cerca de 1 hora. Então eu vi que o jogo alcançou estabilidade em comparação com minhas corridas com 10 ^ 7 jogos. No entanto, com as pessoas ainda postando bots, não farei mais simulações até que a frequência das respostas diminua.

Tento adicionar todos os novos bots e quaisquer alterações feitas nos bots existentes. Se parece que eu perdi o seu bot ou qualquer alteração nova que você tenha, escreva no chat e eu me certificarei de ter a sua versão mais recente na próxima simulação.

Agora temos mais estatísticas para cada bot, graças ao AKroell ! As três novas colunas contêm a pontuação máxima em todos os jogos, a pontuação média por jogo e a pontuação média ao ganhar para cada bot.

Como apontado nos comentários, houve um problema com a lógica do jogo que fez bots com um índice mais alto em um jogo obter uma rodada extra em alguns casos. Isso foi corrigido agora, e as pontuações abaixo refletem isso.

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|
+-----------------------+----+--------+--------+------+------+-------+------+--------+

Os seguintes bots (exceto Rebel) são feitos para violar as regras, e os criadores concordaram em não participar do torneio oficial. No entanto, ainda acho que suas idéias são criativas e merecem uma menção honrosa. Rebelde também está nesta lista porque usa uma estratégia inteligente para evitar sabotagem e, na verdade, tem um desempenho melhor com o bot de sabotagem em jogo.

Os bots NeoBote KwisatzHaderachfaz seguir as regras, mas usa uma brecha prevendo o gerador aleatório. Como esses robôs precisam de muitos recursos para simular, adicionei suas estatísticas a partir de uma simulação com menos jogos. O bot HarkonnenBotalcança a vitória desativando todos os outros bots, o que é estritamente contra as regras.

    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
Portanto, talvez as regras sejam um pouco mais claras se eles disserem "quando um jogador termina o turno com uma pontuação de pelo menos 40, todo mundo recebe o último turno". Isso evita o aparente conflito ao apontar que não está chegando aos 40 que realmente desencadeia a última rodada, está parando com pelo menos 40. #
aschepler

11
@aschepler que é uma boa formulação, eu vou editar o post quando eu estou no meu computador
maxb

2
@maxb Estendi o controlador para adicionar mais estatísticas relevantes ao meu processo de desenvolvimento: pontuação mais alta atingida, pontuação média alcançada e pontuação média vencedora gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell

2
Isso soa muito semelhante a um jogo de dados muito divertido chamado Farkled pt.wikipedia.org/wiki/Farkle
Caleb Jay Caleb Jay

5
Estou votando para encerrar esta pergunta porque ela já está fechada para novas respostas ("O torneio acabou! A simulação final foi realizada durante a noite, um total de 3
×

Respostas:


6

OptFor2X

Este bot segue uma aproximação à estratégia ideal para a versão para dois jogadores deste jogo, usando apenas sua pontuação e a pontuação do melhor oponente. Na última rodada, a versão atualizada considera todas as pontuações.

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

Examinarei a implementação o mais rápido possível. Com as celebrações de Natal, pode não ser até o dia 25
maxb 23/12/19

Seu bot está na liderança! Além disso, não há necessidade de fazê-lo funcionar mais rápido, é aproximadamente tão rápido quanto todos os outros bots na tomada de decisões.
Max26

Eu não queria torná-lo mais rápido. Eu já fiz o que queria fazer - inicialize apenas uma vez -, mas estava procurando uma maneira melhor de fazê-lo, especialmente sem definir funções fora da classe. Eu acho que está melhor agora.
Christian Sievers

Parece muito melhor agora, bom trabalho!
maxb

Parabéns por garantir o primeiro e o segundo lugar!
maxb 6/01

20

NeoBot

Em vez disso, tente apenas perceber a verdade - não há colher

O NeoBot espreita a matriz (também conhecida como aleatória) e prevê se o próximo lançamento será um 6 ou não - ele não pode fazer nada em relação a receber um 6 para começar, mas é mais do que feliz em evitar um ender.

O NeoBot, na verdade, não modifica o controlador ou o tempo de execução, apenas solicita educadamente à biblioteca mais informações.

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

11
Bem-vindo ao PPCG! Esta é uma resposta realmente impressionante. Quando o executei pela primeira vez, fiquei incomodado com o fato de ele usar a mesma quantidade de tempo de execução que todos os outros bots combinados. Então eu olhei para a porcentagem de vitórias. Maneira realmente inteligente de contornar as regras. Permitirei que seu bot participe do torneio, mas espero que outros se abstenham de usar a mesma tática que essa, pois isso viola o espírito do jogo.
Max24 /

2
Como existe uma lacuna tão grande entre esse bot e o segundo lugar, combinado com o fato de seu bot exigir muita computação, você aceitaria que eu execute uma simulação com menos iterações para encontrar sua taxa de vitórias e, em seguida, execute o funcionário simulação sem o seu bot?
Max24 /

3
Por mim tudo bem, achei que isso era provavelmente desqualificável e definitivamente não exatamente no espírito do jogo. Dito isto, foi uma explosão começar a trabalhar e uma desculpa divertida para mexer no código-fonte python.
Principalmente inofensivo

2
obrigado! Eu não acho que nenhum outro bot se aproxime da sua pontuação. E para quem pensa em implementar essa estratégia, não. A partir de agora, essa estratégia será contrária às regras, e o NeoBot é o único autorizado a usá-la para manter o torneio justo.
Max24 /

11
Bem, o myBot vence todos, mas isso é muito melhor - embora eu publicasse bot assim, obteria -100 e não a melhor pontuação.
Jan Ivan

15

Enxame Cooperativo

Estratégia

Acho que ninguém mais notou o significado dessa regra:

Se o jogo for para 200 rodadas, o bot (ou bots) com a maior pontuação será o vencedor, mesmo que eles não tenham 40 pontos ou mais.

Se todos os bot rolassem sempre até serem eliminados, todos teriam pontuação zero no final do round 200 e todos venceria! Assim, a estratégia do Enxame Cooperativo é cooperar desde que todos os jogadores tenham pontuação zero, mas jogar normalmente se alguém marcar algum ponto.

Neste post, estou enviando dois bots: o primeiro é CooperativeSwarmBot e o segundo é CooperativeThrowTwice. O CooperativeSwarmBot serve como uma classe base para todos os bots que fazem parte formalmente do enxame cooperativo e tem um comportamento reservado simplesmente de aceitar sua primeira jogada bem-sucedida quando a cooperação falha. CooperativeSwarmBot tem CooperativeSwarmBot como pai e é idêntico a ele em todos os aspectos, exceto que seu comportamento não cooperativo é fazer duas rolagens em vez de uma. Nos próximos dias, revisarei este post para adicionar novos bots que usam um comportamento muito mais inteligente jogando contra bots não cooperativos.

Código

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

Análise

Viabilidade

É muito difícil cooperar neste jogo, porque precisamos do apoio de todos os oito jogadores para que ele funcione. Como cada classe de bot é limitada a uma instância por jogo, esse é um objetivo difícil de alcançar. Por exemplo, as chances de escolher oito robôs cooperativos de um conjunto de 100 robôs cooperativos e 30 robôs não cooperativos são:

100130991299812897127961269512594124931230.115

icn

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

i=8n=38

Estudo de caso

Por várias razões (veja as notas de rodapé 1 e 2), um enxame cooperativo adequado nunca competirá nos jogos oficiais. Como tal, resumirei os resultados de uma de minhas próprias simulações nesta seção.

Essa simulação executou 10000 jogos usando os outros 38 bots postados aqui na última vez que verifiquei e 2900 bots que tinham o CooperativeSwarmBot como sua classe pai. O controlador informou que 9051 dos 10000 jogos (90,51%) terminaram em 200 rodadas, o que é bastante próximo da previsão de que 90% dos jogos seriam cooperativos. A implementação desses bots foi trivial; exceto CooperativeSwarmBot, todos eles assumiram este formato:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Menos de 3% dos bots tiveram uma porcentagem de vitória abaixo de 80% e pouco mais de 11% dos bots venceram todos os jogos que disputaram. A porcentagem média de vitórias dos 2900 bots no enxame é de cerca de 86%, o que é escandalosamente bom. Para comparação, os melhores jogadores da tabela oficial de líderes atual vencem menos de 22% de seus jogos. Não consigo encaixar a lista completa do enxame cooperativo dentro do comprimento máximo permitido para uma resposta; portanto, se você quiser ver que precisará ir aqui: https://pastebin.com/3Zc8m1Ex

Como cada bot jogou em média cerca de 27 jogos, a sorte joga um rolo relativamente grande quando você olha para os resultados de bots individuais. Como ainda não implementei uma estratégia avançada para jogos não cooperativos, a maioria dos outros bots se beneficiou drasticamente ao jogar contra o enxame cooperativo, realizando até a taxa média de vitórias média de 86% do enxame cooperativo.

Os resultados completos para bots que não estão no enxame estão listados abaixo; existem dois robôs cujos resultados eu acho que merecem atenção especial. Primeiro, o StopBot não conseguiu vencer nenhum jogo. Isso é particularmente trágico porque o enxame cooperativo estava realmente usando exatamente a mesma estratégia que o StopBot; você esperaria que o StopBot vencesse oito de seus jogos por acaso, e um pouco mais porque o enxame cooperativo é forçado a dar aos seus oponentes a primeira jogada. O segundo resultado interessante, no entanto, é que o trabalho duro de PointsAreForNerdsBot finalmente valeu a pena: ele cooperou com o enxame e conseguiu vencer todos os jogos que jogou!

+---------------------+----+--------+--------+------+------+-------+------+--------+
|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|
+---------------------+----+--------+--------+------+------+-------+------+--------+

Falhas

Existem algumas desvantagens nessa abordagem cooperativa. Primeiro, quando jogam contra bots não cooperativos, os bots cooperativos nunca obtêm a vantagem do primeiro turno porque, quando jogam primeiro, ainda não sabem se seus oponentes estão ou não dispostos a cooperar e, portanto, não têm escolha a não ser obter pontuação zero. Da mesma forma, essa estratégia cooperativa é extremamente vulnerável à exploração por bots maliciosos; por exemplo, durante uma jogada cooperativa, o bot que joga por último na última rodada pode optar por parar de rolar imediatamente para fazer com que todo mundo perca (supondo, é claro, que o primeiro lançamento não tenha sido seis).

Ao cooperar, todos os bots podem alcançar a solução ideal de uma taxa de vitória de 100%. Como tal, se a taxa de vitória fosse a única coisa que importava, a cooperação seria um equilíbrio estável e não haveria nada com que se preocupar. No entanto, alguns bots podem priorizar outros objetivos, como chegar ao topo da classificação. Isso significa que há um risco de que outro bot possa desertar após o seu último turno, o que cria um incentivo para você desertar primeiro. Como a organização desta competição não nos permite ver o que nossos adversários fizeram em seus jogos anteriores, não podemos penalizar as pessoas que desertaram. Assim, a cooperação é, em última análise, um equilíbrio instável, fadado ao fracasso.

Notas de rodapé

[1]: As principais razões pelas quais eu não quero enviar milhares de bots em vez de apenas dois são que isso atrasaria a simulação por um fator da ordem de 1000 [2], e que isso afetaria significativamente ganhar porcentagens, já que outros bots jogariam quase exclusivamente contra o enxame e não um contra o outro. Mais importante, no entanto, é o fato de que, mesmo que eu quisesse, não seria capaz de fazer muitos bots em um período de tempo razoável sem quebrar o espírito da regra de que "um bot não deve implementar exatamente a mesma estratégia que um existente, intencional ou acidentalmente ".

[2]: Eu acho que há duas razões principais pelas quais a simulação fica mais lenta ao executar um enxame cooperativo. Primeiro, mais bots significam mais jogos se você quiser que cada bot jogue no mesmo número de jogos (no estudo de caso, o número de jogos diferirá por um fator de cerca de 77). Segundo, os jogos cooperativos demoram mais porque duram 200 rodadas completas e, dentro de uma rodada, os jogadores precisam continuar rolando indefinidamente. Para minha configuração, os jogos demoraram cerca de 40 vezes mais para simular: o estudo de caso demorou um pouco mais de três minutos para executar 10000 jogos, mas após remover o enxame cooperativo, ele terminava 10000 jogos em apenas 4,5 segundos. Entre essas duas razões, eu estimo que demoraria cerca de 3100 vezes mais para medir com precisão o desempenho dos bots quando houver um enxame competindo em comparação com quando não houver.


4
Uau. E bem-vindo ao PPCG. Essa é a primeira resposta. Eu realmente não estava planejando uma situação como essa. Você certamente encontrou uma brecha nas regras. Não tenho muita certeza de como devo pontuar isso, já que sua resposta é uma coleção de bots, e não um único bot. No entanto, a única coisa que direi agora é que parece injusto que um participante controle 98,7% de todos os bots.
Max

2
Na verdade, não quero que bots duplicados participem da competição oficial; é por isso que eu mesmo executei a simulação em vez de enviar milhares de bots quase idênticos. Vou revisar minha submissão para deixar isso mais claro.
Einhaender

11
Se eu tivesse antecipado uma resposta como essa, teria mudado os jogos que vão para 200 rodadas para que eles não dêem pontuação aos jogadores. No entanto, como você observa, existe uma regra sobre a criação de bots idênticos que tornariam essa estratégia contrária às regras. Não vou mudar as regras, pois seria injusto com todos que fizeram um bot. No entanto, o conceito de cooperação é muito interessante, e espero que haja outros bots enviados que implementem a estratégia de cooperação em combinação com sua própria estratégia única.
Max

11
Acho que sua postagem é clara após a leitura mais aprofundada.
Max

Quantos bots existentes precisariam agrupar seu código nessa estrutura de cooperação para que a maioria deles visse um ganho líquido em sua colocação na tabela de classificação? Meu palpite ingênuo é de 50%.
Sparr 04/01

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

Apenas tente com todos GoToNBot, e 20, 22, 24 joga melhor. Não sei porque.


Atualização: sempre pare o arremesso se conseguir pontuação 40 ou mais.


Eu também experimentei esses tipos de bots. A pontuação média mais alta por rodada é encontrada quando o bot passa para 16, mas estou assumindo que o "final do jogo" faz com que o 20-bot ganhe com mais frequência.
Max19 /

@maxb Não é assim, 20 ainda será o melhor sem o "jogo final" no meu teste. Talvez você tenha testado na versão antiga do controlador.
tsh

Fiz um teste separado antes de projetar esse desafio, onde calculei a pontuação média por rodada para as duas táticas no meu post ("jogue x vezes" e "jogue até x pontuação"), e o máximo que encontrei foi para 15-16 . Embora meu tamanho de amostra pudesse ter sido muito pequeno, notei instabilidade.
Max

2
Eu fiz alguns testes com isso, e minha conclusão é simplesmente que 20 funciona bem porque é 40/2. Embora eu não tenha certeza absoluta. Quando configurei end_scorepara 4000 (e mudei seu bot para usar isso no targetcálculo), os 15-16 bots eram muito melhores. Mas se o jogo fosse apenas para aumentar sua pontuação, seria trivial.
maxb

11
@maxb Se end_scorefor 4000, é quase impossível obter 4000 antes das 200 voltas. E o jogo é simplesmente quem obteve a maior pontuação em 200 turnos. E parar aos 15 deve funcionar, pois dessa vez a estratégia para a pontuação mais alta em um turno é igual à pontuação mais alta em 200 turnos.
tsh

10

Rolo adaptável

Começa mais agressivo e se acalma no final da rodada.
Se acreditar que está ganhando, dedique mais tempo à segurança.

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

Ótima primeira submissão! Vou executá-lo nos meus bots que escrevi para teste, mas atualizarei o recorde quando mais bots forem postados.
maxb

Fiz alguns testes com pequenas modificações no seu bot. lim = max(min(self.end_score - scores[self.index], 24), 6)elevar o máximo para 24 e adicionar um mínimo de 6 aumenta a porcentagem de vitórias por conta própria e ainda mais.
AKroell

@AKroell: Legal! Eu pretendia fazer algo semelhante para garantir que ele rolasse algumas vezes no final, mas ainda não tomei tempo para fazê-lo. Estranhamente, porém, parece ter um desempenho pior com esses valores quando eu executo 100k. Só testei com 18 bots. Talvez eu deva fazer alguns testes com todos os bots.
Emigna

5

Alfa

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

Alpha se recusa a ficar em segundo lugar com qualquer um. Enquanto houver um bot com uma pontuação mais alta, ele continuará rolando.


Por causa de como yieldfunciona, se começar a rolar, nunca irá parar. Você deseja atualizar my_scoreno loop.
Spitemaster

@ Spitemaster Fixed, obrigado.
Mnemônico

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

A idéia é que outros bots possam perder pontos, portanto, ficar em 2º não é ruim - mas se você estiver muito atrasado, pode muito bem ir à falência.


11
Bem-vindo ao PPCG! Estou analisando sua inscrição e parece que quanto mais jogadores estiverem no jogo, menor será a porcentagem de vitórias do seu bot. Não sei dizer o porquê imediatamente. Com os bots sendo comparados a 1vs1, você recebe uma taxa de ganho de 10%. A ideia parece promissora, e o código parece correto, então não sei dizer por que o seu winrate não é maior.
Max

6
Eu olhei para o comportamento, e esta linha tinha me confundido: 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. Mesmo que seu bot esteja na liderança após 7 jogadas, ele continua até atingir um 6. Enquanto eu digito isso, descobri o problema! Os scoresúnicos contêm as pontuações totais, não os casos dos dados da rodada atual. Você deve modificá-lo para ser current_score = scores[self.index] + sum(self.current_throws).
Max19

Obrigado - fará essa alteração!
Stuart Moore

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

Queremos ir grande ou ir para casa, certo? O GoHomeBot geralmente vai para casa. (Mas surpreendentemente bem!)


Como esse bot sempre vale 40 pontos, nunca terá pontos na scoreslista. Havia um bot como esse antes (o bot do GoToEnd), mas david excluiu a resposta. Vou substituir esse bot pelo seu.
maxb

11
É muito engraçado, vendo isso bots' estatísticas expaned: Exceto para pointsAreForNerds e StopBot, este bot tem a menor média de pontos, e ainda tem uma relação bela vitória
Belhenix

5

AssegurarLead

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

Certifique-se de emprestar idéias do GoTo20Bot. Ele adiciona o conceito que sempre considera (quando está no last_round ou chega a 40) que existem outros que terão pelo menos mais um teste. Assim, o bot tenta ficar um pouco à frente deles, de modo que eles precisam alcançá-los.


4

Roll6TimesV2

Não supera o melhor atual, mas acho que será melhor com mais bots em jogo.

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

Jogo realmente incrível pelo caminho.


Bem-vindo ao PPCG! Muito impressionante, não apenas para o seu primeiro desafio KotH, mas para a sua primeira resposta. Que bom que você gostou do jogo! Tive muita discussão sobre a melhor tática para o jogo depois da noite em que joguei, então parecia perfeito para um desafio. Você está atualmente em terceiro lugar entre os 18.
maxb 19/12/19

4

StopBot

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

Literalmente, apenas um lance.

Isso é equivalente à Botclasse base .


11
Não se desculpe! Você está seguindo todas as regras, embora eu tenha medo que seu bot não seja muito eficaz, com uma média de 2,5 pontos por rodada.
Max 19/12/19

11
Eu sei, alguém teve que postar esse bot. Bots degenerados pela perda.
Zachary

5
Eu diria que estou impressionado com o seu bot, garantindo exatamente uma vitória na última simulação, provando que não é completamente inútil.
maxb

2
GANHOU UM JOGO ?! Isso é surpreendente.
Zachary

3

BringMyOwn_dice (BMO_d)

Esse bot adora dados, traz 2 (parece ter o melhor) dados por conta própria. Antes de jogar dados em uma rodada, ele lança seus próprios 2 dados e calcula sua soma, este é o número de lançamentos que ele vai realizar, só joga se ainda não tiver 40 pontos.

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
Eu estava pensando em um bot aleatório usando um coin flip, mas isso está mais em espírito com o desafio! Eu acho que dois dados têm o melhor desempenho, já que você obtém o maior número de pontos por rodada quando joga o dado 5-6 vezes, perto da pontuação média ao jogar dois dados.
Max

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é desnecessário - ele lança uma vez antes de ligar para o seu bot. Seu bot sempre jogará no mínimo duas vezes.
Spitemaster

Obrigado. Eu fui enganado pelo nome do método.
Peter Taylor

@PeterTaylor Obrigado pela sua submissão! Eu nomeei o make_throwmétodo desde o início, quando queria que os jogadores pudessem pular sua vez. Eu acho que um nome mais apropriado seria keep_throwing. Obrigado pelo feedback na caixa de areia, isso realmente ajudou a tornar este um desafio adequado!
maxb

3

Go Big Early

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

Conceito: Tente ganhar muito em um rolo inicial (chegando a 25) e suba a partir daí 2 rolos de cada vez.


3

BinaryBot

Tenta chegar perto da pontuação final, para que assim que outra pessoa inicie a última rodada, ela possa superar a pontuação da vitória. A meta está sempre a meio caminho entre a pontuação atual e a pontuação final.

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

Interessante, Hesitatetambém se recusa a cruzar a linha primeiro. Você precisa cercar sua função com essas classcoisas.
Christian Sievers

3

PointsAreForNerdsBot

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

Este não precisa de explicação.

OneInFiveBot

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

Continua rolando até atingir cinco no seu próprio dado de 5 lados. Cinco é menor que seis, por isso TEM QUE GANHAR!


2
Bem-vindo ao PPCG! Tenho certeza que você está ciente, mas seu primeiro bot é literalmente o pior bot nesta competição! Essa OneInFiveBoté uma ideia interessante, mas acho que ela sofre no final do jogo, em comparação com alguns dos bots mais avançados. Ainda é uma ótima finalização!
maxb

2
isso OneInFiveBoté bastante interessante na maneira como ele tem consistentemente a maior pontuação geral alcançada.
AKroell

11
Obrigado por dar StopBotum saco de pancadas: P. O OneInFiveBot, na verdade, é um trabalho muito legal e agradável!
Zachary

@ Maxb Sim, é aí que eu tenho o nome. Eu honestamente não testei OneInFiveBote está indo muito melhor do que eu esperava #
The_Bob


3

LizduadacBot

Tenta ganhar em 1 passo. A condição final é um tanto arbitrária.

Este também é meu primeiro post (e eu sou novo no Python), por isso, se eu vencer o "PointsAreForNerdsBot", ficaria feliz!

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

Bem-vindo ao PPCG (e bem-vindo ao Python)! Você teria dificuldade em perder PointsAreForNerdsBot, mas seu bot realmente se sai muito bem. Atualizarei a pontuação mais tarde esta noite ou amanhã, mas sua taxa de vitórias é de cerca de 15%, que é superior à média de 12,5%.
Max

Por "momento difícil", que significa que é impossível (a menos que eu não entendi muito)
Zachary

@maxb Na verdade, não achei que a taxa de vitória seria tão alta! (Não testei localmente). Gostaria de saber se mudar os 50 para um pouco mais alto / baixo aumentaria a taxa de vitórias.
Lizduadac

3

SlowStart

Este bot implementa o algoritmo TCP Slow Start. Ele ajusta seu número de jogadas ( nor ) de acordo com o turno anterior: se não rolou um 6 no turno anterior, aumenta o nor para esse turno; enquanto reduz nem se o fez.

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

Bem-vindo ao PPCG! Abordagem interessante, não sei como é sensível a flutuações aleatórias. Duas coisas que são necessárias para executar essa execução: def updateValues():devem ser def updateValues(self):(ou def update_values(self):se você deseja seguir o PEP8). Em segundo lugar, a chamada updateValues()deve ser self.updateValues()(ou self.update_vales()).
maxb

2
Além disso, acho que você precisa atualizar sua ivariável no loop while. No momento, seu bot passa o loop while inteiramente ou fica preso no loop while até atingir 6.
maxb

No recorde atual, tomei a liberdade de implementar essas mudanças. Eu acho que você pode experimentar o valor inicial self.nore ver como isso afeta o desempenho do seu bot.
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                                               

A presciência geralmente vence - mas o destino nem sempre pode ser evitado.
Grandes e misteriosos são os caminhos de Shai-Hulud!


Nos primeiros dias deste desafio (ou seja, antes de NeoBotser publicado), escrevi um Oraclebot quase trivial :

    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

mas não o publiquei porque não achei interessante o suficiente;) Mas uma vez NeoBotassumi a liderança, comecei a pensar em como superar sua capacidade perfeita de prever o futuro. Então, aqui está uma citação de Dune; é quando Paul Atreides, o Kwisatz Haderach, fica em um nexo do qual uma infinidade de diferentes futuros pode se desenrolar:

A presciência, ele percebeu, era uma iluminação que incorporava os limites do que revelava - ao mesmo tempo uma fonte de precisão e erro significativo. Uma espécie de indeterminação de Heisenberg interveio: o gasto de energia que revelou o que viu, mudou o que viu ... ... a ação mais minuciosa - um piscar de olhos, uma palavra descuidada, um grão de areia fora de lugar - moveu uma gigantesca alavanca pelo universo conhecido. Ele viu a violência com o resultado sujeita a tantas variáveis ​​que seu menor movimento criou grandes mudanças nos padrões.

A visão o fez querer congelar na imobilidade, mas isso também era ação com suas consequências.

Então aqui estava a resposta: prever o futuro é mudá-lo; e se você for muito cuidadoso, por ação ou inação seletiva, poderá alterá-lo de uma maneira vantajosa - pelo menos na maioria das vezes. Mesmo os KwisatzHaderachque não conseguem obter uma taxa de vitória de 100%!


Parece que esse bot altera o estado do gerador de números aleatórios, para garantir que ele evite rolar 6, ou pelo menos o antecipe. O mesmo vale para o HarkonnenBot. No entanto, observo que a taxa de vitória desses bots é muito maior que a do NeoBot. Você está manipulando ativamente o gerador de números aleatórios para impedir que ele role 6?
maxb 2/01

Oh, na minha primeira leitura, eu não percebi que isso não é apenas melhor do que NeoBotmas também melhor! Também gosto de como você dá um exemplo do que tudo que usa aleatoriedade (especialmente o controlador) aqui deve fazer: use sua própria random.Randominstância. Assim NeoBot, isso parece um pouco sensível a alterações de detalhes de implementação não especificados do controlador.
Christian Sievers

@ maxb: HarkonnenBotnão toca no RNG; não se importa com números aleatórios. Envenena todos os outros bots e depois caminha até a linha de chegada o mais lentamente possível. Como muitas iguarias culinárias, a vingança é um prato melhor saboreado lentamente, após uma preparação longa e delicada.
Dani O

@ChristianSievers: diferente de NeoBot (e HarkonnenBot), KwisatzHaderachconta apenas com um detalhe da implementação; em particular, ele não precisa saber como o random.random () é implementado, apenas que o controlador o usa; D
Dani O

11
Eu olhei através de todos os seus bots. Eu decidi tratar KwisatzHaderache HarkonnenBotda mesma maneira que NeoBot. Eles receberão suas pontuações de uma simulação com menos jogos e não estarão na simulação oficial. No entanto, eles vão acabar na lista de recordes muito parecido NeoBot. A principal razão para eles não estarem na simulação oficial é que eles vão estragar outras estratégias de bot. Contudo. WisdomOfCrowdsdeve ser bem adequado para participação, e estou curioso sobre as novas mudanças que você fez para isso!
maxb 3/01

2
class ThrowThriceBot(Bot):

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

Bem, esse é óbvio


Eu fiz alguns experimentos com essa classe de bots (foi a tática que usei quando joguei o jogo pela primeira vez). Eu fui com 4 jogadas então, apesar de 5-6 ter uma pontuação média mais alta por rodada.
Max19

Além disso, parabéns pela sua primeira resposta KotH!
Max

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 age como se fosse sempre a última rodada e o último bot: ele continua rolando até que esteja na liderança. Ele também não quer se contentar com menos de 15 pontos, a menos que seja a última rodada ou atinja 40 pontos.


Abordagem interessante. Eu acho que seu bot sofre se começar a ficar para trás. Como as chances de obter> 30 pontos em uma única rodada são baixas, é mais provável que seu bot permaneça na pontuação atual.
Max

11
Eu suspeito que isso sofra do mesmo erro que cometi (veja os comentários do NotTooFarBehindBot) - como na última rodada, se você não estiver ganhando, continuará jogando até obter um 6 (a pontuação [self.index] nunca é atualizada) Na verdade - você tem essa desigualdade da maneira errada? máximo (pontuações) sempre será> = pontuações [self.index]
Stuart Moore

@StuartMoore Haha, sim, acho que você está certo. Obrigado!
Spitemaster

Eu suspeito que você quer "e last_round" no 2º tempo para fazer o que quiser - caso contrário, o segundo tempo vai ser usado ou não last_round é verdade
Stuart Moore

3
Isso é intencional. Ele sempre tenta estar na liderança ao finalizar seu turno.
Spitemaster

2

QuotaBot

Eu implementei um sistema ingênuo de "cota", que na verdade parecia ter uma pontuação bastante alta em geral.

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:dá um erro para mim. Tambémwhile sum(self.current_throws)
Spitemaster

O @Spitemaster foi um erro ao colar na troca de pilhas, deve funcionar agora.
FlipTack

@Spitemaster é porque houve <e >símbolos que interferiam com as <pre>marcas que eu estava usando
FlipTack

2

ExpectationsBot

Apenas joga direto, calcula o valor esperado para o lançamento de dados e só o faz se for positivo.

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

Eu estava tendo problemas para executar o controlador, obtive um "NameError: name 'bots_per_game' não está definido" no multithreaded, então realmente não faço ideia de como isso funciona.


11
Eu acho que isso acaba sendo equivalente a um bot de "Ir para 16", mas ainda não temos um desses #
Stuart Moore

11
@StuartMoore Isso ... é um ponto muito verdadeiro, sim
Cain

Encontrei seus problemas com o controlador quando o executei na minha máquina Windows. De alguma forma, ele correu bem na minha máquina Linux. Estou atualizando o controlador e atualizarei a postagem assim que terminar.
maxb

@ maxb Obrigado, provavelmente algo sobre quais variáveis ​​estão disponíveis no processo diferente. FYI também atualizou isso, eu cometi um erro bobo em torno de render: /
Caim

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

Quarenta

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

Tente 14 pontos até a última rodada, então assuma que todos os outros tentarão 14 pontos e tente empatar essa pontuação.


Eu fiquei TypeError: unsupported operand type(s) for -: 'list' and 'int'com seu bot.
tsh

Estou assumindo que você max_projected_scoredeve ser o máximo da lista e não a lista inteira, estou correto? Caso contrário, eu recebo o mesmo problema que o tsh.
maxb

Ops, editado para corrigir.
histocrat

2

Hesitar

Dá dois passos modestos e espera que outra pessoa cruze a linha. A versão atualizada não tenta mais superar o recorde, apenas quer alcançá-lo - melhorando o desempenho excluindo dois bytes do código-fonte!

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

Rebelde

Este bot combina a estratégia simples da Hesitate avançada estratégia da última rodada BotFor2X, tenta lembrar quem é e fica louco quando descobre que vive em uma ilusão.

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)

Bem, isso é bastante elegante :) Além disso, parabéns por conseguir o primeiro e o segundo lugar na competição principal!
Dani O

Naturalmente, aprimorei HarkonnenBotpara que Rebelnão possa mais se desfeitar;) e também aprimorei TleilaxuBotpara que Rebelnão a detecte mais!
Dani O

1

Pegue cinco

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

Metade do tempo, rolamos um 5 antes de um 6. Quando o fizermos, sacaremos.


Se pararmos em 1, o progresso será mais lento, mas é mais provável que chegue a 40 em um único limite.
Mnemonic

Nos meus testes, o TakeOne obteve 20.868 pontos por rodada, comparado aos 24.262 pontos do TakeFive por rodada (e também aumentou a taxa de win de 0,291 para 0,259). Então, eu não acho que vale a pena.
Spitemaster

1

Caçador

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

O artilheiro tenta alcançar a primeira posição. Se for a última rodada, ele tenta desesperadamente alcançar pelo menos 50 pontos. Apenas por uma boa medida, ele lança pelo menos quatro vezes, não importa o que

[edit 1: adicionada estratégia go-for-gold na última rodada]

[editar 2: lógica atualizada porque pensei erroneamente que um bot teria uma pontuação de 40 em vez de apenas a maior pontuação de bot]

[editar 3: deixou o chaser um pouco mais defensivo no final do jogo]


Bem-vindo ao PPCG! Boa idéia para não apenas tentar recuperar o atraso, mas também passar em primeiro lugar. Estou executando uma simulação agora e desejo-lhe boa sorte!
maxb

Obrigado! Inicialmente, tentei superar o líder anterior em uma quantia fixa (valores experimentados entre 6 e 20), mas acontece apenas jogando duas vezes mais feiras melhores.
AKroell

@JonathanFrech obrigado, corrigido
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

Um par de bots, eles trazem seus próprios conjuntos de dados e os rolam para prever o futuro. Se um deles é um 6, o FutureBot não consegue se lembrar qual dos 2 dados era para o próximo lançamento, então ele desiste.

Eu me pergunto o que fará melhor.

O OneStepAhead é um pouco parecido com o OneInFive para o meu gosto, mas também quero ver como ele se compara ao FutureBot e ao OneInFive.

Edit: Agora eles param depois de atingir 45


Bem-vindo ao PPCG! Seu bot definitivamente brinca com o espírito do jogo! Farei uma simulação ainda esta noite.
maxb

Obrigado! Estou curioso para saber como vai dar certo, mas acho que será no lado mais baixo.
William porter
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.