Ein Würfelspiel, aber vermeiden Sie Nummer 6 [geschlossen]


58

Turnier vorbei!

Das Turnier ist jetzt vorbei! Die letzte Simulation wurde während der Nacht durchgeführt, insgesamt Spiele. Der Gewinner ist Christian Sievers mit seinem Bot OptFor2X . Auch Christian Sievers konnte sich mit Rebel den zweiten Platz sichern . Herzliche Glückwünsche! Unten sehen Sie die offizielle Highscore-Liste für das Turnier.3108

Wenn Sie das Spiel dennoch spielen möchten, können Sie gerne den unten angegebenen Controller verwenden und den Code darin verwenden, um Ihr eigenes Spiel zu erstellen.

Würfel

Ich wurde eingeladen, ein Würfelspiel zu spielen, von dem ich noch nie gehört hatte. Die Regeln waren einfach, aber ich denke, es wäre perfekt für eine KotH-Herausforderung.

Die Regeln

Der Beginn des Spiels

Der Würfel geht um den Tisch und jedes Mal, wenn Sie an der Reihe sind, können Sie den Würfel so oft werfen, wie Sie möchten. Sie müssen es jedoch mindestens einmal werfen. Sie behalten die Summe aller Würfe für Ihre Runde im Auge. Wenn Sie aufhören möchten, wird die Punktzahl für die Runde zu Ihrer Gesamtpunktzahl hinzugefügt.

Warum würdest du jemals aufhören, den Würfel zu werfen? Denn wenn Sie 6 bekommen, wird Ihre Punktzahl für die gesamte Runde Null und der Würfel wird weitergereicht. Daher ist das ursprüngliche Ziel, Ihre Punktzahl so schnell wie möglich zu erhöhen.

Wer ist der Gewinner?

Wenn der erste Spieler am Tisch 40 Punkte oder mehr erreicht, beginnt die letzte Runde. Sobald die letzte Runde begonnen hat, erhält jeder außer der Person, die die letzte Runde eingeleitet hat, eine weitere Runde.

Die Regeln für die letzte Runde sind die gleichen wie für jede andere Runde. Sie entscheiden sich dafür, weiter zu werfen oder aufzuhören. Sie wissen jedoch, dass Sie keine Gewinnchance haben, wenn Sie in der letzten Runde keine höhere Punktzahl erzielen als vor Ihnen. Aber wenn Sie zu weit gehen, erhalten Sie möglicherweise eine 6.

Es gibt jedoch noch eine weitere Regel, die berücksichtigt werden muss. Wenn Ihre aktuelle Gesamtpunktzahl (Ihre vorherige Punktzahl + Ihre aktuelle Punktzahl für die Runde) 40 oder mehr beträgt und Sie eine 6 treffen, wird Ihre Gesamtpunktzahl auf 0 gesetzt. Das bedeutet, dass Sie von vorne beginnen müssen. Wenn Sie eine 6 erreichen, wenn Ihre aktuelle Gesamtpunktzahl 40 oder mehr beträgt, wird das Spiel normal fortgesetzt, mit der Ausnahme, dass Sie jetzt auf dem letzten Platz sind. Die letzte Runde wird nicht ausgelöst, wenn Ihre Gesamtpunktzahl zurückgesetzt wird. Sie könnten die Runde noch gewinnen, aber es wird schwieriger.

Der Gewinner ist der Spieler mit der höchsten Punktzahl, wenn die letzte Runde vorbei ist. Wenn zwei oder mehr Spieler die gleiche Punktzahl haben, werden sie alle als Sieger gewertet.

Eine zusätzliche Regel ist, dass das Spiel maximal 200 Runden lang fortgesetzt wird. Dies soll verhindern, dass mehrere Bots grundsätzlich so lange werfen, bis sie 6 treffen, um auf ihrem aktuellen Stand zu bleiben. Sobald die 199. Runde vorbei ist, last_roundwird auf true gesetzt und eine weitere Runde gespielt. Wenn das Spiel bis zu 200 Runden dauert, gewinnt der Bot (oder die Bots) mit der höchsten Punktzahl, auch wenn sie nicht über 40 Punkte oder mehr verfügen.

Rekapitulieren

  • In jeder Runde wirfst du den Würfel, bis du aufhörst oder eine 6 erhältst
  • Du musst den Würfel einmal werfen (wenn dein erster Wurf eine 6 ist, ist deine Runde sofort vorbei)
  • Wenn Sie eine 6 erhalten, wird Ihre aktuelle Punktzahl auf 0 gesetzt (nicht Ihre Gesamtpunktzahl).
  • Sie addieren Ihre aktuelle Punktzahl nach jeder Runde zu Ihrer Gesamtpunktzahl
  • Wenn ein Bot seinen Zug beendet und eine Gesamtpunktzahl von mindestens 40 erreicht, erhält jeder andere eine letzte Runde
  • Wenn Ihre aktuelle Gesamtpunktzahl und Sie eine 6 erhalten, wird Ihre Gesamtpunktzahl auf 0 gesetzt und Ihre Runde ist beendet40
  • Die letzte Runde wird nicht ausgelöst, wenn die oben genannten Ereignisse eintreten
  • Die Person mit der höchsten Gesamtpunktzahl nach der letzten Runde ist der Gewinner
  • Falls es mehrere Gewinner gibt, werden alle als Gewinner gewertet
  • Das Spiel dauert maximal 200 Runden

Klärung der Partituren

  • Gesamtpunktzahl: Die Punktzahl, die Sie in früheren Runden gespeichert haben
  • Aktuelle Punktzahl: Die Punktzahl für die aktuelle Runde
  • Aktuelle Gesamtpunktzahl: Die Summe der beiden obigen Punkte

Wie nimmst du teil?

Um an dieser KotH-Herausforderung teilzunehmen, sollten Sie eine Python-Klasse schreiben, von der erbt Bot. Sie sollten die Funktion implementieren: make_throw(self, scores, last_round). Diese Funktion wird aufgerufen, sobald Sie an der Reihe sind und Ihr erster Wurf keine 6 war. Um weiter zu werfen, sollten Sie yield True. Um mit dem Werfen aufzuhören, solltest du yield False. Nach jedem Wurf wird die übergeordnete Funktion update_stateaufgerufen. Somit haben Sie über die Variable Zugriff auf Ihre Würfe für die aktuelle Runde self.current_throws. Sie haben auch Zugriff auf Ihren eigenen Index mit self.index. So, um Ihre eigene Gesamtpunktzahl zu sehen, würden Sie verwenden scores[self.index]. Sie können auch end_scoremithilfe von auf das für das Spiel zugreifen self.end_score, aber Sie können davon ausgehen, dass es für diese Herausforderung 40 sein wird.

Sie dürfen Hilfsfunktionen in Ihrer Klasse erstellen. Sie können auch in der Botübergeordneten Klasse vorhandene Funktionen überschreiben , z. B. wenn Sie weitere Klasseneigenschaften hinzufügen möchten. Sie dürfen den Status des Spiels in keiner Weise ändern, außer in Bezug auf "Nachgeben" Trueoder " Nachgeben" False.

Es steht Ihnen frei, sich von diesem Beitrag inspirieren zu lassen und einen der beiden Bots zu kopieren, die ich hier aufgenommen habe. Ich fürchte jedoch, dass sie nicht besonders effektiv sind ...

Zum Zulassen anderer Sprachen

Sowohl in der Sandbox als auch in The Nineteenth Byte haben wir Diskussionen darüber geführt, ob Beiträge in anderen Sprachen eingereicht werden dürfen. Nachdem ich über solche Implementierungen gelesen und Argumente von beiden Seiten gehört habe, habe ich beschlossen, diese Herausforderung nur auf Python zu beschränken. Dies ist auf zwei Faktoren zurückzuführen: die für die Unterstützung mehrerer Sprachen erforderliche Zeit und die Zufälligkeit dieser Herausforderung, die eine hohe Anzahl von Iterationen erfordert, um Stabilität zu erreichen. Ich hoffe, dass Sie weiterhin teilnehmen und wenn Sie Python für diese Herausforderung lernen möchten, werde ich versuchen, so oft wie möglich im Chat verfügbar zu sein.

Bei Fragen, die Sie möglicherweise haben, können Sie im Chat-Raum für diese Herausforderung schreiben . Wir sehen uns dort!

Regeln

  • Sabotage ist erlaubt und wird empfohlen. Das heißt, Sabotage gegen andere Spieler
  • Jeder Versuch, mit dem Controller, der Laufzeit oder anderen Einsendungen zu basteln, wird disqualifiziert. Alle Einsendungen sollten nur mit den angegebenen Eingaben und Speichern funktionieren.
  • Jeder Bot, der mehr als 500 MB Speicher benötigt, um seine Entscheidung zu treffen, wird disqualifiziert (wenn Sie so viel Speicher benötigen, sollten Sie Ihre Auswahl überdenken).
  • Ein Bot darf nicht absichtlich oder versehentlich die exakt gleiche Strategie wie ein vorhandener implementieren.
  • Sie dürfen Ihren Bot während der Dauer der Challenge aktualisieren. Sie können jedoch auch einen anderen Bot posten, wenn Ihre Herangehensweise anders ist.

Beispiel

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

Dieser Bot wird so lange weitermachen, bis er mindestens 10 Punkte für die Runde erzielt hat oder eine 6 auslöst. Beachten Sie, dass Sie keine Logik mehr benötigen, um 6 zu werfen. Beachten Sie auch, dass, wenn Ihr erster Wurf eine 6 make_throwist, nie angerufen, da deine Runde sofort vorbei ist.

Für diejenigen, die neu in Python (und neu im yieldKonzept) sind, dies aber ausprobieren möchten, yieldähnelt das Schlüsselwort in gewisser Weise einer Rendite, in anderer Hinsicht unterscheidet es sich jedoch. Über das Konzept können Sie hier lesen . Grundsätzlich yieldstoppt Ihre Funktion , sobald Sie dies tun, und der von Ihnen yieldeingegebene Wert wird an die Steuerung zurückgesendet. Dort verarbeitet der Controller seine Logik, bis es Zeit für Ihren Bot ist, eine andere Entscheidung zu treffen. Dann sendet der Controller Ihnen den Würfelwurf und Ihre make_throwFunktion wird genau dort ausgeführt, wo sie zuvor gestoppt wurde, im Grunde in der Zeile nach der vorherigen yieldAnweisung.

Auf diese Weise kann der Gamecontroller den Status aktualisieren, ohne dass für jeden Würfelwurf ein separater Bot-Funktionsaufruf erforderlich ist.

Spezifikation

Sie können jede Python-Bibliothek verwenden, die in verfügbar ist pip. Um sicherzustellen, dass ich einen guten Durchschnitt erhalten kann, haben Sie ein Zeitlimit von 100 Millisekunden pro Runde. Ich würde mich sehr freuen, wenn Ihr Skript viel schneller wäre, damit ich mehr Runden drehen kann.

Auswertung

Um den Gewinner zu finden, nehme ich alle Bots und starte sie in zufälligen 8er-Gruppen. Wenn weniger als 8 Klassen eingereicht werden, starte ich sie in zufälligen 4er-Gruppen, um zu vermeiden, dass immer alle Bots in jeder Runde sind. Ich werde ungefähr 8 Stunden lang Simulationen durchführen, und der Gewinner wird der Bot mit dem höchsten Gewinnanteil sein. Ich starte die letzten Simulationen Anfang 2019 und gebe dir Weihnachten, um deine Bots zu programmieren! Das vorläufige Enddatum ist der 4. Januar, aber wenn das zu wenig Zeit ist, kann ich es auf ein späteres Datum umstellen.

Bis dahin werde ich versuchen, eine tägliche Simulation mit 30-60 Minuten CPU-Zeit durchzuführen und die Anzeigetafel zu aktualisieren. Dies wird nicht die offizielle Punktzahl sein, aber es wird als Leitfaden dienen, um zu sehen, welche Bots die beste Leistung erbringen. Ich hoffe jedoch, dass Sie mit Weihnachten verstehen können, dass ich nicht immer verfügbar sein werde. Ich werde mein Bestes geben, um Simulationen durchzuführen und alle Fragen im Zusammenhang mit der Herausforderung zu beantworten.

Testen Sie es selbst

Wenn Sie Ihre eigenen Simulationen ausführen möchten, finden Sie hier den vollständigen Code des Controllers, der die Simulation ausführt, einschließlich zweier Beispiel-Bots.

Regler

Hier ist der aktualisierte Controller für diese Herausforderung. Es unterstützt ANSI-Ausgänge, Multithreading und sammelt dank AKroell zusätzliche Statistiken ! Wenn ich Änderungen am Controller vornehme, aktualisiere ich den Post, sobald die Dokumentation vollständig ist.

Dank BMO kann der Controller nun alle Bots von diesem Beitrag mit der -dFlagge herunterladen . Andere Funktionen bleiben in dieser Version unverändert. Dies sollte sicherstellen, dass alle Ihre letzten Änderungen so schnell wie möglich simuliert werden!

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

Wenn Sie für diese Herausforderung auf den ursprünglichen Controller zugreifen möchten, ist dieser im Bearbeitungsverlauf verfügbar. Der neue Controller verfügt über genau die gleiche Logik für die Ausführung des Spiels. Der einzige Unterschied besteht in der Leistung, der Erfassung von Statistiken und dem hübscheren Drucken.

Bots

Auf meinem Computer werden die Bots in der Datei gespeichert forty_game_bots.py. Wenn Sie einen anderen Namen für die Datei verwenden, müssen Sie die importAnweisung oben auf dem Controller aktualisieren .

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

Simulation ausführen

Speichern Sie zum Ausführen einer Simulation beide oben veröffentlichten Codefragmente in zwei separaten Dateien. Ich habe sie als forty_game_controller.pyund gespeichert forty_game_bots.py. Dann benutzt du einfach python forty_game_controller.pyoder python3 forty_game_controller.pyabhängig von deiner Python-Konfiguration. Befolgen Sie die Anweisungen von dort, wenn Sie Ihre Simulation weiter konfigurieren möchten, oder versuchen Sie, den Code zu basteln, wenn Sie möchten.

Spielstatistik

Wenn Sie einen Bot erstellen, der auf eine bestimmte Punktzahl abzielt, ohne andere Bots zu berücksichtigen, sind dies die Perzentile der Siegpunktzahl:

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

Highscores

Wenn weitere Antworten veröffentlicht werden, werde ich versuchen, diese Liste auf dem neuesten Stand zu halten. Der Inhalt der Liste ist immer von der neuesten Simulation. Die Bots ThrowTwiceBotund GoToTenBotsind die Bots aus dem obigen Code und werden als Referenz verwendet. Ich habe eine Simulation mit 10 ^ 8 Spielen gemacht, die ungefähr 1 Stunde gedauert hat. Dann sah ich, dass das Spiel im Vergleich zu meinen Läufen mit 10 ^ 7 Spielen Stabilität erreichte. Da die Leute immer noch Bots posten, werde ich keine Simulationen mehr durchführen, bis die Häufigkeit der Antworten gesunken ist.

Ich versuche, alle neuen Bots hinzuzufügen und alle Änderungen, die Sie an vorhandenen Bots vorgenommen haben, hinzuzufügen. Wenn es den Anschein hat, dass ich Ihren Bot verpasst habe oder Sie neue Änderungen vorgenommen haben, schreiben Sie in den Chat und ich werde sicherstellen, dass Ihre neueste Version in der nächsten Simulation verfügbar ist.

Dank AKroell haben wir jetzt mehr Statistiken für jeden Bot ! Die drei neuen Spalten enthalten die maximale Punktzahl für alle Spiele, die durchschnittliche Punktzahl pro Spiel und die durchschnittliche Punktzahl beim Gewinnen für jeden Bot.

Wie in den Kommentaren erwähnt, gab es ein Problem mit der Spiellogik, bei dem Bots mit einem höheren Index innerhalb eines Spiels in einigen Fällen eine Extrarunde erhielten. Dies wurde jetzt behoben, und die unten stehenden Scores spiegeln dies wider.

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

Die folgenden Bots (außer Rebel) dienen dazu, die Regeln zu biegen, und die Macher haben zugestimmt, nicht am offiziellen Turnier teilzunehmen. Ich denke jedoch immer noch, dass ihre Ideen kreativ sind und eine lobende Erwähnung verdienen. Rebel ist auch auf dieser Liste, weil es eine clevere Strategie einsetzt, um Sabotage zu vermeiden, und mit dem Sabotage-Bot, der gerade im Spiel ist, tatsächlich eine bessere Leistung erzielt.

Die Bots NeoBotund KwisatzHaderachfolgen den Regeln, nutzen aber eine Lücke, indem sie den Zufallsgenerator vorhersagen. Da diese Bots eine Menge Ressourcen zum Simulieren benötigen, habe ich ihre Statistiken aus einer Simulation mit weniger Spielen hinzugefügt. Der Bot HarkonnenBotgewinnt, indem er alle anderen Bots deaktiviert, was streng gegen die Regeln verstößt.

    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
Vielleicht wären die Regeln etwas klarer, wenn sie sagten: "Wenn ein Spieler seinen Zug mit einer Punktzahl von mindestens 40 beendet, erhält jeder andere eine letzte Runde." Dies vermeidet den offensichtlichen Konflikt, indem es darauf hinweist, dass es nicht 40 ist, was die letzte Runde auslöst. Es
endet

1
@taschepler das ist eine gute Formulierung, ich werde den Beitrag bearbeiten, wenn ich auf meinem Computer bin
maxb

2
@maxb Ich habe den Controller erweitert , um mehr Statistiken hinzufügen , die zu meinen Entwicklungsprozess relevant waren: höchste Punktzahl erreicht, durchschnittliche Punktzahl erreicht und durchschnittliche Gewinn Punktzahl gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell

2
Das klingt sehr ähnlich wie ein sehr lustiges Würfelspiel namens Farkled en.wikipedia.org/wiki/Farkle
Caleb Jay

5
Ich stimme dafür, diese Frage zu schließen, da sie bereits de facto für neue Antworten geschlossen ist ("Das Turnier ist jetzt vorbei! Die letzte Simulation wurde in der Nacht durchgeführt, insgesamt 3
×

Antworten:


6

OptFor2X

Dieser Bot folgt einer Annäherung an die optimale Strategie für die Zwei-Spieler-Version dieses Spiels, wobei nur der Score und der Score des besten Gegners verwendet werden. In der letzten Runde werden in der aktualisierten Version alle Ergebnisse berücksichtigt.

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

Ich werde die Implementierung durchsehen, sobald ich kann. Mit Weihnachtsfeiern kann es sein, dass es nicht bis zum 25.
23.

Ihr Bot ist an der Spitze! Außerdem muss es nicht schneller ausgeführt werden. Es ist ungefähr so ​​schnell wie alle anderen Bots, wenn es darum geht, Entscheidungen zu treffen.
Maxb

Ich wollte es nicht schneller machen. Ich habe bereits getan, was ich tun wollte - nur einmal initialisieren -, aber ich habe nach einer besseren Möglichkeit gesucht, dies zu tun, insbesondere ohne Funktionen außerhalb der Klasse zu definieren. Ich denke es ist jetzt besser.
Christian Sievers

Es sieht jetzt viel besser aus, gute Arbeit!
Maxb

Herzlichen Glückwunsch zum ersten und zweiten Platz!
Maxb

20

NeoBot

Versuchen Sie stattdessen nur, die Wahrheit zu erkennen - es gibt keinen Löffel

NeoBot wirft einen Blick in die Matrix (auch bekannt als zufällig) und sagt voraus, ob der nächste Wurf eine 6 ist oder nicht - er kann nichts dagegen tun, wenn er eine 6 erhält, ist aber mehr als glücklich, einem Streak-Ender auszuweichen.

NeoBot ändert weder den Controller noch die Laufzeit, sondern bittet die Bibliothek höflich um weitere Informationen.

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
Willkommen bei PPCG! Dies ist eine wirklich beeindruckende Antwort. Als ich es zum ersten Mal ausführte, störte mich die Tatsache, dass es dieselbe Laufzeit beanspruchte wie alle anderen Bots zusammen. Dann habe ich mir den Gewinnanteil angesehen. Wirklich clevere Art, die Regeln zu umgehen. Ich werde Ihrem Bot erlauben, am Turnier teilzunehmen, aber ich hoffe, dass andere nicht die gleiche Taktik anwenden, da dies den Geist des Spiels verletzt.
Maxb

2
Da es eine so große Lücke zwischen diesem Bot und dem zweiten Platz gibt, in Verbindung mit der Tatsache, dass Ihr Bot viel Computer benötigt, würden Sie akzeptieren, dass ich eine Simulation mit weniger Iterationen durchführe, um Ihre Gewinnrate zu ermitteln, und dann den Offiziellen Simulation ohne Ihren Bot?
Maxb

3
Gut, ich dachte mir, dass dies wahrscheinlich disqualifizierbar und definitiv nicht ganz im Sinne des Spiels ist. Abgesehen davon war es ein Riesenspaß, sich an die Arbeit zu machen und eine lustige Ausrede, um im Python-Quellcode herumzustöbern.
Meistens harmlos

2
Vielen Dank! Ich glaube nicht, dass ein anderer Bot Ihrer Punktzahl nahe kommt. Und für alle anderen, die darüber nachdenken, diese Strategie umzusetzen, gilt dies nicht. Von nun an verstößt diese Strategie gegen die Regeln, und NeoBot ist der einzige, der sie verwenden darf, um das Turnier fair zu halten.
Maxb

1
Nun, myBot schlägt jeden, aber das ist viel zu viel besser - ich denke, wenn ich einen Bot wie diesen posten würde, würde ich -100 bekommen und nicht die beste Punktzahl.
Jan Ivan

15

Kooperativer Schwarm

Strategie

Ich glaube noch niemandem ist die Bedeutung dieser Regel aufgefallen:

Wenn das Spiel bis zu 200 Runden dauert, gewinnt der Bot (oder die Bots) mit der höchsten Punktzahl, auch wenn sie nicht über 40 Punkte oder mehr verfügen.

Wenn jeder Bot immer rollte, bis er pleite war, hätte jeder am Ende der 200. Runde eine Punktzahl von Null und jeder würde gewinnen! Daher besteht die Strategie des Genossenschaftsschwarms darin, zusammenzuarbeiten, solange alle Spieler eine Punktzahl von Null haben, aber normal zu spielen, wenn jemand Punkte erzielt.

In diesem Beitrag reiche ich zwei Bots ein: Der erste ist CooperativeSwarmBot und der zweite ist CooperativeThrowTwice. CooperativeSwarmBot dient als Basisklasse für alle Bots, die formell Teil des kooperativen Schwarms sind, und hat das Platzhalter-Verhalten, einfach den ersten erfolgreichen Wurf zu akzeptieren, wenn die Kooperation fehlschlägt. CooperativeSwarmBot hat CooperativeSwarmBot als übergeordnetes Element und ist in jeder Hinsicht identisch mit CooperativeSwarmBot, mit der Ausnahme, dass es nicht kooperativ ist, zwei Würfe anstatt eines zu machen. In den nächsten Tagen werde ich diesen Beitrag überarbeiten, um neue Bots hinzuzufügen, die viel intelligenteres Verhalten beim Spielen gegen nicht kooperative Bots verwenden.

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

Analyse

Lebensfähigkeit

Es ist sehr schwierig, in diesem Spiel zusammenzuarbeiten, da wir die Unterstützung aller acht Spieler benötigen, damit es funktioniert. Da jede Bot-Klasse auf eine Instanz pro Spiel beschränkt ist, ist dies ein schwieriges Ziel. Die Wahrscheinlichkeit, acht kooperative Bots aus einem Pool von 100 kooperativen Bots und 30 nicht kooperativen Bots auszuwählen, beträgt beispielsweise:

100130991299812897127961269512594124931230.115

icn

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

i=8n=38

Fallstudie

Aus einer Reihe von Gründen (siehe Fußnoten 1 und 2) wird ein ordnungsgemäßer kooperativer Schwarm niemals an den offiziellen Spielen teilnehmen. In diesem Abschnitt fasse ich die Ergebnisse einer meiner eigenen Simulationen zusammen.

Diese Simulation lief 10000 Spiele mit den 38 anderen Bots, die hier das letzte Mal gepostet wurden, und 2900 Bots, die CooperativeSwarmBot als Elternklasse hatten. Der Prüfer berichtete, dass 9051 der 10000 Spiele (90,51%) nach 200 Runden endeten, was ziemlich nahe an der Vorhersage liegt, dass 90% der Spiele kooperativ sein würden. Die Implementierung dieser Bots war trivial. außer CooperativeSwarmBot hatten alle diese Form:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Weniger als 3% der Bots hatten einen Gewinnanteil, der unter 80% lag, und etwas mehr als 11% der Bots gewannen jedes einzelne Spiel, das sie spielten. Der mittlere Gewinnprozentsatz der 2900 Bots im Schwarm liegt bei 86%, was unverschämt gut ist. Zum Vergleich: Die Top-Performer der aktuellen offiziellen Bestenliste gewinnen weniger als 22% ihrer Spiele. Ich kann die vollständige Auflistung des Genossenschaftsschwarms nicht innerhalb der maximal zulässigen Länge für eine Antwort anpassen. Wenn Sie also anzeigen möchten, müssen Sie stattdessen hier klicken: https://pastebin.com/3Zc8m1Ex

Da jeder Bot in durchschnittlich 27 Spielen gespielt hat, spielt das Glück eine relativ große Rolle, wenn man die Ergebnisse für einzelne Bots betrachtet. Da ich noch keine fortgeschrittene Strategie für nicht kooperative Spiele implementiert habe, haben die meisten anderen Bots drastisch vom Spiel gegen den kooperativen Schwarm profitiert und sogar die mittlere Gewinnrate des kooperativen Schwarms von 86% erreicht.

Die vollständigen Ergebnisse für Bots, die nicht im Schwarm sind, sind unten aufgeführt. Es gibt zwei Bots, deren Ergebnisse meiner Meinung nach besondere Aufmerksamkeit verdienen. Erstens konnte StopBot überhaupt keine Spiele gewinnen. Dies ist besonders tragisch, da der Genossenschaftsschwarm genau dieselbe Strategie wie StopBot verwendete. Sie hätten erwartet, dass StopBot zufällig acht seiner Spiele gewinnt, und ein bisschen mehr, weil der kooperative Schwarm gezwungen ist, seinen Gegnern den ersten Zug zu geben. Das zweite interessante Ergebnis ist jedoch, dass sich die harte Arbeit von PointsAreForNerdsBot endlich gelohnt hat: Es hat mit dem Schwarm zusammengearbeitet und es geschafft, jedes einzelne gespielte Spiel zu gewinnen!

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

Mängel

Dieser kooperative Ansatz weist einige Nachteile auf. Erstens, wenn sie gegen nicht kooperative Bots spielen, erhalten kooperative Bots niemals den Vorteil der ersten Runde, weil sie, wenn sie zuerst spielen, noch nicht wissen, ob ihre Gegner zur Zusammenarbeit bereit sind oder nicht, und daher keine andere Wahl haben, als eine zu bekommen Punktzahl von Null. In ähnlicher Weise ist diese kooperative Strategie äußerst anfällig für die Ausbeutung durch böswillige Bots. Zum Beispiel kann der Bot, der zuletzt in der letzten Runde spielt, während des kooperativen Spiels sofort aufhören zu würfeln, um alle anderen zu verlieren (vorausgesetzt natürlich, sein erster Wurf war keine Sechs).

Durch die Zusammenarbeit können alle Bots die optimale Lösung mit einer Gewinnrate von 100% erzielen. Wäre also nur die Gewinnrate von Bedeutung, wäre die Zusammenarbeit ein stabiles Gleichgewicht und es gäbe nichts, worüber man sich Sorgen machen müsste. Einige Bots setzen jedoch Prioritäten für andere Ziele, z. B. das Erreichen des oberen Bereichs der Bestenliste. Dies bedeutet, dass das Risiko besteht, dass ein anderer Bot nach Ihrem letzten Zug ausfällt, was einen Anreiz für Sie darstellt, zuerst auszufallen. Da wir durch die Einrichtung dieses Wettbewerbs nicht sehen können, was unsere Gegner in ihren vorherigen Spielen getan haben, können wir Personen, die übergelaufen sind, nicht bestrafen. Kooperation ist also letztendlich ein instabiles Gleichgewicht, das zum Scheitern verurteilt ist.

Fußnoten

[1]: Die Hauptgründe, warum ich nicht Tausende von Bots anstatt nur zwei einsenden möchte, sind, dass dies die Simulation um einen Faktor in der Größenordnung von 1000 verlangsamen würde [2] und dass dies einen erheblichen Schaden anrichten würde Gewinnanteile, da andere Bots fast ausschließlich gegen den Schwarm und nicht gegeneinander spielen. Wichtiger ist jedoch die Tatsache, dass ich selbst dann, wenn ich wollte, nicht in der Lage wäre, so viele Bots in einem angemessenen Zeitrahmen zu erstellen, ohne den Geist der Regel zu brechen, dass "ein Bot nicht genau die gleiche Strategie wie ein Bots implementieren darf absichtlich oder versehentlich ".

[2]: Ich denke, es gibt zwei Hauptgründe, warum die Simulation beim Laufen eines kooperativen Schwarms langsamer wird. Erstens bedeutet mehr Bots mehr Spiele, wenn Sie möchten, dass jeder Bot in der gleichen Anzahl von Spielen spielt (in der Fallstudie würde sich die Anzahl der Spiele um den Faktor 77 unterscheiden). Zweitens dauern kooperative Spiele nur länger, weil sie volle 200 Runden dauern und die Spieler innerhalb einer Runde auf unbestimmte Zeit weitermachen müssen. Bei meinem Setup dauerte die Simulation von Spielen etwa 40-mal länger: Die Fallstudie dauerte etwas mehr als drei Minuten, um 10000 Spiele auszuführen, aber nach dem Entfernen des kooperativen Schwarms waren 10000 Spiele in nur 4,5 Sekunden beendet. Ich schätze, zwischen diesen beiden Gründen würde es ungefähr 3100 Mal länger dauern, die Leistung von Bots genau zu messen, wenn ein Schwarm im Wettbewerb ist, verglichen mit dem, wenn es keinen gibt.


4
Beeindruckend. Und willkommen bei PPCG. Das ist schon die erste Antwort. Ich hatte so eine Situation nicht wirklich geplant. Sie haben sicherlich eine Lücke in den Regeln gefunden. Ich bin mir nicht sicher, wie ich das bewerten soll, da Ihre Antwort eher eine Ansammlung von Bots als ein einzelner Bot ist. Das Einzige, was ich im Moment sagen werde, ist, dass es sich unfair anfühlt, wenn ein Teilnehmer 98,7% aller Bots kontrolliert.
Maxb

2
Ich möchte eigentlich nicht, dass doppelte Bots am offiziellen Wettbewerb teilnehmen. Aus diesem Grund habe ich die Simulation selbst ausgeführt, anstatt Tausende von nahezu identischen Bots zu senden. Ich werde meine Vorlage überarbeiten, um das klarer zu machen.
Einhaender

1
Hätte ich eine Antwort wie diese erwartet, hätte ich die Partien, die zu 200 Runden gehören, so geändert, dass sie den Spielern keine Punkte geben. Wie Sie jedoch bemerken, gibt es eine Regel zum Erstellen identischer Bots, die dazu führen würde, dass diese Strategie gegen die Regeln verstößt. Ich werde die Regeln nicht ändern, da es für jeden unfair wäre, der einen Bot gemacht hat. Das Konzept der Zusammenarbeit ist jedoch sehr interessant. Ich hoffe, dass auch andere Bots eingereicht werden, die die Kooperationsstrategie in Kombination mit ihrer eigenen einzigartigen Strategie umsetzen.
Maxb

1
Ich denke, dein Beitrag ist klar, nachdem du ihn gründlicher gelesen hast.
Maxb

Wie viele existierende Bots müssten ihren Code in dieses Kooperations-Framework einbinden, damit die Mehrheit von ihnen einen Nettogewinn bei ihrer Platzierung in der Rangliste sieht? Meine naive Vermutung ist 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

Probieren Sie es einfach mit allen aus GoToNBot, und 20, 22, 24 spielen am besten. Ich weiß nicht warum.


Update: Stoppen Sie immer zu werfen, wenn Sie 40 oder mehr Punkte bekommen.


Ich habe auch mit solchen Bots experimentiert. Die höchste durchschnittliche Punktzahl pro Runde wird gefunden, wenn der Bot auf 16 steigt, aber ich gehe davon aus, dass das "Endspiel" den 20-Bot häufiger zum Gewinnen bringt.
Maxb

@maxb Nicht so, 20 sind immer noch die Besten ohne das "Endspiel" in meinem Test. Vielleicht haben Sie es auf der alten Version des Controllers getestet.
Dienstag,

Vor dem Entwerfen dieser Herausforderung habe ich einen separaten Test durchgeführt, bei dem ich die durchschnittliche Punktzahl pro Runde für die beiden Taktiken in meinem Beitrag ("x mal werfen" und "bis x Punktzahl werfen") berechnet habe. Das Maximum, das ich gefunden habe, lag bei 15-16 . Obwohl meine Stichprobe zu klein hätte sein können, bemerkte ich eine Instabilität.
Maxb

2
Ich habe einige Tests damit durchgeführt, und meine Schlussfolgerung ist einfach, dass 20 gut funktioniert, weil es 40/2 ist. Obwohl ich nicht ganz sicher bin. Als ich end_score4000 eingestellt habe (und Ihren Bot geändert habe, um dies in der targetBerechnung zu verwenden), waren die 15-16 Bots viel besser. Aber wenn es bei dem Spiel nur darum ging, die Punktzahl zu erhöhen, wäre es trivial.
Maxb

1
@maxb Wenn end_score4000 ist, ist es fast unmöglich, 4000 vor 200 Umdrehungen zu bekommen. Und das Spiel ist einfach, wer in 200 Runden die höchste Punktzahl erzielt hat. Und aufhören bei 15 sollte funktionieren, da diesmal die Strategie für die höchste Punktzahl in einer Runde dieselbe ist wie die höchste Punktzahl in 200 Runden.
tsh

10

Adaptive Walze

Fängt aggressiver an und beruhigt sich gegen Ende der Runde.
Wenn es glaubt, dass es gewinnt, würfeln Sie eine zusätzliche Zeit für die Sicherheit.

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

Tolle erste Einreichung! Ich werde es gegen meine Bots laufen lassen, die ich zum Testen geschrieben habe, aber ich werde den Highscore aktualisieren, wenn mehr Bots gepostet wurden.
Maxb

Ich habe einige Tests mit geringfügigen Änderungen an Ihrem Bot durchgeführt. lim = max(min(self.end_score - scores[self.index], 24), 6)Durch Erhöhen des Maximums auf 24 und Hinzufügen eines Minimums von 6 erhöht sich der Gewinnprozentsatz für sich und noch mehr für sich.
AKroell

@AKroell: Cool! Ich habe vorgehabt, etwas Ähnliches zu tun, um sicherzustellen, dass es am Ende einige Male rollt, aber ich habe mir noch nicht die Zeit genommen, es zu tun. Seltsamerweise scheint es mit diesen Werten schlechter zu laufen, wenn ich 100k-Läufe mache. Ich habe allerdings nur mit 18 Bots getestet. Vielleicht sollte ich ein paar Tests mit allen Bots machen.
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 weigert sich, hinter irgendjemandem zurückzubleiben. Solange es einen Bot mit einer höheren Punktzahl gibt, rollt er weiter.


Weil, wie yieldfunktioniert, wenn es beginnt , rollt es wird nie aufhören. Sie möchten my_scorein der Schleife aktualisieren .
Spitemaster

@Spitemaster Behoben, danke.
Mnemonik

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

Die Idee ist, dass andere Bots Punkte verlieren können, also ist es nicht schlecht, Zweiter zu sein - aber wenn Sie sehr weit hinten liegen, können Sie genauso gut pleite gehen.


1
Willkommen bei PPCG! Ich schaue mich nach Ihrer Einsendung um und es scheint, dass je mehr Spieler im Spiel sind, desto niedriger ist der Gewinnprozentsatz für Ihren Bot. Ich kann nicht sofort sagen warum. Wenn die Bots mit 1 gegen 1 verglichen werden, erhalten Sie eine Winrate von 10%. Die Idee klingt vielversprechend und der Code sieht korrekt aus, sodass ich nicht wirklich sagen kann, warum Ihre Winrate nicht höher ist.
Maxb

6
Ich habe in das Verhalten sieht, und diese Linie hatte mich verwirrt: 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. Obwohl Ihr Bot nach 7 Würfen die Führung innehat, geht er weiter, bis er eine 6 erreicht. Während ich dies schreibe, habe ich das Problem herausgefunden! Sie scoresenthalten nur die Gesamtpunktzahlen, nicht die Fälle für die aktuelle Runde. Sie sollten es ändern, um zu sein current_score = scores[self.index] + sum(self.current_throws).
Maxb

Danke - wird das ändern!
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

Wir wollen groß oder nach Hause gehen, oder? GoHomeBot geht meistens einfach nach Hause. (Macht sich aber überraschend gut!)


Da dieser Bot immer für 40 Punkte geht, hat er nie Punkte in der scoresListe. Es gab vorher einen solchen Bot (den GoToEnd-Bot), aber David hat seine Antwort gelöscht. Ich werde diesen Bot durch deinen ersetzen.
Maxb

1
Es ist ziemlich lustig, die erweiterten Statistiken dieses Bots zu sehen: Mit Ausnahme von pointsAreForNerds und StopBot hat dieser Bot die niedrigsten Durchschnittspunkte und dennoch eine schöne Gewinnquote
Belhenix,

5

Stellen Sie sicher, führen

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 leiht Ideen von GoTo20Bot aus. Es fügt das Konzept hinzu, dass immer berücksichtigt wird (wenn in last_round oder 40 erreicht wird), dass es andere gibt, die mindestens einen weiteren Wurf haben. So versucht der Bot, ihnen ein Stück voraus zu sein, so dass sie aufholen müssen.


4

Roll6TimesV2

Schlägt nicht die aktuell besten, aber ich denke, es wird besser mit mehr Bots im Spiel.

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

Wirklich tolles Spiel übrigens.


Willkommen bei PPCG! Sehr beeindruckend, nicht nur für Ihre erste KotH-Herausforderung, sondern auch für Ihre erste Antwort. Schön, dass dir das Spiel gefallen hat! Ich habe nach dem Abend, als ich es gespielt habe, viele Diskussionen über die beste Taktik für das Spiel geführt, daher schien es perfekt für eine Herausforderung zu sein. Sie sind derzeit auf dem dritten Platz von 18.
Maxb

4

StopBot

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

Wörtlich nur einen Wurf.

Dies entspricht der Basisklasse Bot.


1
Tut mir nicht leid Sie befolgen alle Regeln, obwohl ich befürchte, dass Ihr Bot mit durchschnittlich 2,5 Punkten pro Runde nicht besonders effektiv ist.
Maxb

1
Ich weiß, aber jemand musste diesen Bot posten. Entartete Bots für den Verlust.
Zacharý

5
Ich würde sagen, ich bin beeindruckt davon, dass Ihr Bot in der letzten Simulation genau einen Sieg eingefahren hat, was beweist, dass es nicht völlig nutzlos ist.
Maxb

2
ES GEWANN EIN SPIEL ?! Das ist überraschend.
Zacharý

3

BringMyOwn_dice (BMO_d)

Dieser Bot liebt Würfel, er bringt 2 (scheinbar die besten) Würfel für sich. Bevor er in einer Runde Würfel wirft, wirft er seine eigenen 2 Würfel und berechnet ihre Summe. Dies ist die Anzahl der Würfe, die er ausführen wird. Er wirft nur, wenn er noch keine 40 Punkte hat.

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
Ich dachte an einen zufälligen Bot mit einem Münzwurf, aber das ist mehr im Sinne der Herausforderung! Ich denke, dass zwei Würfel die beste Leistung bringen, da Sie die meisten Punkte pro Runde erzielen, wenn Sie den Würfel fünf- bis sechsmal werfen. Dies liegt in der Nähe der durchschnittlichen Punktzahl, wenn Sie zwei Würfel werfen.
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 onceist nicht nötig - es wird einmal geworfen, bevor Sie Ihren Bot anrufen. Ihr Bot wirft immer mindestens zweimal.
Spitemaster

Vielen Dank. Der Name der Methode hat mich in die Irre geführt.
Peter Taylor

@ PeterTaylor Vielen Dank für Ihren Beitrag! Ich habe die make_throwMethode früh benannt, als ich wollte, dass die Spieler ihren Zug überspringen können. Ich denke ein passenderer Name wäre keep_throwing. Vielen Dank für das Feedback in der Sandbox, es hat wirklich geholfen, dies zu einer richtigen Herausforderung zu machen!
Maxb

3

Gehen Sie früh groß raus

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

Konzept: Versuchen Sie, bei einem frühen Wurf (bis 25) groß zu gewinnen, und steigen Sie dann von dort aus zu 2 Würfen auf einmal auf.


3

BinaryBot

Versucht, sich dem Endergebnis zu nähern, damit jemand, der die letzte Runde auslöst, sein Endergebnis übertrifft. Das Ziel liegt immer in der Mitte zwischen dem aktuellen Punktestand und dem Endpunktestand.

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

Interessant, Hesitateweigert sich auch, die Linie zuerst zu überqueren. Sie müssen Ihre Funktion mit dem classZeug umgeben.
Christian Sievers

3

PointsAreForNerdsBot

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

Dieser braucht keine Erklärung.

OneInFiveBot

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

Würfelt so lange, bis er auf seinem eigenen 5-seitigen Würfel eine Fünf würfelt. Fünf ist weniger als sechs, also MUSS es GEWINNEN!


2
Willkommen bei PPCG! Ich bin mir sicher, dass Sie es wissen, aber Ihr erster Bot ist buchstäblich der schlechteste Bot in diesem Wettbewerb! Das OneInFiveBotist eine nette Idee, aber ich denke, es leidet im Endspiel im Vergleich zu einigen der fortgeschritteneren Bots. Noch eine tolle Einreichung!
Maxb

2
Das OneInFiveBotist insofern recht interessant, als er durchweg die höchste erreichte Gesamtpunktzahl hat.
AKroell

1
Danke fürs Geben StopBot einen Boxsack gegeben hast: P. Der OneInFiveBot ist eigentlich ziemlich ordentlich, nette Arbeit!
Zacharý

@maxb Ja, da habe ich den Namen bekommen. Ich habe ehrlich gesagt nicht getestet OneInFiveBotund es
läuft


3

LizduadacBot

Versucht in einem Schritt zu gewinnen. Endbedingung ist etwas willkürlich.

Dies ist auch mein erster Beitrag (und ich bin neu in Python). Wenn ich also "PointsAreForNerdsBot" besiege, würde ich mich freuen!

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

Willkommen bei PPCG (und willkommen bei Python)! Es würde Ihnen schwer fallen, dagegen zu verlieren PointsAreForNerdsBot, aber Ihr Bot schlägt sich tatsächlich recht gut. Ich werde die Punktzahl entweder später heute Abend oder morgen aktualisieren, aber Ihre Winrate liegt bei 15%, was über dem Durchschnitt von 12,5% liegt.
Maxb

Mit "harte Zeit" meinen sie, dass es unmöglich ist (es sei denn, ich habe es stark missverstanden)
Zacharý

@maxb Ich hätte eigentlich nicht gedacht, dass die Gewinnrate so hoch sein würde! (Ich habe es nicht vor Ort getestet). Ich frage mich, ob eine Änderung der 50-Punkte in einen höheren / niedrigeren Wert die Gewinnrate erhöhen würde.
Lizduadac

3

Langsamer Start

Dieser Bot implementiert den TCP Slow Start Algorithmus. Er passt die Anzahl seiner Würfe ( noch ) entsprechend seiner vorherigen Runde an: Wenn er in der vorherigen Runde keine 6 gewürfelt hat, erhöht er das Nor für diese Runde; während es reduziert noch wenn es tat.

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

Willkommen bei PPCG! Interessanter Ansatz, ich weiß nicht, wie empfindlich es gegen zufällige Schwankungen ist. Zwei Dinge, die benötigt werden, um diesen Lauf zu machen: def updateValues():sollte sein def updateValues(self):(oder def update_values(self):wenn Sie PEP8 folgen wollen). Zweitens sollte der Aufruf updateValues()stattdessen self.updateValues()(oder self.update_vales()) sein.
Maxb

2
Außerdem denke ich, dass Sie Ihre iVariable in der while-Schleife aktualisieren müssen . Im Moment übergibt Ihr Bot entweder die while-Schleife vollständig oder bleibt in der while-Schleife stecken, bis er 6 erreicht.
maxb

Im aktuellen Highscore habe ich mir erlaubt, diese Änderungen umzusetzen. Ich denke, Sie könnten mit dem Anfangswert für experimentieren self.norund sehen, wie sich dies auf die Leistung Ihres Bots auswirkt.
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                                               

Gewöhnlich gewinnt das Bewusstsein - aber das Schicksal lässt sich nicht immer vermeiden.
Großartig und mysteriös sind die Wege von Shai-Hulud!


In den Anfängen dieser Herausforderung (dh bevor sie NeoBotveröffentlicht wurde) schrieb ich einen fast trivialen OracleBot:

    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

habe es aber nicht gepostet, da ich es nicht interessant genug fand;) Aber einmal NeoBot die Führung übernahm, begann ich darüber nachzudenken, wie ich seine perfekte Fähigkeit, die Zukunft vorherzusagen, übertreffen könnte. Also hier ist ein Dünenzitat; es ist, wenn Paul Atreides, der Kwisatz Haderach, an einem Zusammenhang steht, von dem aus sich unendlich viele verschiedene Zukünfte abwickeln können:

Er erkannte, dass die Voraussicht eine Beleuchtung war, die die Grenzen dessen, was sie enthüllte, einbezog - eine Quelle der Genauigkeit und des bedeutungsvollen Irrtums. Eine Art Heisenberg-Unbestimmtheit griff ein: Der Energieaufwand, der enthüllte, was er sah, änderte, was er sah…… die kleinste Handlung - ein Augenzwinkern, ein nachlässiges Wort, ein verlegtes Sandkorn - bewegte einen gigantischen Hebel über den Bildschirm bekanntes Universum. Er sah Gewalt mit dem Ergebnis, das so vielen Variablen unterworfen war, dass seine kleinste Bewegung große Veränderungen in den Mustern hervorrief.

Die Vision brachte ihn dazu, in die Unbeweglichkeit einzufrieren, aber auch dies war eine Aktion mit ihren Konsequenzen.

Hier war also die Antwort: Die Zukunft vorauszusehen bedeutet, sie zu ändern. und wenn Sie sehr vorsichtig sind, dann können Sie es durch selektives Handeln oder Untätigkeit auf vorteilhafte Weise ändern - zumindest die meiste Zeit. Sogar die KwisatzHaderachkönnen keine 100% Gewinnrate erzielen!


Es scheint, dass dieser Bot den Status des Zufallszahlengenerators ändert, um sicherzustellen, dass er nicht mit 6 würfelt oder es zumindest vorwegnimmt. Gleiches gilt für HarkonnenBot. Ich stelle jedoch fest, dass die Gewinnrate dieser Bots viel höher ist als die von NeoBot. Manipulieren Sie den Zufallsgenerator aktiv, um zu verhindern, dass er 6 rollt?
2.

Oh, bei meiner ersten Lesung habe ich nicht bemerkt, dass dies nicht nur schöner, NeoBotsondern auch besser ist! Mir gefällt auch, wie Sie ein Beispiel geben, was alles mit Zufälligkeit (insbesondere der Steuerung) tun sollte: Verwenden Sie Ihre eigene random.RandomInstanz. Wie NeoBot, scheint dies ein wenig empfindlich auf Veränderungen von nicht näheren Implementierungsdetails des Controllers.
Christian Sievers

@maxb: HarkonnenBotberührt das RNG nicht; es interessiert sich überhaupt nicht für Zufallszahlen. Es vergiftet nur alle anderen Bots und läuft dann so langsam wie möglich bis zur Ziellinie. Wie viele kulinarische Köstlichkeiten ist Rache ein Gericht, das nach langer und delikater Zubereitung am besten langsam genossen wird.
Dani O.

@ChristianSievers: Im Gegensatz zu NeoBot (und HarkonnenBot) basiert es KwisatzHaderachnur auf einem Detail der Implementierung. Insbesondere muss nicht bekannt sein, wie random.random () implementiert ist, sondern nur, dass der Controller es verwendet. D
Dani O

1
Ich habe alle Ihre Bots durchgesehen. Ich habe mich entschieden KwisatzHaderachund HarkonnenBotgenauso zu behandeln wie NeoBot. Sie erhalten ihre Punkte aus einer Simulation mit weniger Spielen und sind nicht in der offiziellen Simulation enthalten. Sie landen jedoch in der Highscoreliste NeoBot. Der Hauptgrund dafür, dass sie nicht in der offiziellen Simulation dabei sind, ist, dass sie andere Bot-Strategien durcheinander bringen. Jedoch. WisdomOfCrowdssollte für die Teilnahme gut geeignet sein, und ich bin gespannt auf die neuen Änderungen, die Sie daran vorgenommen haben!
Maxb

2
class ThrowThriceBot(Bot):

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

Nun, das ist offensichtlich


Ich habe einige Experimente mit dieser Klasse von Bots durchgeführt (das war die Taktik, die ich beim ersten Spielen angewendet habe). Ich habe dann 4 Würfe ausgeführt, obwohl 5-6 eine höhere durchschnittliche Punktzahl pro Runde haben.
Maxb

Herzlichen Glückwunsch zu Ihrer ersten KotH-Antwort!
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 ist immer die letzte Runde und der letzte Bot: Es rollt weiter, bis es an der Spitze liegt. Es möchte sich auch nicht mit weniger als 15 Punkten zufrieden geben, es sei denn, es ist tatsächlich die letzte Runde oder es werden 40 Punkte erreicht.


Interessanter Ansatz. Ich denke, dein Bot leidet, wenn er ins Hintertreffen gerät. Da die Chancen, in einer einzelnen Runde mehr als 30 Punkte zu erzielen, gering sind, bleibt Ihr Bot mit größerer Wahrscheinlichkeit auf dem aktuellen Stand.
Maxb

1
Ich vermute, dass dies an dem gleichen Fehler leidet, den ich gemacht habe (siehe NotTooFarBehindBot-Kommentare). Wenn Sie nicht gewinnen, werfen Sie so lange, bis Sie eine 6 erhalten (Punktzahl [self.index] wird nie aktualisiert). Hast du diese Ungleichung auf die falsche Weise? Max (Punkte) wird immer> = Punkte sein [self.index]
Stuart Moore

@StuartMoore Haha, ja, ich denke du hast recht. Vielen Dank!
Spitemaster

Ich vermute, Sie wollen "und last_round" am 2., während Sie tun, was Sie wollen - andernfalls wird die 2., während verwendet, ob last_round wahr ist oder nicht
Stuart Moore

3
Das ist beabsichtigt. Es versucht immer an der Spitze zu sein, wenn es seinen Zug beendet.
Spitemaster

2

QuotaBot

Ein naives "Quoten" -System, das ich implementiert habe und das insgesamt ziemlich gut abschneidet.

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:gibt einen fehler für mich. Alsowhile sum(self.current_throws)
Spitemaster

@Spitemaster war ein Fehler beim Einfügen in den Stapelaustausch, sollte jetzt funktionieren.
FlipTack

@ Splitemaster es ist, weil es gab <und >Symbole, die mit den <pre>Tags störten, die ich verwendete
FlipTack

2

ErwartungenBot

Spielt es einfach geradeaus, berechnet den erwarteten Wert für den Würfelwurf und macht es nur, wenn es positiv ist.

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

Ich hatte Probleme beim Laufenlassen des Controllers, bekam einen "NameError: Name 'bots_per_game' ist nicht definiert" auf dem Multithread-Controller, also keine Ahnung, wie sich das verhält.


1
Ich denke, dass dies einem "Go to 16" -Bot entspricht, aber wir haben noch keinen
Stuart Moore

1
@StuartMoore Das ... ist ein sehr wahrer Punkt, ja
Kain

Ich bin auf Ihre Probleme mit dem Controller gestoßen, als ich ihn auf meinem Windows-Computer ausgeführt habe. Irgendwie lief es gut auf meinem Linux-Rechner. Ich aktualisiere den Controller und werde den Post aktualisieren, sobald er fertig ist.
Maxb

@maxb Danke, wahrscheinlich etwas darüber, welche Variablen in den verschiedenen Prozessen verfügbar sind. Zu Ihrer Information, ich habe dies auch aktualisiert und einen dummen Fehler gemacht: /
Cain

2

Segen

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

Segen FrankerZ GabeN Segen


2

Vierzigzehn

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

Versuchen Sie, bis zur letzten Runde 14 Punkte zu erzielen, und gehen Sie dann davon aus, dass alle anderen 14 Punkte erzielen und versuchen, diese Punktzahl zu erzielen.


Ich habe TypeError: unsupported operand type(s) for -: 'list' and 'int'mit deinem Bot.
TSH

Ich gehe davon aus, dass Sie max_projected_scoredas Maximum der Liste sein sollten und nicht die gesamte Liste. Stimmt das? Ansonsten bekomme ich das gleiche Problem wie TSH.
Maxb

Hoppla, bearbeitet, um das Problem zu beheben.
Histokrat

2

Zögern

Führt zwei bescheidene Schritte aus und wartet dann darauf, dass jemand anderes die Grenze überschreitet. Die aktualisierte Version versucht nicht mehr, den Highscore zu übertreffen, sondern will ihn nur noch erreichen - Verbesserung der Leistung durch Löschen von zwei Bytes des Quellcodes!

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

Rebell

Dieser Bot kombiniert die einfache Strategie von Hesitate mit der fortgeschrittenen Strategie der letzten Runde von BotFor2X, versucht sich zu erinnern, wer er ist und wird wild, wenn er feststellt, dass er in einer Illusion lebt.

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)

Nun, das ist ziemlich elegant :) Außerdem herzlichen Glückwunsch zum Erreichen des ersten und zweiten Platzes im Hauptwettbewerb!
Dani O

Natürlich habe ich gezwickt, HarkonnenBotdamit es Rebelsich nicht mehr selbst vergiften kann;) und ich habe auch gezwickt, TleilaxuBotdamit Rebeles nicht mehr auffällt !
Dani O

1

Nimm fünf

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

In der Hälfte der Zeit würfeln wir eine 5 vor einer 6. Wenn wir dies tun, erhalten Sie eine Auszahlung.


Wenn wir stattdessen bei 1 anhalten, macht es langsamere Fortschritte, aber es ist wahrscheinlicher, dass es in einer einzigen Schranke 40 wird.
Mnemonik

Bei meinen Tests erhielt TakeOne 20,868 Punkte pro Runde im Vergleich zu den 24,262 Punkten von TakeFive pro Runde (und brachte auch eine Winrate von 0,291 auf 0,259). Ich glaube nicht, dass es sich lohnt.
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

Chaser versucht, auf Position eins aufzuholen. Wenn es die letzte Runde ist, versucht er verzweifelt, mindestens 50 Punkte zu erreichen. Nur zum guten Zweck wirft er mindestens vier Mal, egal was passiert

[Edit 1: Go-for-Gold-Strategie in der letzten Runde hinzugefügt]

[edit 2: Logik aktualisiert, weil ich fälschlicherweise dachte, ein Bot würde eher 40 Punkte erzielen als nur die höchste Punktzahl]

[Edit 3: Chaser im Endspiel etwas defensiver gemacht]


Willkommen bei PPCG! Gute Idee, nicht nur aufzuholen, sondern auch den ersten Platz zu belegen. Ich führe gerade eine Simulation durch und wünsche Ihnen viel Glück!
Maxb

Vielen Dank! Anfangs habe ich versucht, den vorherigen Anführer um einen festen Betrag zu übertreffen (versuchte Werte zwischen 6 und 20), aber es stellte sich heraus, dass ich nur zweimal mehr Messen besser warf.
AKroell

@ JonathanFrech danke, behoben
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

Als Paar Bots bringen sie ihre eigenen Würfel und werfen sie, um die Zukunft vorherzusagen. Wenn einer eine 6 ist, hört FutureBot auf, sich zu erinnern, welcher der 2 Würfel für den nächsten Wurf war, und gibt auf.

Ich frage mich, was es besser machen wird.

OneStepAhead ist OneInFive für meinen Geschmack ein wenig zu ähnlich, aber ich möchte auch sehen, wie es mit FutureBot und OneInFive verglichen wird.

Edit: Jetzt hören sie auf, nachdem sie 45 getroffen haben


Willkommen bei PPCG! Ihr Bot spielt definitiv mit dem Geist des Spiels! Ich werde später am Abend eine Simulation durchführen.
Maxb

Vielen Dank! Ich bin gespannt, wie gut es wird, aber ich gehe davon aus, dass es auf der niedrigen Seite sein wird.
William Porter
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.