Una partita a dadi, ma evita il numero 6 [chiuso]


58

Torneo finito!

Il torneo è finito! La simulazione finale è stata eseguita durante la notte, per un totale di partite. Il vincitore è Christian Sievers con il suo bot OptFor2X . Christian Sievers è anche riuscito ad assicurarsi il secondo posto con Rebel . Congratulazioni! Di seguito puoi vedere l'elenco ufficiale dei punteggi migliori del torneo.3108

Se vuoi ancora giocare, sei più che benvenuto a utilizzare il controller pubblicato di seguito e utilizzare il codice al suo interno per creare il tuo gioco.

Dado

Sono stato invitato a giocare una partita a dadi di cui non avevo mai sentito parlare. Le regole erano semplici, ma penso che sarebbe perfetto per una sfida KotH.

Le regole

L'inizio del gioco

Il dado gira intorno al tavolo e ogni volta che è il tuo turno, puoi lanciare il dado tutte le volte che vuoi. Tuttavia, devi lanciarlo almeno una volta. Tieni traccia della somma di tutti i tiri per il tuo round. Se si sceglie di interrompere, il punteggio per il round viene aggiunto al punteggio totale.

Quindi perché mai smetteresti di lanciare il dado? Perché se ottieni 6, il tuo punteggio per l'intero round diventa zero e il dado viene passato. Pertanto, l'obiettivo iniziale è aumentare il punteggio il più rapidamente possibile.

Chi è il vincitore?

Quando il primo giocatore attorno al tavolo raggiunge 40 o più punti, inizia l'ultimo round. Una volta iniziato l'ultimo round, tutti tranne la persona che ha iniziato l'ultimo round ottengono un altro turno.

Le regole per l'ultimo round sono le stesse di qualsiasi altro round. Scegli di continuare a lanciare o di smettere. Tuttavia, sai che non hai alcuna possibilità di vincere se non ottieni un punteggio più alto di quelli precedenti a te nell'ultimo round. Ma se continui ad andare troppo lontano, potresti ottenere un 6.

Tuttavia, c'è un'altra regola da prendere in considerazione. Se il tuo punteggio totale attuale (il tuo punteggio precedente + il tuo punteggio attuale per il round) è di 40 o più e ottieni un 6, il tuo punteggio totale è impostato su 0. Ciò significa che devi ricominciare tutto da capo. Se ottieni un 6 quando il tuo punteggio totale attuale è 40 o più, il gioco continua normalmente, tranne per il fatto che ora sei all'ultimo posto. L'ultimo round non viene attivato quando viene azzerato il punteggio totale. Potresti ancora vincere il round, ma diventa più impegnativo.

Il vincitore è il giocatore con il punteggio più alto al termine dell'ultimo round. Se due o più giocatori condividono lo stesso punteggio, verranno conteggiati tutti come vincitori.

Un'ulteriore regola è che il gioco continua per un massimo di 200 round. Questo per evitare casi in cui più robot continuano a lanciare fino a quando non colpiscono 6 per rimanere al loro punteggio attuale. Una volta superato il 199 ° round, last_roundviene impostato su true e viene riprodotto un altro round. Se il gioco passa a 200 round, il bot (o i bot) con il punteggio più alto è il vincitore, anche se non ha 40 punti o più.

Ricapitolare

  • Ad ogni round continui a lanciare il dado finché non decidi di fermarti o di ottenere un 6
  • Devi lanciare il dado una volta (se il tuo primo lancio è un 6, il tuo round è immediatamente finito)
  • Se ottieni un 6, il tuo punteggio attuale è impostato su 0 (non il tuo punteggio totale)
  • Aggiungi il tuo punteggio attuale al tuo punteggio totale dopo ogni round
  • Quando un bot termina il proprio turno con un punteggio totale di almeno 40, tutti gli altri ottengono un ultimo turno
  • Se il tuo punteggio totale attuale è e ottieni un 6, il tuo punteggio totale è impostato su 0 e il tuo round è finito40
  • L'ultimo round non viene attivato quando si verifica quanto sopra
  • La persona con il punteggio totale più alto dopo l'ultimo round è il vincitore
  • Nel caso in cui vi siano più vincitori, tutti verranno conteggiati come vincitori
  • Il gioco dura un massimo di 200 round

Chiarimento dei punteggi

  • Punteggio totale: il punteggio che hai salvato nei round precedenti
  • Punteggio attuale: il punteggio per il round corrente
  • Punteggio totale attuale: la somma dei due punteggi sopra

Come partecipi

Per partecipare a questa sfida di KotH, dovresti scrivere una classe Python che eredita da Bot. Si dovrebbe implementare la funzione: make_throw(self, scores, last_round). Quella funzione verrà chiamata una volta che è il tuo turno e il tuo primo lancio non è stato un 6. Per continuare a lanciare, dovresti yield True. Per smettere di lanciare, dovresti yield False. Dopo ogni lancio, update_stateviene chiamata la funzione genitore . Quindi, hai accesso ai tuoi tiri per il round corrente usando la variabile self.current_throws. Puoi anche accedere al tuo indice utilizzando self.index. Quindi, per vedere il tuo punteggio totale che useresti scores[self.index]. Puoi anche accedere a end_scoreper il gioco usando self.end_score, ma puoi tranquillamente supporre che saranno 40 per questa sfida.

Puoi creare funzioni di supporto all'interno della tua classe. È inoltre possibile ignorare le funzioni esistenti nella Botclasse genitore, ad esempio se si desidera aggiungere più proprietà della classe. Non è consentito modificare lo stato del gioco in alcun modo tranne che per cedere Trueo False.

Sei libero di trarre ispirazione da questo post e di copiare uno dei due robot che ho incluso qui. Tuttavia, temo che non siano particolarmente efficaci ...

Sul consentire altre lingue

Sia nella sandbox che in The Nineteenth Byte, abbiamo discusso di come consentire l'invio in altre lingue. Dopo aver letto di tali implementazioni e aver ascoltato argomenti da entrambe le parti, ho deciso di limitare questa sfida solo a Python. Ciò è dovuto a due fattori: il tempo necessario per supportare più lingue e la casualità di questa sfida che richiede un numero elevato di iterazioni per raggiungere la stabilità. Spero che parteciperai ancora e, se vuoi imparare un po 'di Python per questa sfida, cercherò di essere disponibile nella chat il più spesso possibile.

Per qualsiasi domanda tu possa avere, puoi scrivere nella chat room per questa sfida . Ci vediamo lì!

Regole

  • Il sabotaggio è permesso e incoraggiato. Cioè, sabotaggio contro altri giocatori
  • Qualsiasi tentativo di armeggiare con il controller, il tempo di esecuzione o altri invii verrà squalificato. Tutti gli invii dovrebbero funzionare solo con gli input e lo spazio di archiviazione forniti.
  • Qualsiasi bot che utilizza più di 500 MB di memoria per prendere una decisione sarà squalificato (se hai bisogno di tanta memoria, dovresti ripensare le tue scelte)
  • Un bot non deve implementare esattamente la stessa strategia esistente, intenzionalmente o accidentalmente.
  • Puoi aggiornare il tuo bot al momento della sfida. Tuttavia, potresti anche pubblicare un altro bot se il tuo approccio è diverso.

Esempio

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

Questo robot continuerà fino a quando non avrà un punteggio di almeno 10 per il round, o lancia un 6. Nota che non hai bisogno di alcuna logica per gestire il lancio 6. Nota anche che se il tuo primo lancio è un 6, make_throwè mai chiamato, dato che il tuo round è immediatamente finito.

Per coloro che sono nuovi in ​​Python (e nuovi nel yieldconcetto), ma vogliono provarlo, la yieldparola chiave è simile a un ritorno in alcuni modi, ma diversa in altri modi. Puoi leggere il concetto qui . Fondamentalmente, una volta terminato yield, la funzione si interromperà e il valore modificato yieldverrà inviato al controller. Lì, il controller gestisce la sua logica fino a quando non è il momento per il bot di prendere un'altra decisione. Quindi il controller ti manda il lancio dei dadi e la tua make_throwfunzione continuerà ad essere eseguita esattamente dove prima si fermava, sostanzialmente sulla linea dopo la precedente yielddichiarazione.

In questo modo, il controller di gioco può aggiornare lo stato senza richiedere una chiamata di funzione bot separata per ogni lancio di dadi.

specificazione

È possibile utilizzare qualsiasi libreria Python disponibile in pip. Per essere in grado di ottenere una buona media, hai un limite di tempo di 100 millisecondi per round. Sarei davvero felice se la tua sceneggiatura fosse molto più veloce di così, così da poter eseguire più round.

Valutazione

Per trovare il vincitore, prenderò tutti i robot e li eseguirò in gruppi casuali di 8. Se ci sono meno di 8 classi presentate, li eseguirò in gruppi casuali di 4 per evitare di avere sempre tutti i robot in ogni round. Eseguirò simulazioni per circa 8 ore e il vincitore sarà il bot con la percentuale di vincita più alta. Eseguirò le simulazioni finali all'inizio del 2019, dandoti tutto il Natale per codificare i tuoi robot! La data finale preliminare è il 4 gennaio, ma se è troppo poco tempo posso cambiarla in una data successiva.

Fino ad allora, proverò a fare una simulazione giornaliera usando 30-60 minuti di tempo della CPU e aggiornando il quadro di valutazione. Questo non sarà il punteggio ufficiale, ma servirà da guida per vedere quali robot funzionano meglio. Tuttavia, con l'arrivo del Natale, spero che tu possa capire che non sarò sempre disponibile. Farò del mio meglio per eseguire simulazioni e rispondere a qualsiasi domanda relativa alla sfida.

Provalo tu stesso

Se vuoi eseguire le tue simulazioni, ecco il codice completo per il controller che esegue la simulazione, inclusi due robot di esempio.

controllore

Ecco il controller aggiornato per questa sfida. Supporta output ANSI, multi-threading e raccoglie statistiche aggiuntive grazie ad AKroell ! Quando apporterò modifiche al controller, aggiornerò il post una volta completata la documentazione.

Grazie a BMO , il controller è ora in grado di scaricare tutti i bot da questo post usando il -dflag. Altre funzionalità sono invariate in questa versione. Ciò dovrebbe garantire che tutte le ultime modifiche vengano simulate al più presto!

#!/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 si desidera accedere al controller originale per questa sfida, è disponibile nella cronologia delle modifiche. Il nuovo controller ha la stessa logica per l'esecuzione del gioco, l'unica differenza sono le prestazioni, la raccolta delle statistiche e la stampa più bella.

Motori di ricerca

Sulla mia macchina, i robot sono conservati nel file forty_game_bots.py. Se si utilizza qualsiasi altro nome per il file, è necessario aggiornare l' importistruzione nella parte superiore del controller.

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

Esecuzione della simulazione

Per eseguire una simulazione, salva entrambi i frammenti di codice pubblicati sopra in due file separati. Li ho salvati come forty_game_controller.pye forty_game_bots.py. Quindi semplicemente usi python forty_game_controller.pyo python3 forty_game_controller.pydipende dalla tua configurazione di Python. Seguire le istruzioni da lì se si desidera configurare ulteriormente la simulazione, oppure provare a armeggiare con il codice, se lo si desidera.

Statistiche del gioco

Se stai creando un bot che punta a un determinato punteggio senza prendere in considerazione altri bot, questi sono i percentili dei punteggi vincenti:

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

Miglior punteggio

Man mano che verranno pubblicate più risposte, proverò a mantenere aggiornato questo elenco. I contenuti dell'elenco saranno sempre tratti dall'ultima simulazione. I bot ThrowTwiceBote GoToTenBotsono i bot del codice sopra e vengono utilizzati come riferimento. Ho fatto una simulazione con 10 ^ 8 giochi, che ha richiesto circa 1 ora. Poi ho visto che il gioco ha raggiunto la stabilità rispetto alle mie corse con 10 ^ 7 partite. Tuttavia, con le persone che pubblicano ancora robot, non farò più simulazioni fino a quando la frequenza delle risposte non sarà diminuita.

Provo ad aggiungere tutti i nuovi robot e aggiungere tutte le modifiche apportate ai robot esistenti. Se sembra che mi sia sfuggito il tuo bot o eventuali nuove modifiche, scrivi nella chat e mi assicurerò di avere la tua versione più recente nella prossima simulazione.

Ora abbiamo più statistiche per ogni bot grazie ad AKroell ! Le tre nuove colonne contengono il punteggio massimo per tutte le partite, il punteggio medio per partita e il punteggio medio quando si vince per ciascun bot.

Come sottolineato nei commenti, c'è stato un problema con la logica del gioco che ha reso i bot con un indice più alto all'interno di un gioco ottenere in alcuni casi un round extra. Questo è stato risolto ora, e i punteggi seguenti riflettono questo.

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

I seguenti robot (tranne Rebel) sono fatti per piegare le regole e i creatori hanno accettato di non prendere parte al torneo ufficiale. Tuttavia, penso ancora che le loro idee siano creative e che meritino una menzione d'onore. Rebel è anche in questo elenco perché utilizza una strategia intelligente per evitare il sabotaggio e in realtà si comporta meglio con il robot di sabotaggio in gioco.

I bot NeoBote KwisatzHaderachfa seguire le regole, ma utilizza una lacuna prevedendo il generatore casuale. Dato che questi robot richiedono molte risorse per simulare, ho aggiunto le sue statistiche da una simulazione con meno giochi. Il bot HarkonnenBotottiene la vittoria disabilitando tutti gli altri bot, il che è strettamente contro le regole.

    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
Quindi forse le regole sarebbero leggermente più chiare se dicessero "quando un giocatore termina il proprio turno con un punteggio di almeno 40, tutti gli altri ottengono un ultimo turno". Questo evita l'apparente conflitto sottolineando che non è il 40 che innesca davvero l'ultimo round, si ferma con almeno 40.
aschepler

1
@aschepler è una buona formulazione, modificherò il post quando sono sul mio computer
maxb

2
@maxb Ho esteso il controller per aggiungere altre statistiche rilevanti per il mio processo di sviluppo: punteggio più alto raggiunto, punteggio medio raggiunto e punteggio medio vincente gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell

2
Sembra molto simile a un gioco di dadi molto divertente chiamato Farkled en.wikipedia.org/wiki/Farkle
Caleb Jay

5
Sto votando per chiudere questa domanda perché è già di fatto chiuso a nuove risposte ("Il torneo è finito! La simulazione finale è stata eseguita durante la notte, per un totale di 3 ∗ 108 partite")
pppery

Risposte:


6

OptFor2X

Questo bot segue un'approssimazione della strategia ottimale per la versione a due giocatori di questo gioco, usando solo il suo punteggio e il punteggio del miglior avversario. Nell'ultimo round, la versione aggiornata considera tutti i punteggi.

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

Esaminerò l'implementazione il prima possibile. Con le celebrazioni natalizie, potrebbe non essere fino al 25
massimo

Il tuo bot è in testa! Inoltre, non è necessario per farlo funzionare più velocemente, è approssimativamente veloce come tutti gli altri robot nel prendere decisioni.
max

Non volevo renderlo più veloce. Ho già fatto quello che volevo fare - inizializzare solo una volta -, ma cercavo un modo migliore per farlo, soprattutto senza definire funzioni al di fuori della classe. Penso che ora sia meglio.
Christian Sievers,

Ora sembra molto meglio, buon lavoro!
max

Congratulazioni per aver ottenuto il primo e il secondo posto!
max.

20

NeoBot

Invece, cerca solo di realizzare la verità: non c'è un cucchiaio

NeoBot fa capolino nella matrice (aka random) e predice se il prossimo tiro sarà un 6 o no - non può fare nulla per essere dato un 6 all'inizio, ma è più che felice di schivare un end streak.

NeoBot in realtà non modifica il controller o il runtime, ma richiede educatamente alla libreria ulteriori informazioni.

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

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

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

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

1
Benvenuti in PPCG! Questa è una risposta davvero impressionante. Quando l'ho eseguito per la prima volta, ero infastidito dal fatto che utilizzava la stessa quantità di runtime di tutti gli altri robot combinati. Poi ho guardato la percentuale di vincita. Modo davvero intelligente di evitare le regole. Consentirò al tuo bot di partecipare al torneo, ma spero che altri si astengano dall'utilizzare la stessa tattica di questo, in quanto viola lo spirito del gioco.
massimo

2
Dato che esiste un divario così grande tra questo bot e il secondo posto, combinato con il fatto che il tuo bot richiede un sacco di elaborazione, accetteresti che io esegua una simulazione con meno iterazioni per trovare la tua percentuale di vincita, e quindi eseguo il funzionario simulazione senza il tuo bot?
massimo

3
Bene da parte mia, ho pensato che questo fosse probabilmente squalificabile e sicuramente non del tutto nello spirito del gioco. Detto questo, è stato un vero spasso lavorare e una scusa divertente per cercare nel codice sorgente di Python.
Per lo più innocuo, il

2
Grazie! Non credo che nessun altro robot si avvicinerà al tuo punteggio. E per chiunque stia pensando di implementare questa strategia, non farlo. D'ora in poi questa strategia è contro le regole e NeoBot è l'unico a cui è consentito utilizzarlo per mantenere il torneo equo.
massimo

1
Bene, myBot batte tutti, ma questo è troppo meglio - anche se se pubblicassi un bot in questo modo, otterrei -100 e non il miglior punteggio.
Jan Ivan

15

Cooperative Swarm

Strategia

Non credo che nessun altro abbia ancora notato il significato di questa regola:

Se il gioco passa a 200 round, il bot (o i bot) con il punteggio più alto è il vincitore, anche se non ha 40 punti o più.

Se ogni robot rotolasse sempre fino a quando non veniva eliminato, allora tutti avrebbero avuto un punteggio pari a zero alla fine del round 200 e tutti avrebbero vinto! Pertanto, la strategia di Cooperative Swarm è di cooperare fintanto che tutti i giocatori hanno un punteggio pari a zero, ma di giocare normalmente se qualcuno segna punti.

In questo post, invio due robot: il primo è CooperativeSwarmBot e il secondo è CooperativeThrowTwice. CooperativeSwarmBot funge da classe base per tutti i robot che fanno parte formalmente dello sciame cooperativo e ha un comportamento segnaposto che accetta semplicemente il suo primo lancio riuscito quando la cooperazione fallisce. CooperativeSwarmBot ha CooperativeSwarmBot come suo genitore ed è identico ad esso in ogni modo, tranne per il fatto che il suo comportamento non cooperativo è quello di fare due tiri invece di uno. Nei prossimi giorni rivedrò questo post per aggiungere nuovi robot che usano un comportamento molto più intelligente giocando contro robot non cooperativi.

Codice

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

Analisi

vitalità

È molto difficile cooperare in questo gioco perché abbiamo bisogno del supporto di tutti e otto i giocatori perché funzioni. Poiché ogni classe di bot è limitata a un'istanza per partita, questo è un obiettivo difficile da raggiungere. Ad esempio, la probabilità di scegliere otto robot cooperativi da un pool di 100 robot cooperativi e 30 robot non cooperativi è:

100130991299812897127961269512594124931230.115

icn

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

i=8n=38

Argomento di studio

Per una serie di motivi (vedi note 1 e 2), uno sciame cooperativo adeguato non potrà mai competere nei giochi ufficiali. Come tale, riassumerò i risultati di una delle mie simulazioni in questa sezione.

Questa simulazione ha eseguito 10000 giochi usando gli altri 38 bot che erano stati pubblicati qui l'ultima volta che ho controllato e 2900 bot che avevano CooperativeSwarmBot come classe principale. Il controller ha riferito che il 9051 dei 10000 giochi (90,51%) è terminato a 200 round, il che è abbastanza vicino alla previsione che il 90% dei giochi sarebbe cooperativo. L'implementazione di questi robot è stata banale; diversi da CooperativeSwarmBot hanno tutti preso questa forma:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Meno del 3% dei bot ha una percentuale di vincita inferiore all'80% e poco più dell'11% dei bot ha vinto ogni singolo gioco a cui ha giocato. La percentuale media di vincita dei 2900 robot nello sciame è di circa l'86%, il che è scandalosamente buono. Per fare un confronto, i migliori giocatori nella classifica ufficiale attuale vincono meno del 22% dei loro giochi. Non riesco ad adattare la lista completa dello sciame cooperativo entro la lunghezza massima consentita per una risposta, quindi se vuoi vedere che dovrai invece andare qui: https://pastebin.com/3Zc8m1Ex

Poiché ogni bot ha giocato in media circa 27 partite, la fortuna gioca un tiro relativamente grande quando si guardano i risultati per i singoli robot. Poiché non ho ancora implementato una strategia avanzata per i giochi non cooperativi, la maggior parte degli altri robot ha beneficiato drasticamente del gioco contro lo sciame cooperativo, realizzando persino un tasso di vincita mediano dello sciame cooperativo dell'86%.

I risultati completi per i robot che non sono nello sciame sono elencati di seguito; ci sono due robot i cui risultati ritengo meritino un'attenzione particolare. Innanzitutto, StopBot non è riuscito a vincere alcuna partita. Ciò è particolarmente tragico perché lo sciame cooperativo stava usando esattamente la stessa strategia di StopBot; ti saresti aspettato che StopBot vincesse otto delle sue partite per caso, e un po 'di più perché lo sciame cooperativo è costretto a dare ai suoi avversari la prima mossa. Il secondo risultato interessante, tuttavia, è che il duro lavoro di PointsAreForNerdsBot alla fine è stato ripagato: ha collaborato con lo sciame e è riuscito a vincere ogni singola partita a cui ha giocato!

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

Difetti

Ci sono un paio di inconvenienti a questo approccio cooperativo. In primo luogo, quando si gioca contro robot non cooperativi, i robot cooperativi non ottengono mai il vantaggio del primo turno perché quando giocano per primi, non sanno ancora se i loro avversari sono disposti a collaborare e quindi non hanno altra scelta che ottenere un punteggio pari a zero. Allo stesso modo, questa strategia cooperativa è estremamente vulnerabile allo sfruttamento da parte di bot dannosi; ad esempio, durante il gioco cooperativo il robot che gioca l'ultimo nell'ultimo round può scegliere di interrompere immediatamente il lancio per far perdere tutti gli altri (supponendo, ovviamente, che il loro primo lancio non fosse un sei).

Cooperando, tutti i robot possono ottenere la soluzione ottimale con una percentuale di vincita del 100%. Pertanto, se il tasso di vittoria fosse l'unica cosa che contava, la cooperazione sarebbe un equilibrio stabile e non ci sarebbe nulla di cui preoccuparsi. Tuttavia, alcuni robot potrebbero dare la priorità ad altri obiettivi, come raggiungere la vetta della classifica. Ciò significa che esiste il rischio che un altro bot possa disertare dopo il tuo ultimo turno, il che crea un incentivo per te al primo difetto. Poiché la preparazione di questa competizione non ci consente di vedere cosa hanno fatto i nostri avversari nelle loro partite precedenti, non possiamo penalizzare le persone che hanno disertato. Pertanto, la cooperazione è in definitiva un equilibrio instabile destinato al fallimento.

Le note

[1]: I motivi principali per cui non desidero inviare migliaia di bot invece di solo due sono che ciò rallenterebbe la simulazione di un fattore dell'ordine di 1000 [2] e che farebbe molto casino ottenere percentuali in quanto altri robot giocheranno quasi esclusivamente contro lo sciame anziché uno contro l'altro. Più importante, tuttavia, è il fatto che, anche se volessi, non sarei in grado di realizzare così tanti robot in un lasso di tempo ragionevole senza infrangere lo spirito della regola secondo cui "Un bot non deve implementare esattamente la stessa strategia di un esistente, intenzionalmente o accidentalmente ".

[2]: Penso che ci siano due ragioni principali per cui la simulazione rallenta quando si esegue uno sciame cooperativo. Innanzitutto, più bot significa più giochi se si desidera che ogni bot giochi nello stesso numero di giochi (nel caso di studio, il numero di giochi differirebbe di un fattore di circa 77). In secondo luogo, i giochi cooperativi richiedono solo più tempo perché durano per 200 round completi e all'interno di un round i giocatori devono continuare a girare indefinitamente. Per la mia configurazione, i giochi hanno impiegato circa 40 volte di più per simulare: il case study ha richiesto poco più di tre minuti per eseguire 10000 giochi, ma dopo aver rimosso lo sciame cooperativo avrebbe finito per 10000 giochi in soli 4,5 secondi. Tra questi due motivi, stima che occorrerebbe circa 3100 volte più a lungo per misurare accuratamente le prestazioni dei robot quando c'è uno sciame in competizione rispetto a quando non lo è.


4
Wow. E benvenuto in PPCG. Questa è piuttosto la prima risposta. Non stavo davvero pianificando una situazione come questa. Hai sicuramente trovato una scappatoia nelle regole. Non sono davvero sicuro di come dovrei segnare questo, dal momento che la tua risposta è una raccolta di bot piuttosto che un singolo bot. Tuttavia, l'unica cosa che dirò in questo momento è che sembra ingiusto che un partecipante controlli il 98,7% di tutti i robot.
massimo

2
In realtà non voglio che i robot duplicati partecipino alla competizione ufficiale; ecco perché ho eseguito la simulazione da solo invece di inviare migliaia di robot quasi identici. Revisionerò la mia richiesta per renderlo più chiaro.
Einhaender,

1
Se avessi anticipato una risposta come questa, avrei cambiato i giochi che vanno a 200 round in modo che non diano punteggi ai giocatori. Tuttavia, come notate, esiste una regola sulla creazione di robot identici che renderebbe questa strategia contraria alle regole. Non ho intenzione di cambiare le regole, poiché sarebbe ingiusto per tutti coloro che hanno creato un bot. Tuttavia, il concetto di cooperazione è molto interessante e spero che siano stati inviati altri robot che implementano la strategia di cooperazione in combinazione con la sua strategia unica.
massimo

1
Penso che il tuo post sia chiaro dopo averlo letto più a fondo.
massimo

Quanti robot esistenti dovrebbero avvolgere il proprio codice in questo quadro di cooperazione in modo che la maggior parte di loro possa vedere un guadagno netto nella posizione in classifica? La mia ipotesi ingenua è del 50%.
Sparr

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

Basta provare con tutto GoToNBot, e 20, 22, 24 gioca meglio. Non so perché.


Aggiornamento: interrompi sempre il tiro se ottieni un punteggio di 40 o più.


Ho anche sperimentato questo tipo di robot. Il punteggio medio più alto per round si trova quando il bot arriva a 16, ma presumo che il "fine partita" faccia vincere il 20-bot più spesso.
massimo

@maxb Non è così, 20 sono ancora i migliori senza "end game" nel mio test. Forse l'hai provato sulla vecchia versione del controller.
tsh

Ho eseguito un test separato prima di progettare questa sfida, in cui ho calcolato il punteggio medio per round per le due tattiche nel mio post ("lancio x volte" e "lancio fino a x punteggio"), e il massimo che ho trovato era per 15-16 . Sebbene la mia dimensione del campione avrebbe potuto essere troppo piccola, ho notato instabilità.
massimo

2
Ho fatto alcuni test con questo, e la mia conclusione è semplicemente che 20 funziona bene perché è 40/2. Anche se non sono completamente sicuro. Quando ho impostato end_score4000 (e ho cambiato il tuo bot per usarlo nel targetcalcolo), i 15-16 bot erano molto meglio. Ma se il gioco fosse solo per aumentare il tuo punteggio sarebbe banale.
max

1
@maxb Se end_scoreè 4000, è quasi impossibile ottenerne 4000 prima di 200 turni. E il gioco è semplicemente chi ha ottenuto il punteggio più alto in 200 turni. E fermarsi alle 15 dovrebbe funzionare da quando questa volta la strategia per il punteggio più alto in un turno è uguale al punteggio più alto in 200 turni.
TSH

10

Rullo adattivo

Inizia più aggressivo e si calma verso la fine del round.
Se crede che stia vincendo, tira un ulteriore tempo per la sicurezza.

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

Grande prima presentazione! Lo eseguirò contro i miei robot che ho scritto per i test, ma aggiornerò il punteggio più alto quando saranno stati pubblicati più bot.
massimo

Ho eseguito alcuni test con lievi modifiche al tuo bot. lim = max(min(self.end_score - scores[self.index], 24), 6)alzando il massimo a 24 e aggiungendo un minimo di 6 entrambi aumentano la percentuale di vincita da soli e ancor di più combinati.
AKroell,

@AKroell: Cool! Ho intenzione di fare qualcosa di simile per assicurarmi che rotoli alcune volte alla fine, ma non mi sono ancora preso il tempo di farlo. Stranamente, però, sembra che vada peggio con quei valori quando eseguo 100.000 corse. Ho provato solo con 18 robot però. Forse dovrei fare dei test con tutti i robot.
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 si rifiuta di essere secondo a nessuno. Finché c'è un bot con un punteggio più alto, continuerà a girare.


A causa di come yieldfunziona, se inizia a rotolare non si fermerà mai. Ti consigliamo di aggiornare my_scorenel ciclo.
Spitemaster,

@Spitemaster Risolto, grazie.
Mnemonico

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

L'idea è che altri robot potrebbero perdere punti, quindi essere secondi non è male - ma se sei molto indietro, potresti anche andare in rovina.


1
Benvenuti in PPCG! Sto esaminando la tua richiesta e sembra che più giocatori partecipano al gioco, più bassa è la percentuale di vincita per il tuo bot. Non so subito perché. Con i bot abbinati a 1vs1 ottieni un tasso di vincita del 10%. L'idea sembra promettente e il codice sembra corretto, quindi non posso davvero dire perché il tuo tasso di vittoria non sia più alto.
massimo

6
Ho esaminato il comportamento, e questa linea mi aveva confuso: 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. Anche se il tuo bot è in testa dopo 7 lanci, continua fino a quando non colpisce un 6. Mentre sto scrivendo questo ho capito il problema! Gli scoresunici contengono i punteggi totali, non i casi di dado per il round corrente. Dovresti modificarlo per essere current_score = scores[self.index] + sum(self.current_throws).
massimo

Grazie - farà quel cambiamento!
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

Vogliamo andare alla grande o andare a casa, giusto? GoHomeBot principalmente va a casa. (Ma sorprendentemente bene!)


Poiché questo bot vale sempre per 40 punti, non avrà mai alcun punto scoresnell'elenco. C'era un bot come questo prima (il bot GoToEnd), ma David ha eliminato la loro risposta. Sostituirò quel bot con il tuo.
massimo

1
È abbastanza divertente, visto le statistiche estese di questo robot: Tranne i punti AreForNerds e StopBot, questo bot ha il punteggio medio più basso, eppure ha un buon rapporto di vittorie
Belhenix,

5

EnsureLead

class EnsureLead(Bot):

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

Garantire che prende in prestito idee da GoTo20Bot. Aggiunge il concetto che considera sempre (quando in last_round o raggiungendo 40) che ce ne sono altri che avranno almeno un altro tiro. Pertanto, il bot cerca di anticiparli un po ', in modo tale che debbano recuperarli.


4

Roll6TimesV2

Non batte l'attuale migliore, ma penso che andrà meglio con più robot in gioco.

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

A proposito, gioco davvero fantastico.


Benvenuti in PPCG! Molto impressionante non solo per la tua prima sfida KotH, ma anche per la tua prima risposta. Sono contento che ti sia piaciuto il gioco! Ho discusso molto sulla migliore tattica per il gioco dopo la sera in cui l'ho giocata, quindi mi è sembrato perfetto per una sfida. Attualmente sei al terzo posto su 18.
massimo

4

StopBot

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

Letteralmente solo un tiro.

Ciò equivale alla Botclasse base .


1
Non dispiacerti! Stai seguendo tutte le regole, anche se temo che il tuo bot non sia terribilmente efficace con una media di 2,5 punti per round.
massimo

1
Lo so, qualcuno ha dovuto pubblicare quel bot però. Bot degenerati per la perdita.
Zacharý,

5
Direi che sono rimasto colpito dal fatto che il tuo bot abbia ottenuto esattamente una vittoria nell'ultima simulazione, dimostrando che non è del tutto inutile.
massimo

2
VINCE UN GIOCO ?! Questo è sorprendente.
Zacharý,

3

BringMyOwn_dice (BMO_d)

Questo robot adora i dadi, porta 2 (sembra funzionare al meglio) da solo. Prima di lanciare i dadi in un round, lancia i suoi 2 dadi e calcola la loro somma, questo è il numero di tiri che sta per eseguire, lancia solo se non ha già 40 punti.

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
Stavo pensando a un robot a caso usando un lancio della moneta, ma questo è più in spirito con la sfida! Penso che due dadi ottengano il meglio, dato che ottieni il maggior numero di punti per round quando lanci il dado 5-6 volte, vicino al punteggio medio quando lanci due dadi.
massimo

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 oncenon è necessario: viene lanciato una volta prima di chiamare il bot. Il tuo bot lancerà sempre almeno due volte.
Spitemaster,

Grazie. Sono stato ingannato dal nome del metodo.
Peter Taylor,

@PeterTaylor Grazie per l'invio! Ho chiamato il make_throwmetodo all'inizio, quando volevo che i giocatori potessero saltare il loro turno. Immagino che sarebbe un nome più appropriato keep_throwing. Grazie per il feedback nella sandbox, ha davvero contribuito a rendere questa una vera sfida!
massimo

3

Vai in grande presto

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

Concetto: prova a vincere alla grande con un lancio iniziale (arrivando a 25), quindi sali da lì 2 tiri alla volta.


3

BinaryBot

Cerca di avvicinarsi al punteggio finale, in modo che non appena qualcun altro innesca l'ultimo round, può battere il loro punteggio per la vittoria. L'obiettivo è sempre a metà strada tra il punteggio attuale e il punteggio finale.

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, Hesitatesi rifiuta anche di attraversare la linea prima. Devi circondare la tua funzione con le classcose.
Christian Sievers,

3

PointsAreForNerdsBot

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

Questo non ha bisogno di spiegazioni.

OneInFiveBot

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

Continua a rotolare fino a quando non lancia un cinque sul proprio dado a 5 facce. Cinque è meno di sei, quindi DEVE VINCERE!


2
Benvenuti in PPCG! Sono sicuro che ne sei consapevole, ma il tuo primo bot è letteralmente il peggior bot in questa competizione! È OneInFiveBotun'idea chiara, ma penso che soffra alla fine del gioco rispetto ad alcuni dei robot più avanzati. Ancora un'ottima presentazione!
massimo

2
il OneInFiveBotè molto interessante nel modo in cui egli ha sempre il più alto punteggio complessivo raggiunto.
AKroell,

1
Grazie per aver dato StopBotun sacco da boxe: P. Il OneInFiveBot in realtà è abbastanza pulito, bel lavoro!
Zacharý,

@maxb Sì, è lì che ho ottenuto il nome. Onestamente non ho testato OneInFiveBote sta andando molto meglio di quanto mi aspettassi
The_Bob il


3

LizduadacBot

Cerca di vincere in 1 passaggio. La condizione finale è in qualche modo arbritraria.

Questo è anche il mio primo post (e sono nuovo su Python), quindi se battessi "PointsAreForNerdsBot", sarei felice!

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

Benvenuto in PPCG (e benvenuto in Python)! Avresti difficoltà a perdere contro PointsAreForNerdsBot, ma il tuo bot in realtà funziona abbastanza bene. Aggiornerò il punteggio più tardi stasera o domani, ma il tuo tasso di vincita è di circa il 15%, che è superiore alla media del 12,5%.
massimo

Per "momento difficile", significano che è impossibile (a meno che non abbia frainteso molto)
Zacharý

@maxb In realtà non pensavo che la percentuale di vincita sarebbe stata così alta! (Non l'ho provato localmente). Mi chiedo se cambiando il 50 per essere un po 'più alto / più basso aumenterebbe il tasso di vittoria.
lizduadac,

3

partenza lenta

Questo bot implementa l'algoritmo TCP Slow Start. Regola il suo numero di tiri ( ) in base al suo turno precedente: se non ha tirato un 6 nel turno precedente, aumenta il per questo turno; mentre riduce se lo ha fatto.

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

Benvenuti in PPCG! Approccio interessante, non so quanto sia sensibile alle fluttuazioni casuali. Due cose che sono necessarie per eseguire questa corsa: def updateValues():dovrebbe essere def updateValues(self):(o def update_values(self):se vuoi seguire PEP8). In secondo luogo, la chiamata updateValues()dovrebbe invece essere self.updateValues()(o self.update_vales()).
massimo

2
Inoltre, penso che sia necessario aggiornare la ivariabile nel ciclo while. In questo momento il tuo bot passa interamente il ciclo while o rimane bloccato nel ciclo while fino a quando non colpisce 6.
maxb

Nell'attuale punteggio, mi sono preso la libertà di attuare questi cambiamenti. Penso che potresti sperimentare il valore iniziale di self.nore vedere come influenza le prestazioni del tuo bot.
massimo

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                                               

La presenza di solito vince, ma il destino non può sempre essere evitato.
Grandi e misteriosi sono i modi di Shai-Hulud!


All'inizio di questa sfida (cioè prima che NeoBotfosse pubblicato), ho scritto un Oraclebot quasi banale :

    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

ma non l'ho pubblicato perché non pensavo fosse abbastanza interessante;) Ma una volta NeoBotentrato in testa ho iniziato a pensare a come battere la sua perfetta capacità di predire il futuro. Quindi ecco una citazione di Dune; è quando Paul Atreides, il Kwisatz Haderach, si trova in un nesso da cui si può srotolare un'infinità di futuri diversi:

La prescienza, si rese conto, era un'illuminazione che incorporava i limiti di ciò che rivelava, allo stesso tempo una fonte di accuratezza ed errore significativo. Una sorta di indeterminatezza di Heisenberg intervenne: il dispendio di energia che rivelò ciò che vide, cambiò ciò che vide ... ... l'azione più minuta - l'occhiolino di un occhio, una parola incurante, un granello di sabbia fuori posto - fece muovere una leva gigantesca attraverso universo noto. Vide la violenza con il risultato soggetto a così tante variabili che il suo minimo movimento ha creato grandi cambiamenti nei modelli.

La visione gli fece venire voglia di congelare nell'immobilità, ma anche questa era azione con le sue conseguenze.

Quindi ecco la risposta: prevedere il futuro è cambiarlo; e se stai molto attento, quindi con un'azione selettiva o inazione, puoi cambiarlo in modo vantaggioso, almeno la maggior parte delle volte. Anche il KwisatzHaderachnon può ottenere una percentuale di vincita del 100%!


Sembra che questo bot cambi lo stato del generatore di numeri casuali, per assicurarsi che eviti di far rotolare 6, o almeno anticiparlo. Lo stesso vale per HarkonnenBot. Tuttavia, noto che la percentuale di vincita di questi robot è molto più alta di quella di NeoBot. Stai manipolando attivamente il generatore di numeri casuali per evitare che rotoli 6?
max

Oh, nella mia prima lettura non ho notato che questo non è solo più bello di, NeoBotma anche migliore! Mi piace anche come fai un esempio di ciò che tutto ciò che usa la casualità (specialmente il controller) qui dovrebbe fare: usa la tua random.Randomistanza. Ad esempio NeoBot, questo sembra un po 'sensibile alle modifiche dei dettagli di implementazione non specificati del controller.
Christian Sievers,

@maxb: HarkonnenBotnon tocca l'RNG; non gli interessa affatto dei numeri casuali. Avvelena solo tutti gli altri robot, quindi si avvicina al traguardo il più lentamente possibile. Come molte prelibatezze culinarie, la vendetta è un piatto da gustare lentamente, dopo una lunga e delicata preparazione.
Dani O

@ChristianSievers: a differenza di NeoBot (e HarkonnenBot), KwisatzHaderachsi basa solo su un dettaglio dell'implementazione; in particolare non ha bisogno di sapere come viene implementato random.random (), solo che il controller lo utilizza; D
Dani O

1
Ho esaminato tutti i tuoi robot. Ho deciso di trattare KwisatzHaderache HarkonnenBotallo stesso modo di NeoBot. Riceveranno i loro punteggi da una simulazione con meno giochi e non saranno presenti nella simulazione ufficiale. Tuttavia, finiranno nella lista dei punteggi molto simili NeoBot. Il motivo principale per cui non partecipano alla simulazione ufficiale è che rovineranno altre strategie di bot. Però. WisdomOfCrowdsdovrebbe essere adatto alla partecipazione e sono curioso di sapere quali sono le novità che hai apportato!
max

2
class ThrowThriceBot(Bot):

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

Bene, quello è ovvio


Ho fatto alcuni esperimenti con quella classe di robot (era la tattica che ho usato quando ho giocato per la prima volta). Sono andato con 4 tiri quindi, anche se 5-6 hanno un punteggio medio più alto per round.
massimo

Inoltre, congratulazioni per la tua prima risposta KotH!
massimo

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 si comporta come se fosse sempre l'ultimo round ed è l'ultimo bot: continua a girare fino a quando non è in testa. Inoltre, non vuole accontentarsi di meno di 15 punti a meno che non sia effettivamente l'ultimo round o non raggiunga 40 punti.


Approccio interessante Penso che il tuo bot soffra se inizia a rimanere indietro. Poiché le probabilità di ottenere> 30 punti in un singolo round sono basse, è più probabile che il tuo bot rimanga al suo punteggio attuale.
max

1
Ho il sospetto che questo soffra dello stesso errore che ho fatto (vedi i commenti di NotTooFarBehindBot) - come nell'ultimo round, se non stai vincendo continuerai a lanciare fino a ottenere un 6 (i punteggi [self.index] non si aggiorna mai) In realtà - hai questa disuguaglianza nel modo sbagliato? max (score) sarà sempre> = score [self.index]
Stuart Moore

@StuartMoore Haha, sì, penso che tu abbia ragione. Grazie!
Spitemaster,

Ho il sospetto che tu voglia "e last_round" il secondo mentre fai quello che vuoi - altrimenti verrà usato il secondo mentre sia last_round vero o no
Stuart Moore

3
È intenzionale. Cerca sempre di essere in testa quando termina il suo turno.
Spitemaster,

2

QuotaBot

Ho implementato un ingenuo sistema di "quote", che in realtà sembrava avere un punteggio abbastanza alto nel complesso.

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à un errore per me. Inoltrewhile sum(self.current_throws)
Spitemaster il

@Spitemaster è stato un errore incollato nello scambio di stack, ora dovrebbe funzionare.
FlipTack

@Spitemaster è perché c'erano <e >simboli che interferivano con i <pre>tag che stavo usando
FlipTack

2

ExpectationsBot

Gioca semplicemente dritto, calcola il valore atteso per il lancio dei dadi e lo fa solo se è 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

Ho avuto problemi con il controller, ho ricevuto un "NameError: il nome 'bots_per_game' non è definito" su quello multithread, quindi non ho idea di come funzioni.


1
Penso che questo finisca per essere equivalente a un bot "Vai a 16", ma non ne abbiamo ancora uno
Stuart Moore,

1
@StuartMoore Questo ... è un punto molto vero, sì
Caino

Ho riscontrato problemi con il controller quando l'ho eseguito sul mio computer Windows. In qualche modo ha funzionato bene sulla mia macchina Linux. Sto aggiornando il controller e aggiornerò il post una volta fatto.
max

@maxb Grazie, probabilmente qualcosa su quali variabili sono disponibili nel diverso processo. FYI anche aggiornato questo, ho fatto un errore sciocco nel cedere: /
Caino

2

BlessRNG

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

BlessRNG FrankerZ GabeN BlessRNG


2

FortyTeen

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

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

Prova per 14 punti fino all'ultimo round, quindi supponi che tutti gli altri proveranno per 14 punti e provano a legare quel punteggio.


Ho ottenuto TypeError: unsupported operand type(s) for -: 'list' and 'int'con il tuo bot.
tsh

Suppongo che tu max_projected_scoredebba essere il massimo dell'elenco piuttosto che l'intero elenco, ho ragione? Altrimenti ricevo lo stesso problema di tsh.
massimo

Spiacenti, modificato per la correzione.
istocratico,

2

esitare

Esegue due passaggi modesti, quindi attende che qualcun altro attraversi la linea. La versione aggiornata non prova più a battere il punteggio più alto, vuole solo raggiungerlo - migliorando le prestazioni eliminando due byte del codice sorgente!

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

Ribelle

Questo bot combina la semplice strategia di Hesitate con la strategia avanzata dell'ultimo round di BotFor2X, cerca di ricordare chi è e si scatena quando trova che vive in un'illusione.

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)

Bene, questo è abbastanza elegante :) Inoltre, congratulazioni per aver ottenuto sia il primo che il secondo posto nella competizione principale!
Dani O

Naturalmente ho ottimizzato in HarkonnenBotmodo che Rebelnon possa più scostarsi;) e ho anche ottimizzato in TleilaxuBotmodo che Rebelnon lo rilevi più!
Dani O

1

Dammi il cinque

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

La metà delle volte, tireremo un 5 prima di un 6. Quando lo facciamo, incassiamo.


Se invece ci fermiamo a 1, fa progressi più lenti, ma è più probabile che arrivi a 40 in un singolo limite.
Mnemonico

Nei miei test, TakeOne ha ottenuto 20.868 punti per round rispetto ai 24.262 punti di TakeFive per round (e ha portato anche il tasso di vittoria da 0,291 a 0,259). Quindi non credo ne valga la pena.
Spitemaster,

1

inseguitore

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

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

Chaser cerca di raggiungere la posizione 1 Se è l'ultimo round cerca disperatamente di raggiungere almeno 50 punti Solo per buona misura lancia almeno quattro volte, non importa quale

[modifica 1: aggiunta la strategia go-for-gold nell'ultimo round]

[modifica 2: logica aggiornata perché ho erroneamente pensato che un bot avrebbe ottenuto un punteggio di 40 anziché solo il punteggio più alto del bot]

[modifica 3: reso chaser un po 'più difensivo alla fine del gioco]


Benvenuti in PPCG! Ottima idea di non solo cercare di recuperare il ritardo, ma anche passare il primo posto. Sto eseguendo una simulazione in questo momento e ti auguro buona fortuna!
massimo

Grazie! Inizialmente ho cercato di superare il leader precedente di un importo fisso (valori provati tra 6 e 20) ma risulta semplicemente lanciando altre due volte meglio.
AKroell,

@JonathanFrech grazie, risolto
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

Una coppia di robot, portano i propri set di dadi e li lancia per prevedere il futuro. Se uno ha un 6 si fermano, FutureBot non riesce a ricordare quale dei suoi 2 dadi era per il tiro successivo, quindi si arrende.

Mi chiedo quale farà meglio.

OneStepAhead è un po 'troppo simile a OneInFive per i miei gusti, ma voglio anche vedere come si confronta con FutureBot e OneInFive.

Modifica: ora si fermano dopo aver colpito 45


Benvenuti in PPCG! Il tuo bot gioca sicuramente con lo spirito del gioco! Farò una simulazione più tardi questa sera.
massimo

Grazie! Sono curioso di sapere come andrà bene, ma immagino che sarà nella parte bassa.
William Porter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.