Un jeu de dés, mais évitez le numéro 6 [fermé]


58

Tournoi terminé!

Le tournoi est maintenant terminé! La simulation finale s’est déroulée pendant la nuit, avec un total de parties. Le gagnant est Christian Sievers avec son bot OptFor2X . Christian Sievers a également réussi à s'assurer la deuxième place avec Rebel . Toutes nos félicitations! Ci-dessous, vous pouvez voir la liste officielle des meilleurs scores du tournoi.3108

Si vous souhaitez toujours jouer au jeu, vous pouvez utiliser le contrôleur affiché ci-dessous et utiliser le code qu'il contient pour créer votre propre jeu.

Dé

J'ai été invité à jouer à un jeu de dés dont je n'avais jamais entendu parler. Les règles étaient simples, mais je pense que ce serait parfait pour un défi KotH.

Les règles

Le début du jeu

Le dé fait le tour de la table et chaque fois que c'est votre tour, vous lancez le dé autant de fois que vous le souhaitez. Cependant, vous devez le jeter au moins une fois. Vous gardez une trace de la somme de tous les lancers pour votre tour. Si vous choisissez d'arrêter, le score de la ronde s'ajoute à votre score total.

Alors, pourquoi voudriez-vous jamais arrêter de jeter le dé? Parce que si vous obtenez 6, votre score pour le tour entier devient zéro et le dé est transmis. Ainsi, l'objectif initial est d'augmenter votre score le plus rapidement possible.

Qui est le gagnant?

Lorsque le premier joueur autour de la table atteint 40 points ou plus, le dernier tour commence. Une fois que le dernier tour a commencé, tout le monde sauf celui qui a initié le dernier tour a droit à un tour supplémentaire.

Les règles du dernier tour sont les mêmes que pour tout autre tour. Vous choisissez de continuer à lancer ou de vous arrêter. Cependant, vous savez que vous n’avez aucune chance de gagner si vous n’obtenez pas un score plus élevé que ceux que vous aviez au dernier tour. Mais si vous continuez à aller trop loin, alors vous pourriez obtenir un 6.

Cependant, il y a une autre règle à prendre en compte. Si votre score total actuel (votre score précédent + votre score actuel du tour) est égal ou supérieur à 40 et que vous atteignez un 6, votre score total est défini sur 0. Cela signifie que vous devez tout recommencer. Si vous frappez un 6 lorsque votre score total actuel est de 40 ou plus, le jeu continue normalement, sauf que vous êtes maintenant à la dernière place. Le dernier tour n'est pas déclenché lorsque votre score total est réinitialisé. Vous pouvez toujours gagner la manche, mais cela devient plus difficile.

Le gagnant est le joueur avec le score le plus élevé une fois le dernier tour terminé. Si deux joueurs ou plus partagent le même score, ils seront tous comptés comme vainqueurs.

Une règle supplémentaire est que le jeu continue pour un maximum de 200 tours. Ceci permet d'éviter les cas où plusieurs robots continuent à lancer jusqu'à ce qu'ils atteignent 6 pour rester à leur score actuel. Une fois que le 199ème tour est passé, last_roundest défini sur true et un autre tour est joué. Si le jeu se déroule à 200 tours, le bot (ou les bots) avec le score le plus élevé est le vainqueur, même s’ils n’ont pas 40 points ou plus.

résumer

  • À chaque tour, vous continuez à lancer le dé jusqu'à ce que vous choisissiez de vous arrêter ou que vous obteniez un 6
  • Vous devez lancer le dé une fois (si votre premier lancer est un 6, votre tour est immédiatement terminé)
  • Si vous obtenez un 6, votre score actuel est mis à 0 (pas votre score total)
  • Vous ajoutez votre score actuel à votre score total après chaque tour
  • Lorsqu'un bot termine son tour, ce qui donne un score total d'au moins 40, tout le monde a son dernier tour.
  • Si votre score total actuel est de et que vous obtenez un 6, votre score total est mis à 0 et votre partie est terminée40
  • Le dernier tour n'est pas déclenché lorsque ce qui précède se produit
  • La personne avec le score total le plus élevé après le dernier tour est le gagnant
  • Au cas où il y aurait plusieurs gagnants, tous seront comptés comme gagnants.
  • Le jeu dure 200 tours maximum

Clarification des scores

  • Score total: le score que vous avez enregistré lors des tours précédents
  • Score actuel: le score du tour en cours
  • Score total actuel: la somme des deux scores ci-dessus

Comment participez-vous

Pour participer à ce défi KotH, vous devez écrire une classe Python dont elle hérite Bot. Vous devez implémenter la fonction: make_throw(self, scores, last_round). Cette fonction sera appelée une fois que c'est votre tour, et votre premier lancer n'était pas un 6. Pour continuer à lancer, vous devriez yield True. Pour arrêter de lancer, vous devriez yield False. Après chaque lancer, la fonction parent update_stateest appelée. Ainsi, vous avez accès à vos lancers pour le tour en cours en utilisant la variable self.current_throws. Vous avez également accès à votre propre index en utilisant self.index. Ainsi, pour voir votre propre score total, vous utiliseriez scores[self.index]. Vous pouvez également accéder au end_scorejeu en utilisant self.end_score, mais vous pouvez supposer en toute sécurité qu'il sera de 40 pour ce défi.

Vous êtes autorisé à créer des fonctions d’aide dans votre classe. Vous pouvez également remplacer des fonctions existantes dans la Botclasse parente, par exemple si vous souhaitez ajouter d'autres propriétés de classe. Vous n'êtes pas autorisé à modifier l'état du jeu de quelque manière que ce Truesoit, sauf céder ou False.

Vous êtes libre de vous inspirer de ce message et de copier l'un des deux robots que j'ai inclus ici. Cependant, j'ai bien peur qu'ils ne soient pas particulièrement efficaces ...

En autorisant d'autres langues

Dans le bac à sable et sur le dix-neuvième octet, nous avons discuté de l’autorisation des soumissions dans d’autres langues. Après avoir pris connaissance de ces implémentations et entendu les arguments des deux côtés, j'ai décidé de limiter ce défi à Python uniquement. Cela est dû à deux facteurs: le temps nécessaire pour prendre en charge plusieurs langues et le caractère aléatoire de ce défi qui nécessite un grand nombre d'itérations pour atteindre la stabilité. J'espère que vous continuerez à participer et si vous voulez apprendre un peu de Python pour ce défi, je vais essayer d'être disponible dans le chat le plus souvent possible.

Pour toute question que vous pourriez avoir, vous pouvez écrire dans la salle de discussion pour ce défi . On se voit là-bas!

Règles

  • Le sabotage est autorisé et encouragé. C'est-à-dire sabotage contre d'autres joueurs
  • Toute tentative de modification du contrôleur, de l'exécution ou de toute autre soumission sera rejetée. Toutes les soumissions ne doivent fonctionner qu'avec les entrées et le stockage qui leur sont attribués.
  • Tout bot utilisant plus de 500 Mo de mémoire pour prendre sa décision sera disqualifié (si vous avez besoin de plus de mémoire, vous devriez repenser vos choix).
  • Un bot ne doit pas implémenter exactement la même stratégie qu'un existant, intentionnellement ou accidentellement.
  • Vous êtes autorisé à mettre à jour votre bot pendant la durée du défi. Cependant, vous pouvez également poster un autre bot si votre approche est différente.

Exemple

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

Ce bot va continuer jusqu'à ce qu'il ait un score d'au moins 10 pour le tour, ou il jette un 6. Notez que vous ne avez pas besoin de logique pour gérer lancer 6. Notez également que si votre premier jet est un 6, make_throwest jamais appelé, puisque votre tour est immédiatement terminé.

Pour ceux qui découvrent Python (et qui ne connaissent pas bien le yieldconcept), mais qui veulent essayer, le yieldmot clé ressemble à un retour, à certains égards, mais différent à d'autres. Vous pouvez lire sur le concept ici . En gros, une fois que yieldvotre fonction sera arrêtée, la valeur que vous yieldaurez modifiée sera renvoyée au contrôleur. Là, le contrôleur gère sa logique jusqu’à ce que votre bot prenne une autre décision. Ensuite, le contrôleur vous envoie le lancer de dés, et votre make_throwfonction continuera à s’exécuter exactement là où elle s’est arrêtée avant, essentiellement sur la ligne après la yielddéclaration précédente .

De cette façon, le contrôleur de jeu peut mettre à jour l'état sans nécessiter un appel de fonction bot distinct pour chaque lancer de dés.

spécification

Vous pouvez utiliser n’importe quelle bibliothèque Python disponible dans pip. Pour que je puisse obtenir une bonne moyenne, vous avez une limite de temps de 100 millisecondes par tour. Je serais vraiment heureux si votre script était bien plus rapide que ça, afin que je puisse faire plus de rondes.

Évaluation

Pour trouver le gagnant, je prendrai tous les robots et les ferai par groupes de 8. Si moins de 8 classes ont été soumises, je les ferai par groupes de 4 au hasard pour éviter d'avoir toujours tous les bots à chaque tour. Je vais faire des simulations pendant environ 8 heures, et le gagnant sera le bot avec le pourcentage de victoire le plus élevé. Je lancerai les dernières simulations au début de 2019, en vous donnant tout Noël pour coder vos robots! La date finale préliminaire est le 4 janvier, mais si le temps est compté, je peux le changer pour une date ultérieure.

En attendant, je vais essayer de faire une simulation quotidienne en utilisant 30 à 60 minutes de temps CPU et en mettant à jour le tableau de résultats. Ce ne sera pas le score officiel, mais il servira de guide pour voir quels bots sont les plus performants. Cependant, à l'approche de Noël, j'espère que vous comprendrez que je ne serai pas disponible à tout moment. Je ferai de mon mieux pour exécuter des simulations et répondre à toutes les questions relatives au défi.

Testez-le vous-même

Si vous souhaitez exécuter vos propres simulations, voici le code complet destiné au contrôleur qui exécute la simulation, y compris deux exemples de robots.

Manette

Voici le contrôleur mis à jour pour ce défi. Il supporte les sorties ANSI, le multi-threading et collecte des statistiques supplémentaires grâce à AKroell ! Lorsque je modifie le contrôleur, je mets à jour le message une fois la documentation terminée.

Grâce à BMO , le contrôleur est maintenant en mesure de télécharger tous les robots de cet article en utilisant le -ddrapeau. Les autres fonctionnalités ne sont pas modifiées dans cette version. Cela devrait vous assurer que toutes vos dernières modifications sont simulées dès que possible!

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

Si vous souhaitez accéder au contrôleur d'origine pour ce défi, il est disponible dans l'historique des modifications. Le nouveau contrôleur a exactement la même logique pour exécuter le jeu, la seule différence est la performance, la collecte de statistiques et une impression plus jolie.

Bots

Sur ma machine, les robots sont conservés dans le fichier forty_game_bots.py. Si vous utilisez un autre nom pour le fichier, vous devez mettre à jour l' importinstruction en haut du contrôleur.

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

Lancer la simulation

Pour exécuter une simulation, enregistrez les deux extraits de code publiés ci-dessus dans deux fichiers distincts. Je les ai sauvegardés au fur forty_game_controller.pyet à mesure forty_game_bots.py. Ensuite, vous utilisez simplement python forty_game_controller.pyou en python3 forty_game_controller.pyfonction de votre configuration Python. Suivez les instructions à partir de là si vous souhaitez configurer davantage votre simulation ou essayez de modifier le code si vous le souhaitez.

Statistiques du jeu

Si vous créez un bot qui vise un certain score sans prendre en compte les autres robots, voici les centiles de score gagnants:

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

Scores élevés

Comme plus de réponses sont postées, je vais essayer de garder cette liste à jour. Le contenu de la liste sera toujours de la dernière simulation. Les bots ThrowTwiceBotet GoToTenBotsont les bots du code ci-dessus, et sont utilisés comme référence. J'ai fait une simulation avec 10 ^ 8 jeux, ce qui a pris environ 1 heure. Ensuite, j'ai constaté que le jeu avait atteint la stabilité par rapport à mes manches de 10 ^ 7 parties. Cependant, comme les gens continuent à envoyer des bots, je ne ferai plus de simulations tant que la fréquence des réponses n’aura pas diminué.

J'essaie d'ajouter de nouveaux robots et d'ajouter les modifications que vous avez apportées aux robots existants. S'il semble que j'ai raté votre bot ou vos modifications, écrivez dans le chat et je m'assurerai d'avoir votre toute dernière version dans la prochaine simulation.

Nous avons maintenant plus de statistiques pour chaque bot grâce à AKroell ! Les trois nouvelles colonnes contiennent le score maximum pour tous les jeux, le score moyen par match et le score moyen lors de la victoire pour chaque bot.

Comme indiqué dans les commentaires, il y avait un problème avec la logique de jeu qui faisait que les bots ayant un indice plus élevé au sein d'un jeu obtenaient une partie supplémentaire dans certains cas. Ceci a été corrigé maintenant, et les scores ci-dessous reflètent cela.

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

Les bots suivants (sauf Rebel) sont faits pour se plier aux règles, et les créateurs ont accepté de ne pas participer au tournoi officiel. Cependant, je pense toujours que leurs idées sont créatives et qu’elles méritent une mention honorable. Rebel figure également sur cette liste car il utilise une stratégie astucieuse pour éviter le sabotage et fonctionne mieux avec le bot saboteur en jeu.

Les bots NeoBotet KwisatzHaderachsuivent les règles, mais utilisent une échappatoire en prédisant le générateur aléatoire. Comme ces robots nécessitent beaucoup de ressources pour simuler, j'ai ajouté ses statistiques à partir d'une simulation avec moins de jeux. Le bot HarkonnenBotréalise la victoire en désactivant tous les autres robots, ce qui est strictement contraire aux règles.

    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
Alors, peut-être que les règles seraient un peu plus claires s’ils disaient "quand un joueur termine son tour avec un score d’au moins 40, tous les autres auront un dernier tour". Cela évite le conflit apparent en faisant remarquer que ce n’est pas atteindre 40 qui déclenche réellement le dernier round, mais qu’il s’arrête à au moins 40.
aschepler

1
@aschepler c'est une bonne formulation, je vais éditer le post quand je suis sur mon ordinateur
maxb

2
@maxb J'ai étendu le contrôleur d'ajouter plus de stats qui étaient pertinentes à mon processus de développement: score le plus élevé atteint, le score moyen atteint et le score moyen gagnant gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell

2
Cela ressemble beaucoup à un jeu de dés très amusant appelé Farkled fr.wikipedia.org/wiki/Farkle
Caleb Jay

5
Je vote pour clore cette question car elle est déjà de facto fermée à de nouvelles réponses ("Le tournoi est maintenant terminé! La simulation finale s’est déroulée dans la nuit, sur un total de 3 ∗ 108 parties")
pppery le

Réponses:


6

OptFor2X

Ce bot suit une approximation de la stratégie optimale pour la version à deux joueurs de ce jeu, en utilisant uniquement son score et le score du meilleur adversaire. Au dernier tour, la version mise à jour prend en compte tous les scores.

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

Je vais examiner la mise en œuvre dès que possible. Avec les fêtes de Noël, il se peut que ce ne soit pas avant le 25
maxb

Votre bot est en tête! En outre, il n’est pas nécessaire de le faire fonctionner plus rapidement, il est à peu près aussi rapide que tous les autres robots à prendre des décisions.
maxb

Je ne voulais pas aller plus vite. J'ai déjà fait ce que je voulais faire - initialiser une seule fois -, mais je cherchais un moyen plus agréable de le faire, en particulier sans définir de fonctions en dehors de la classe. Je pense que c'est mieux maintenant.
Christian Sievers

Ça a l'air beaucoup mieux maintenant, bon travail!
maxb

Félicitations pour avoir obtenu les première et deuxième places!
Maxb

20

NeoBot

Au lieu de cela, essayez seulement de réaliser la vérité - il n'y a pas de cuillère

NeoBot jette un coup d'œil à la matrice (c'est-à-dire aléatoire) et prédit si le prochain résultat sera un 6 ou non.

NeoBot ne modifie pas réellement le contrôleur ou le moteur d'exécution, mais demande poliment à la bibliothèque de plus amples informations.

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
Bienvenue chez PPCG! C'est une réponse vraiment impressionnante. Lorsque je l'ai utilisé pour la première fois, j'étais ennuyé par le fait qu'il utilisait la même durée d'exécution que tous les autres robots combinés. Ensuite, j'ai regardé le pourcentage de victoire. Une manière vraiment intelligente de contourner les règles. Je permettrai à votre bot de participer au tournoi, mais j'espère que les autres s'abstiendront d'utiliser la même tactique que celle-ci, car cela enfreint l'esprit du jeu.
maxb

2
Étant donné l’écart énorme entre ce bot et la deuxième place et le fait que votre bot nécessite beaucoup d’informatique, accepteriez-vous que je lance une simulation avec moins d’itérations pour trouver votre taux de gain, puis que je lance le jeu officiel simulation sans votre bot?
maxb

3
Très bien, j’ai pensé que c’était probablement disqualifiable et certainement pas tout à fait dans l’esprit du match. Cela étant dit, c’était un plaisir de travailler et une excuse amusante de fouiller dans le code source de Python.
Généralement inoffensif

2
Merci! Je ne pense pas qu'aucun autre bot s'approche de votre score. Et si vous envisagez de mettre en œuvre cette stratégie, ne le faites pas. Cette stratégie va désormais à l’encontre des règles et NeoBot est le seul autorisé à l’utiliser pour préserver l’équité des tournois.
maxb

1
Eh bien, myBot bat tout le monde, mais c’est beaucoup mieux. Je pense que si je postais comme ça, j’obtiendrais -100 et pas le meilleur score.
Jan Ivan le

15

Essaim coopératif

Stratégie

Je pense que personne d'autre n'a encore remarqué la portée de cette règle:

Si le jeu se déroule à 200 tours, le bot (ou les bots) avec le score le plus élevé est le vainqueur, même s'il n'a pas 40 points ou plus.

Si chaque bot réussissait toujours jusqu'à ce qu'il soit éliminé, tout le monde aurait un score de zéro à la fin de la ronde 200 et tout le monde gagnerait! Ainsi, la stratégie de Cooperative Swarm est de coopérer tant que tous les joueurs ont un score de zéro, mais de jouer normalement si quelqu'un marque des points.

Dans ce post, je soumets deux robots: le premier est CooperativeSwarmBot et le second est CooperativeThrowTwice. CooperativeSwarmBot sert de classe de base pour tous les bots faisant officiellement partie de l'essaim coopératif, et a pour comportement réservé de simplement accepter son premier jet réussi lorsque la coopération échoue. CooperativeSwarmBot a pour coopérative CooperativeSwarmBot et lui est identique à tous les égards sauf que son comportement non coopératif consiste à faire deux lancers au lieu d'un. Dans les prochains jours, je réviserai ce message afin d'ajouter de nouveaux robots qui utilisent un comportement beaucoup plus intelligent en jouant contre des robots non coopératifs.

Code

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

Une analyse

Viabilité

Il est très difficile de coopérer dans ce jeu car nous avons besoin du soutien des huit joueurs pour que cela fonctionne. Étant donné que chaque classe de bot est limitée à une instance par match, il s'agit d'un objectif difficile à atteindre. Par exemple, les chances de choisir huit robots coopératifs parmi un pool de 100 robots coopératifs et de 30 robots non coopératifs sont:

100130991299812897127961269512594124931230.115

icn

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

i=8n=38

Étude de cas

Pour un certain nombre de raisons (voir les notes 1 et 2), un véritable essaim coopératif ne participera jamais aux jeux officiels. En tant que tel, je vais résumer les résultats d'une de mes propres simulations dans cette section.

Cette simulation a fonctionné 10000 jeux en utilisant les 38 autres bots qui avaient été postés ici la dernière fois que j'ai vérifié et 2900 bots qui avaient CooperativeSwarmBot comme classe parent. Le contrôleur a signalé que 9051 des 10 000 parties (90,51%) se sont terminées à 200 tours, ce qui est assez proche de la prédiction selon laquelle 90% des parties seraient coopératives. La mise en œuvre de ces robots était triviale; autres que CooperativeSwarmBot ils ont tous pris cette forme:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Moins de 3% des bots avaient un pourcentage de victoire inférieur à 80% et un peu plus de 11% des bots gagnaient à chaque match. Le pourcentage médian de victoire sur les 2 900 robots de l'essaim est d'environ 86%, ce qui est scandaleusement bon. À titre de comparaison, les meilleurs joueurs du classement officiel actuel gagnent moins de 22% de leurs jeux. Je ne peux pas adapter la liste complète de l'essaim coopératif à la longueur maximale autorisée pour une réponse. Si vous voulez voir que vous devrez aller ici à la place: https://pastebin.com/3Zc8m1Ex

Étant donné que chaque bot a participé à environ 27 parties en moyenne, la chance joue un rôle relativement important lorsque vous examinez les résultats obtenus pour des bots individuels. Comme je n'ai pas encore mis en place de stratégie avancée pour les jeux non coopératifs, la plupart des autres robots ont considérablement profité des avantages de leur confrontation avec l'essaim coopératif, atteignant même le taux de victoire médian de cet essaim coopératif de 86%.

Les résultats complets pour les bots qui ne font pas partie de l'essaim sont énumérés ci-dessous; Je pense que les résultats de deux robots méritent une attention particulière. Premièrement, StopBot n’a remporté aucune partie. Ceci est particulièrement tragique parce que l'essaim coopératif utilisait en fait exactement la même stratégie que StopBot; vous vous attendriez à ce que StopBot gagne huit de ses jeux par hasard, et un peu plus, car l'essaim de la coopérative est obligé de donner le premier coup à ses adversaires. Le deuxième résultat intéressant, cependant, est que le travail acharné de PointsAreForNerdsBot a finalement porté ses fruits: il a coopéré avec l'essaim et a réussi à gagner chaque match joué!

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

Défauts

Cette approche coopérative présente quelques inconvénients. Premièrement, lorsqu’ils jouent contre des bots non coopératifs, les bots coopératifs n’obtiennent jamais l’avantage du premier tour, car lorsqu’ils jouent les premiers, ils ne savent pas encore si leurs adversaires sont disposés à coopérer et n’ont donc pas le choix. score de zéro. De même, cette stratégie de coopération est extrêmement vulnérable à l’exploitation par des robots malveillants; par exemple, lors d'une partie coopérative, le bot qui joue en dernier dans la dernière manche peut choisir d'arrêter immédiatement de rouler pour faire perdre tout le monde (en supposant, bien sûr, que son premier lancer n'était pas un six).

En coopérant, tous les robots peuvent atteindre la solution optimale d'un taux de victoire de 100%. En tant que tel, si le taux de gain était la seule chose qui importait, la coopération constituerait un équilibre stable et il n'y aurait plus rien à craindre. Cependant, certains robots peuvent donner la priorité à d’autres objectifs, comme atteindre le sommet du classement. Cela signifie qu'un autre bot risque de faire défaut après votre dernier tour, ce qui vous incite à commencer par faire défaut. Parce que la configuration de cette compétition ne nous permet pas de voir ce que nos adversaires ont fait lors de leurs matchs précédents, nous ne pouvons pas pénaliser les individus qui ont fait défection. Ainsi, la coopération est finalement un équilibre instable voué à l'échec.

Notes de bas de page

[1]: Les principales raisons pour lesquelles je ne souhaite pas envoyer des milliers de robots au lieu de deux sont que cela ralentirait la simulation d'un facteur de l'ordre de 1000 [2], et que cela gâcherait considérablement gagner des pourcentages car les autres robots joueraient presque exclusivement contre l’essaim plutôt que les autres. Le plus important, toutefois, est le fait que même si je le voulais, je ne pourrais pas créer autant de robots dans un délai raisonnable sans enfreindre l’esprit de la règle selon laquelle "un bot ne doit pas mettre en œuvre la même stratégie existant, intentionnellement ou accidentellement ".

[2]: Je pense qu'il y a deux raisons principales pour lesquelles la simulation ralentit lors de l'exécution d'un essaim coopératif. Premièrement, plus de robots signifie plus de jeux si vous voulez que chaque bot joue dans le même nombre de jeux (dans l'étude de cas, le nombre de jeux différerait d'un facteur 77 environ). Deuxièmement, les jeux coopératifs prennent plus de temps, car ils durent 200 tours et, dans un tour, les joueurs doivent continuer à rouler indéfiniment. Pour ma configuration, la simulation des jeux prenait environ 40 fois plus de temps: l’étude de cas prenait un peu plus de trois minutes pour exécuter 10 000 parties, mais après la suppression de l’essaim coopératif, elle terminerait 10 000 parties en seulement 4,5 secondes. Entre ces deux raisons, j’estime qu’il faudrait environ 3100 fois plus de temps pour mesurer avec précision les performances des bots lorsqu’un essaim est en compétition par rapport à ce qu’il n’y en a pas.


4
Sensationnel. Et bienvenue à PPCG. C'est tout à fait la première réponse. Je n'avais pas vraiment prévu une telle situation. Vous avez certainement trouvé une faille dans les règles. Je ne sais pas trop comment noter cela, car votre réponse est une collection de robots plutôt qu'un seul. Cependant, la seule chose que je peux dire pour le moment, c'est qu'il est injuste qu'un participant contrôle 98,7% de tous les robots.
maxb

2
En fait, je ne veux pas que les robots en double fassent partie de la compétition officielle; C'est pourquoi j'ai exécuté la simulation moi-même au lieu de soumettre des milliers de robots presque identiques. Je vais réviser mon mémoire pour le rendre plus clair.
Einhaender

1
Si j'avais anticipé une réponse comme celle-ci, j'aurais changé les jeux à 200 tours pour qu'ils ne donnent pas de points aux joueurs. Cependant, comme vous le constatez, il existe une règle sur la création de bots identiques qui rendrait cette stratégie contraire aux règles. Je ne changerai pas les règles, ce serait injuste pour tous ceux qui ont créé un bot. Cependant, le concept de coopération est très intéressant et j'espère que d'autres robots soumis mettront en œuvre la stratégie de coopération en combinaison avec sa propre stratégie unique.
maxb

1
Je pense que votre message est clair après l'avoir lu de manière plus approfondie.
maxb

Combien de robots existants auraient besoin d'insérer leur code dans ce cadre de coopération pour qu'une majorité d'entre eux voient un gain net dans leur classement au classement? Ma conjecture naïve est de 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

Juste essayer avec tous GoToNBot, et 20, 22, 24 joue mieux. Je ne sais pas pourquoi.


Mise à jour: arrêtez toujours le lancer si vous marquez 40 ou plus.


J'ai aussi expérimenté ce genre de robots. Le score moyen le plus élevé par tour est atteint lorsque le bot passe à 16, mais je suppose que la "fin de partie" fait gagner plus de 20 bot.
Maxb

@maxb Non, 20 reste le meilleur sans le "jeu final" dans mon test. Peut-être l'avez-vous testé sur l'ancienne version du contrôleur.
Tsh

Avant de concevoir ce défi, j’avais effectué un test séparé, où j’avais calculé le score moyen par tour pour les deux tactiques de mon post ("lancer x fois" et "lancer jusqu’au score"), et le maximum que j’avais trouvé était de 15-16. . Bien que ma taille d'échantillon ait pu être trop petite, j'ai remarqué une instabilité.
maxb

2
J'ai fait quelques essais avec ceci, et ma conclusion est simplement que 20 fonctionne bien parce que c'est 40/2. Bien que je ne sois pas complètement sûr. Quand j'ai réglé end_scoresur 4000 (et que votre bot a été utilisé pour le targetcalcul), les 15-16 bots étaient bien meilleurs. Mais si le jeu consistait uniquement à augmenter votre score, ce serait trivial.
Maxb

1
@maxb Si end_scoreest 4000, il est presque impossible d'obtenir 4000 avant 200 tours. Et le jeu consiste simplement à savoir qui a obtenu le meilleur score en 200 tours. Et arrêter à 15 heures devrait fonctionner dans la mesure où, cette fois, la stratégie du score le plus élevé en un tour est la même que celle du score le plus élevé en 200 tours.
tsh

10

Rouleau Adaptatif

Début plus agressif et calme vers la fin du tour.
S'il croit gagner, roulez un temps supplémentaire pour la sécurité.

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 première soumission! Je vais le lancer contre les robots que j'ai écrits pour les tests, mais je mettrai à jour les meilleurs scores lorsque plus de robots auront été postés.
Maxb

J'ai couru quelques tests avec de légères modifications à votre bot. lim = max(min(self.end_score - scores[self.index], 24), 6)en augmentant le maximum à 24 et en ajoutant un minimum de 6, le pourcentage de gagnants augmente et le reste est combiné.
AKroell

@ AKroell: Cool! J'avais l'intention de faire quelque chose de similaire pour m'assurer que ça roule quelques fois à la fin, mais je n'ai pas encore pris le temps de le faire. Bizarrement, cela semble pire avec ces valeurs quand je fais 100k runs. J'ai seulement testé avec 18 bots cependant. Peut-être que je devrais faire des tests avec tous les robots.
Emigna

5

Alpha

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 refuse de ne jamais être inférieur à quiconque. Tant qu'il y a un bot avec un score plus élevé, il continuera à rouler.


À cause de la façon dont ça yieldmarche, si ça commence à rouler, ça n'arrêtera jamais. Vous aurez envie de mettre à jour my_scoredans la boucle.
Spitemaster

@Spitemaster Fixe, merci.
Mnémonique

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'idée est que les autres bots risquent de perdre des points, donc être 2e n'est pas mauvais, mais si vous êtes très en retard, vous pourriez aussi bien faire une faillite.


1
Bienvenue chez PPCG! Je regarde votre soumission, et il semble que plus il y a de joueurs dans le jeu, plus le pourcentage de victoire est bas pour votre bot. Je ne peux pas dire pourquoi tout de suite. Avec les bots correspondant 1vs1, vous obtenez un taux de victoire de 10%. L'idée semble prometteuse, et le code semble correct, donc je ne peux pas vraiment dire pourquoi votre taux de gain n'est pas plus élevé.
Maxb

6
Je l' ai regardé dans le comportement, et cette ligne me confondait: 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. Même si votre bot est en tête après 7 lancers, il continue jusqu'à atteindre un 6. Pendant que je tape ceci, j'ai compris le problème! Les scoresseuls contiennent les scores totaux, pas les cas de dés pour le tour en cours. Vous devriez le modifier pour être current_score = scores[self.index] + sum(self.current_throws).
maxb

Merci - va faire ce changement!
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

Nous voulons aller gros ou rentrer à la maison, non? GoHomeBot rentre chez lui pour la plupart. (Mais étonnamment bien!)


Comme ce bot va toujours pour 40 points, il n’aura jamais de points dans la scoresliste. Il y avait un bot comme celui-ci auparavant (le bot GoToEnd), mais David a supprimé leur réponse. Je vais remplacer ce bot par le vôtre.
Maxb

1
C'est assez amusant de voir les statistiques développées de ces bots: à l'exception des pointsAreForNerds et StopBot, ce bot a les points les plus bas, et pourtant, il a un bon ratio de
gains

5

EnsureLead

class EnsureLead(Bot):

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

EnsureLead emprunte des idées à GoTo20Bot. Il ajoute le concept selon lequel il considère toujours (en dernier ou à 40 ans) qu'il y en a d'autres qui auront au moins un jet de plus. Ainsi, le bot tente de les devancer, de sorte qu'ils doivent se rattraper.


4

Roll6TimesV2

Ne bat pas le meilleur actuel, mais je pense que ça ira mieux avec plus de robots en jeu.

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

Jeu vraiment génial au fait.


Bienvenue chez PPCG! Très impressionnant pour non seulement votre premier défi KotH, mais votre première réponse. Heureux que vous ayez aimé le jeu! J'ai eu beaucoup de discussions sur la meilleure tactique pour le jeu après la soirée quand je l'ai joué, donc cela semblait parfait pour un défi. Vous êtes actuellement à la troisième place sur 18.
maxb

4

StopBot

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

Littéralement, un seul lancer.

Ceci est équivalent à la Botclasse de base .


1
Ne sois pas désolé! Vous suivez toutes les règles, bien que je crains que votre bot ne soit pas très efficace avec une moyenne de 2,5 points par tour.
maxb

1
Je sais que quelqu'un a pourtant dû poster ce bot. Des robots dégénérés pour la perte.
Zacharý

5
Je dirais que je suis impressionné par le fait que votre bot ait obtenu exactement un gain dans la dernière simulation, prouvant que ce n'est pas complètement inutile.
Maxb

2
IL A GAGNÉ UN JEU?! C'est surprenant.
Zacharý

3

BringMyOwn_dice (BMO_d)

Ce bot aime les dés, il apporte 2 dés (qui semblent donner les meilleurs résultats). Avant de lancer les dés d'un tour, il lance ses propres 2 dés et calcule leur somme, c'est le nombre de lancers qu'il va effectuer, il ne jette que s'il n'a pas déjà 40 points.

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
Je pensais à un robot aléatoire utilisant une pièce de monnaie, mais c'est plus dans l'esprit du défi! Je pense que deux dés donnent les meilleurs résultats, puisque vous obtenez le plus de points par tour lorsque vous lancez le dé 5-6 fois, ce qui est proche du score moyen lorsque vous lancez deux dés.
maxb

3

FooBot

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

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

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

            yield True

        yield False

# Must throw at least onceest inutile - il lance une fois avant d'appeler votre bot. Votre bot lancera toujours un minimum de deux fois.
Spitemaster

Merci. J'ai été induit en erreur par le nom de la méthode.
Peter Taylor

@PeterTaylor Merci pour votre soumission! J'ai nommé la make_throwméthode très tôt, quand je voulais que les joueurs puissent passer leur tour. Je suppose qu'un nom plus approprié serait keep_throwing. Merci pour les commentaires dans le bac à sable, cela a vraiment contribué à faire de ce défi un vrai défi!
maxb

3

Go Big Early

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

Concept: essayer de gagner gros dès le début (arriver à 25) puis remonter de 2 rouleaux à la fois.


3

BinaryBot

Essaie de se rapprocher du score final afin que, dès que quelqu'un d'autre déclenche le dernier tour, il peut battre son score pour la victoire. La cible est toujours à mi-chemin entre le score actuel et le score final.

class BinaryBot(Bot):

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

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

        yield False

Intéressant, Hesitaterefuse également de franchir la ligne en premier. Vous devez entourer votre fonction de ces classchoses.
Christian Sievers

3

PointsAreForNerdsBot

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

Celui-ci n'a besoin d'aucune explication.

OneInFiveBot

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

Continue à rouler jusqu'à ce qu'il lance un 5 sur son propre dé à 5 faces. Cinq, c'est moins que six, alors, IL FAUT GAGNER!


2
Bienvenue chez PPCG! Je suis sûr que vous en êtes conscient, mais votre premier bot est littéralement le pire bot de cette compétition! C'est OneInFiveBotune bonne idée, mais je pense que cela finit par souffrir par rapport à certains des robots les plus avancés. Encore une belle soumission!
Maxb

2
l' OneInFiveBotest tout à fait intéressant dans la façon dont il a toujours le meilleur score global atteint.
AKroell

1
Merci de donner StopBotun sac de boxe: P. Le OneInFiveBot est en fait assez chouette, bon travail!
Zacharý

@ maxb Oui, c'est là que j'ai reçu le nom. Honnêtement, je n'ai pas testé OneInFiveBotet ça va beaucoup mieux que ce à
quoi


3

LizduadacBot

Essaie de gagner en 1 étape. La condition de fin est quelque peu arbitraire.

C'est aussi mon premier post (et je suis nouveau sur Python), donc si je battais "PointsAreForNerdsBot", je serais heureux!

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

Bienvenue sur PPCG (et bienvenue sur Python)! Vous auriez du mal à perdre contre PointsAreForNerdsBot, mais votre bot se débrouille plutôt bien. Je mettrai à jour le score plus tard ce soir ou demain, mais votre taux de victoire est d'environ 15%, ce qui est supérieur à la moyenne de 12,5%.
maxb

Par "temps dur", ils veulent dire que c'est impossible (sauf si j'ai très mal compris)
Zacharý

@maxb En fait, je ne pensais pas que le taux de victoire serait aussi élevé! (Je ne l'ai pas testé localement). Je me demande si changer le 50 pour être un peu plus haut / plus bas augmenterait le taux de victoire.
Lizduadac

3

SlowStart

Ce bot implémente l'algorithme TCP Slow Start. Il ajuste son nombre de jets ( nor ) en fonction de son tour précédent: s'il n'a pas obtenu un 6 au tour précédent, augmente le nor pour ce tour; alors qu'il ne réduit ni s'il l'a fait.

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

Bienvenue chez PPCG! Approche intéressante, je ne sais pas à quel point il est sensible aux fluctuations aléatoires. Deux choses sont nécessaires pour faire cette course: def updateValues():devrait être def updateValues(self):(ou def update_values(self):si vous voulez suivre PEP8). Deuxièmement, l'appel updateValues()devrait plutôt être self.updateValues()(ou self.update_vales()).
Maxb

2
De plus, je pense que vous devez mettre à jour votre ivariable dans la boucle while. En ce moment, votre bot passe la boucle while entièrement ou est bloqué dans la boucle tant jusqu'à ce qu'il
atteigne

Dans les meilleurs scores actuels, j'ai pris la liberté de mettre en œuvre ces changements. Je pense que vous pouvez expérimenter avec la valeur initiale self.noret voir comment cela affecte les performances de votre bot.
maxb

3

KwisatzHaderach

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

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

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

La prescience gagne généralement - mais le destin ne peut pas toujours être évité.
Grands et mystérieux sont les manières de Shai-Hulud!


Au tout début de ce défi (c’est-à-dire qu’il NeoBotétait posté avant ), j’écrivais un Oraclebot presque trivial :

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

mais ne l'a pas publié car je ne pensais pas que c'était assez intéressant;) Mais une fois NeoBotque j'ai pris les devants, j'ai commencé à réfléchir à la manière de battre sa capacité parfaite de prédire l'avenir. Alors, voici une citation de Dune; c'est lorsque Paul Atréides, le Kwisatz Haderach, se trouve à un point de rencontre duquel peut se dérouler une infinité d'avenir différents:

Il réalisa que la prescience était une illumination incorporant les limites de ce qu’elle révélait - à la fois source d’exactitude et d’erreur significative. Une sorte d'indétermination de Heisenberg est intervenue: la dépense d'énergie qui révélait ce qu'il voyait changeait ce qu'il voyait… univers connu. Il a vu la violence dont le résultat était soumis à tellement de variables que son moindre mouvement a provoqué de vastes changements dans les schémas.

La vision lui donna envie de sombrer dans l'immobilité, mais c'était aussi une action avec ses conséquences.

La réponse a donc été la suivante: prévoir l’avenir, c’est le changer. et si vous faites très attention, alors par action sélective ou par inaction, vous pouvez le changer de manière avantageuse - du moins la plupart du temps. Même les KwisatzHaderachne peuvent pas obtenir un taux de victoire de 100%!


Il semble que ce bot modifie l’état du générateur de nombres aléatoires afin d’éviter le roulement 6 ou au moins de l’anticiper. Il en va de même pour HarkonnenBot. Cependant, je note que le taux de victoire de ces robots est beaucoup plus élevé que celui de NeoBot. Manipulez-vous activement le générateur de nombres aléatoires afin de l'empêcher de lancer 6?
maxb

Oh, lors de ma première lecture, je n’avais pas remarqué que c’était non seulement meilleur NeoBotmais aussi meilleur! J'aime aussi la façon dont vous donnez un exemple de ce que tout ce qui utilise le caractère aléatoire (en particulier le contrôleur) devrait faire ici: utilisez votre propre random.Randomexemple. De même NeoBot, cela semble un peu sensible aux changements de détails d'implémentation non spécifiés du contrôleur.
Christian Sievers le

@maxb: HarkonnenBotne touche pas le RNG; il se fiche des nombres aléatoires. Il empoisonne tous les autres robots, puis avance le plus lentement possible jusqu'à la ligne d'arrivée. Comme beaucoup de délices culinaires, la vengeance est un plat à savourer lentement, après une préparation longue et délicate.
Dani O

@ChristianSievers: contrairement à NeoBot (et HarkonnenBot), ne KwisatzHaderachrepose que sur un détail de la mise en œuvre; en particulier, il n'a pas besoin de savoir comment random.random () est implémenté, mais seulement que le contrôleur l'utilise; D
Dani O

1
J'ai parcouru tous tes robots. J'ai décidé de traiter KwisatzHaderachet de HarkonnenBotla même manière que NeoBot. Ils recevront leurs scores d'une simulation avec moins de jeux et ne seront pas dans la simulation officielle. Cependant, ils finiront par figurer sur la liste des meilleurs scores NeoBot. La raison principale pour laquelle ils ne participent pas à la simulation officielle est qu'ils vont gâcher d'autres stratégies de bot. Pourtant. WisdomOfCrowdsdevrait être bien adapté à la participation, et je suis curieux de connaître les nouveaux changements que vous avez apportés à cela!
Maxb

2
class ThrowThriceBot(Bot):

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

Eh bien, celui-là est évident


J'ai fait quelques expériences avec cette classe de robots (c'était la tactique que j'ai utilisée lorsque j'ai joué au jeu pour la première fois). Je suis allé avec 4 lancers alors, bien que 5-6 aient un score moyen plus élevé par tour.
maxb

Aussi, félicitations pour votre première réponse à KotH!
Maxb

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

LastRound agit comme si c'était toujours le dernier tour et le dernier bot: il continue de rouler jusqu'à ce qu'il soit en tête. De plus, il ne veut pas se contenter de moins de 15 points à moins que ce soit le dernier tour ou qu'il atteigne 40 points.


Approche intéressante. Je pense que votre bot souffre s'il commence à prendre du retard. Étant donné que les chances d'obtenir plus de 30 points au cours d'une seule manche sont faibles, votre bot est plus susceptible de rester à son score actuel.
maxb

1
Je suppose que cela souffre de la même erreur que j'ai faite (voir les commentaires de NotTooFarBehindBot) - comme dans le dernier tour, si vous ne gagnez pas, vous continuerez à lancer jusqu'à ce que vous obteniez un 6 (les scores [self.index] ne sont jamais mis à jour) En fait - Avez-vous cette inégalité dans le mauvais sens? max (scores) sera toujours> = scores [self.index]
Stuart Moore

@ StuartMoore Haha, oui, je pense que vous avez raison. Merci!
Spitemaster

Je pense que vous voulez "et last_round" le 2ème temps pour faire ce que vous voulez - sinon, le second temps sera utilisé, que le dernier_round soit vrai ou non
Stuart Moore

3
C'est intentionnel. Il essaie toujours d'être en tête lorsqu'il termine son tour.
Spitemaster

2

QuotaBot

Un système de "quota" naïf que j’ai mis en place et qui a semblé en réalité avoir un score assez élevé dans l’ensemble.

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:donne une erreur pour moi. Aussiwhile sum(self.current_throws)
Spitemaster

@Spitemaster était une erreur de collage dans l'échange de pile, devrait fonctionner maintenant.
FlipTack

@Spitemaster c'est parce qu'il y avait <et des >symboles qui interfèrent avec les <pre>balises que j'utilisais
FlipTack

2

AttentesBot

Il suffit de jouer droit, calcule la valeur attendue pour le lancer de dés et ne le fait que si c'est positif.

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

J'avais du mal à exécuter le contrôleur, j'ai un "NameError: le nom 'bots_per_game' n'est pas défini" sur le multithread, donc vraiment aucune idée de la façon dont cela fonctionne.


1
Je pense que cela finit par être équivalent à un bot "Go to 16", mais nous n'en avons pas encore
Stuart Moore

1
@StuartMoore That ... est un très vrai point, oui
Cain

J'ai rencontré vos problèmes avec le contrôleur lorsque je l'ai exécuté sur mon ordinateur Windows. D'une manière ou d'une autre, cela s'est bien passé sur ma machine Linux. Je mets à jour le contrôleur, et mettra à jour le post une fois que c'est fait.
maxb

@maxb Merci, probablement quelque chose sur les variables qui sont disponibles dans le processus différent. FYI a également mis à jour cela, j'ai fait une erreur idiote de céder: /
Cain

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

Quarante ans

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

Essayez de marquer 14 points jusqu’à la dernière ronde, puis supposez que tous les autres vont essayer d’obtenir 14 points et essayer d’atteindre ce score.


Je suis TypeError: unsupported operand type(s) for -: 'list' and 'int'avec votre bot.
tsh

Je suppose que votre max_projected_score devriez être le maximum de la liste plutôt que la liste entière, est-ce que j'ai raison? Sinon, j'obtiens le même problème que tsh.
Maxb

Oups, édité pour réparer.
histocrat

2

Hésiter

Fait deux pas modestes, puis attend que quelqu'un d'autre franchisse la ligne. La version mise à jour n'essaie plus de battre le record, elle veut seulement l'atteindre - améliorer les performances en supprimant deux octets du code source!

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

Rebelle

Ce bot combine la stratégie simple de la stratégie Hesitate avancée du dernier tour BotFor2X, tente de se rappeler de qui il s'agit et se déchaîne lorsqu'il découvre qu'il vit dans une illusion.

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)

Eh bien, c'est assez élégant :) En outre, félicitations pour avoir obtenu les première et deuxième places dans la compétition principale!
Dani O

Naturellement, j'ai peaufiné HarkonnenBotpour que Rebelcela ne puisse plus s'empoisonner;) et j'ai aussi peaufiné TleilaxuBotpour que Rebelcela ne le détecte plus!
Dani O

1

Cinquième prise

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 moitié du temps, nous allons lancer un 5 avant un 6. Lorsque nous le faisons, encaisser.


Si nous nous arrêtons à 1 à la place, les progrès seront plus lents, mais il est plus probable d’atteindre 40 en un seul bond.
Mnémonique

Lors de mes tests, TakeOne a obtenu 20,868 points par tour, comparé aux 24,262 points par tour de TakeFive (et a également porté le taux de gain de 0,291 à 0,259). Donc, je ne pense pas que ça en vaut la peine.
Spitemaster

1

Chaser

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

Si le dernier tour est terminé, il tente désespérément d'atteindre au moins 50 points. Juste pour faire bonne mesure, il lance au moins quatre fois, peu importe le résultat.

[modifier 1: ajouté stratégie go-for-gold au dernier tour]

[edit 2: logique mise à jour parce que je pensais à tort qu'un bot marquerait à 40 plutôt que seulement le score de bot le plus élevé]

[edit 3: fait un peu plus défensif au jeu final]


Bienvenue chez PPCG! Bonne idée non seulement d'essayer de se rattraper, mais aussi de passer la première place. Je lance une simulation en ce moment et je vous souhaite bonne chance!
maxb

Merci! Au départ, j’ai essayé de surpasser l’ancien leader d’un montant fixe (valeurs essayées entre 6 et 20), mais il s’avère que nous avons deux fois plus de salons en mieux.
AKroell

@ JonathanFrech merci, corrigé
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

Une paire de robots, ils apportent leurs propres jeux de dés et les lancent pour prédire l'avenir. Si un joueur est un 6, il s'arrête, FutureBot ne peut pas se rappeler lequel de ses 2 dés était destiné au prochain lancer, il abandonne donc.

Je me demande qui fera mieux.

OneStepAhead est un peu trop similaire à OneInFive à mon goût, mais je veux également voir comment il se compare à FutureBot et OneInFive.

Edit: Maintenant, ils s'arrêtent après avoir frappé 45


Bienvenue chez PPCG! Votre bot joue définitivement avec l'esprit du jeu! Je vais faire une simulation plus tard ce soir.
maxb

Merci! Je suis curieux de savoir si cela va bien, mais je suppose que ce sera plutôt faible.
william porter
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.