Gra w kości, ale unikaj numeru 6 [zamknięte]

58

Turniej się skończył!

Turniej się zakończył! Ostateczna symulacja została przeprowadzona w nocy, łącznie gier. Zwycięzcą został Christian Sievers ze swoim botem OptFor2X . Christian Sievers zdołał także zabezpieczyć drugie miejsce Rebelią . Gratulacje! Poniżej znajduje się oficjalna lista najlepszych wyników turnieju.3108

Jeśli nadal chcesz zagrać w tę grę, z przyjemnością skorzystaj z kontrolera zamieszczonego poniżej i użyj kodu w nim, aby stworzyć własną grę.

Kostka do gry

Zostałem zaproszony do gry w kości, o której nigdy nie słyszałem. Zasady były proste, ale myślę, że byłoby idealne na wyzwanie KotH.

Zasady

Początek gry

Kostka porusza się wokół stołu i za każdym razem, gdy jest twoja kolej, możesz rzucić kostką tyle razy, ile chcesz. Musisz jednak rzucić to przynajmniej raz. Śledzisz sumę wszystkich rzutów w swojej rundzie. Jeśli zdecydujesz się zatrzymać, wynik za rundę zostanie dodany do całkowitego wyniku.

Dlaczego więc miałbyś przestać rzucać kostką? Ponieważ jeśli zdobędziesz 6, twój wynik za całą rundę wyniesie zero, a kość zostanie przekazana. Zatem początkowym celem jest jak najszybsze zwiększenie wyniku.

Kto jest zwycięzcą?

Gdy pierwszy gracz przy stole osiągnie 40 lub więcej punktów, rozpoczyna się ostatnia runda. Po rozpoczęciu ostatniej rundy wszyscy oprócz osoby, która zainicjowała ostatnią rundę, dostaje jeszcze jedną turę.

Zasady dla ostatniej rundy są takie same jak dla każdej innej rundy. Wybierasz rzucanie lub przestaniesz. Wiesz jednak, że nie masz szans na wygraną, jeśli nie uzyskasz wyższego wyniku niż przed ostatnią rundą. Ale jeśli posuniesz się za daleko, możesz otrzymać 6.

Należy jednak wziąć pod uwagę jeszcze jedną zasadę. Jeśli bieżący całkowity wynik (poprzedni wynik + aktualny wynik w rundzie) wynosi 40 lub więcej i trafisz 6, łączny wynik jest ustawiony na 0. Oznacza to, że musisz zacząć wszystko od nowa. Jeśli trafisz 6, gdy obecny całkowity wynik wynosi 40 lub więcej, gra będzie kontynuowana normalnie, z tym wyjątkiem, że jesteś teraz na ostatnim miejscu. Ostatnia runda nie jest uruchamiana po zresetowaniu całkowitego wyniku. Nadal możesz wygrać rundę, ale staje się ona trudniejsza.

Zwycięzcą zostaje gracz z najwyższym wynikiem po zakończeniu ostatniej rundy. Jeśli dwóch lub więcej graczy podzieli ten sam wynik, wszyscy będą liczeni jako zwycięzcy.

Dodatkową zasadą jest to, że gra trwa maksymalnie 200 rund. Ma to na celu zapobieganie przypadkom, w których wiele botów w zasadzie rzuca, dopóki nie osiągnie 6, aby utrzymać swój obecny wynik. Po przejściu 199. rundy zostaje last_roundustawiona na wartość true i rozpoczyna się jeszcze jedna runda. Jeśli gra przejdzie do 200 rund, zwycięzcą jest bot (lub boty) z najwyższym wynikiem, nawet jeśli nie mają 40 punktów lub więcej.

Podsumować

  • W każdej rundzie rzucasz kością, dopóki nie zatrzymasz się lub nie otrzymasz 6
  • Musisz rzucić kostką raz (jeśli twój pierwszy rzut to 6, twoja runda natychmiast się kończy)
  • Jeśli otrzymasz 6, twój aktualny wynik jest ustawiony na 0 (nie twój całkowity wynik)
  • Po każdej rundzie dodajesz swój aktualny wynik do całkowitego wyniku
  • Kiedy bot kończy swoją turę, co daje łączny wynik co najmniej 40, wszyscy inni otrzymują ostatnią turę
  • Jeśli twój obecny całkowity wynik to a otrzymasz 6, twój całkowity wynik jest ustawiony na 0 i Twoja runda się kończy40
  • Ostatnia runda nie jest uruchamiana, gdy wystąpi powyższe
  • Zwycięzcą zostaje osoba z najwyższym łącznym wynikiem po ostatniej rundzie
  • W przypadku wielu zwycięzców, wszyscy będą liczeni jako zwycięzcy
  • Gra trwa maksymalnie 200 rund

Wyjaśnienie wyników

  • Łączny wynik: wynik zapisany z poprzednich rund
  • Aktualny wynik: wynik dla bieżącej rundy
  • Aktualny łączny wynik: suma dwóch powyższych wyników

Jak uczestniczysz

Aby wziąć udział w tym wyzwaniu KotH, powinieneś napisać klasę Python, która dziedziczy po Bot. Należy wdrożyć funkcję: make_throw(self, scores, last_round). Ta funkcja zostanie wywołana, gdy nadejdzie Twoja kolej, a twój pierwszy rzut nie był 6. Aby kontynuować rzucanie, powinieneś yield True. Aby przestać rzucać, powinieneś yield False. Po każdym rzucie update_statewywoływana jest funkcja rodzica . Dzięki temu masz dostęp do swoich rzutów w bieżącej rundzie za pomocą zmiennej self.current_throws. Masz również dostęp do własnego indeksu za pomocą self.index. Zatem, aby zobaczyć swój własny wynik, którego byś użył scores[self.index]. Możesz również uzyskać dostęp end_scoredo gry za pomocą self.end_score, ale możesz bezpiecznie założyć, że będzie to 40 za to wyzwanie.

Możesz tworzyć funkcje pomocnicze w swojej klasie. Możesz również zastąpić funkcje istniejące w Botklasie nadrzędnej, np. Jeśli chcesz dodać więcej właściwości klasy. Nie możesz modyfikować stanu gry w jakikolwiek sposób, z wyjątkiem ustępowania Truelub False.

Możesz szukać inspiracji w tym poście i skopiować dowolny z dwóch botów, które tu zawarłem. Obawiam się jednak, że nie są szczególnie skuteczne ...

Zezwalanie na inne języki

Zarówno w piaskownicy, jak i w The Nineteenth Byte, rozmawialiśmy o dopuszczaniu zgłoszeń w innych językach. Po przeczytaniu o takich implementacjach i wysłuchaniu argumentów z obu stron postanowiłem ograniczyć to wyzwanie tylko do Pythona. Wynika to z dwóch czynników: czasu wymaganego do obsługi wielu języków oraz losowości tego wyzwania wymagającego dużej liczby iteracji w celu osiągnięcia stabilności. Mam nadzieję, że nadal będziesz brać udział, a jeśli chcesz nauczyć się Pythona do tego wyzwania, postaram się być dostępny na czacie tak często, jak to możliwe.

W przypadku jakichkolwiek pytań możesz napisać w pokoju czatu dotyczącym tego wyzwania . Do zobaczenia tam!

Zasady

  • Sabotaż jest dozwolony i zachęcany. To znaczy sabotaż przeciwko innym graczom
  • Wszelkie próby majsterkowania przy kontrolerze, czasie wykonywania lub innych zgłoszeniach zostaną zdyskwalifikowane. Wszystkie zgłoszenia powinny działać tylko z danymi wejściowymi i pamięcią.
  • Każdy bot, który wykorzystuje więcej niż 500 MB pamięci do podjęcia decyzji, zostanie zdyskwalifikowany (jeśli potrzebujesz takiej ilości pamięci, powinieneś przemyśleć swoje wybory)
  • Bot nie może wdrożyć dokładnie tej samej strategii, co istniejąca, celowo lub przypadkowo.
  • W trakcie wyzwania możesz aktualizować swojego bota. Możesz jednak również opublikować innego bota, jeśli Twoje podejście jest inne.

Przykład

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

Ten bot będzie działał, dopóki nie uzyska wyniku co najmniej 10 w rundzie lub nie wyrzuci 6. Zauważ, że nie potrzebujesz żadnej logiki, aby poradzić sobie z rzucaniem 6. Pamiętaj również, że jeśli twój pierwszy rzut to 6, make_throwto nigdy nie sprawdzany, ponieważ Twoja runda natychmiast się kończy.

Dla tych, którzy są nowi w Pythonie (i nowi w yieldkoncepcji), ale chcą spróbować, yieldsłowo kluczowe jest pod pewnymi względami podobne do zwrotu, ale różni się pod innymi względami. O tej koncepcji możesz przeczytać tutaj . Zasadniczo, gdy ty yield, twoja funkcja zatrzyma się, a edytowana wartość yieldzostanie odesłana z powrotem do kontrolera. Tam kontroler obsługuje logikę, aż nadejdzie czas, aby bot podjął kolejną decyzję. Następnie kontroler wyśle ​​ci rzut kostką, a twoja make_throwfunkcja będzie kontynuowała wykonywanie dokładnie tam, gdzie została zatrzymana wcześniej, zasadniczo na linii po poprzedniej yieldinstrukcji.

W ten sposób kontroler gier może aktualizować stan bez konieczności oddzielnego wywołania funkcji bota dla każdego rzutu kostką.

Specyfikacja

Możesz użyć dowolnej biblioteki Python dostępnej w pip. Aby upewnić się, że będę w stanie uzyskać dobrą średnią, masz limit 100 milisekund na rundę. Byłbym bardzo szczęśliwy, gdyby twój skrypt był o wiele szybszy, abym mógł uruchomić więcej rund.

Ocena

Aby znaleźć zwycięzcę, wezmę wszystkie boty i uruchomię je w losowych grupach po 8 osób. Jeśli zgłoszono mniej niż 8 klas, poprowadzę je w losowych grupach po 4, aby uniknąć zawsze posiadania wszystkich botów w każdej rundzie. Będę przeprowadzał symulacje przez około 8 godzin, a zwycięzcą zostanie bot z najwyższym procentem wygranych. Rozpocznę końcowe symulacje na początku 2019 roku, dając wam wszystkie Święta Bożego Narodzenia do kodowania botów! Wstępna data końcowa to 4 stycznia, ale jeśli to za mało czasu, mogę zmienić ją na późniejszą.

Do tego czasu spróbuję wykonać codzienną symulację z wykorzystaniem 30–60 minut czasu procesora i zaktualizować tablicę wyników. To nie będzie oficjalny wynik, ale posłuży jako przewodnik do sprawdzenia, które boty działają najlepiej. Jednak zbliża się Boże Narodzenie, mam nadzieję, że zrozumiesz, że nie będę przez cały czas dostępny. Zrobię co w mojej mocy, aby przeprowadzić symulacje i odpowiedzieć na wszelkie pytania związane z wyzwaniem.

Sprawdź to sam

Jeśli chcesz uruchomić własne symulacje, oto pełny kod kontrolera uruchamiającego symulację, w tym dwa przykładowe boty.

Kontroler

Oto zaktualizowany kontroler tego wyzwania. Obsługuje wyjścia ANSI, wielowątkowość i zbiera dodatkowe statystyki dzięki AKroell ! Kiedy wprowadzę zmiany w kontrolerze, zaktualizuję post po zakończeniu dokumentacji.

Dzięki BMO kontroler może teraz pobierać wszystkie boty z tego postu za pomocą -dflagi. Inne funkcje pozostają niezmienione w tej wersji. To powinno zapewnić, że wszystkie twoje najnowsze zmiany zostaną jak najszybciej zasymulowane!

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

Jeśli chcesz uzyskać dostęp do oryginalnego kontrolera dla tego wyzwania, jest on dostępny w historii edycji. Nowy kontroler ma tę samą logikę do uruchamiania gry, jedyną różnicą jest wydajność, zbieranie statystyk i ładniejsze drukowanie.

Boty

Na moim komputerze boty są przechowywane w pliku forty_game_bots.py. Jeśli używasz innej nazwy pliku, musisz zaktualizować importinstrukcję na górze kontrolera.

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

Uruchamianie symulacji

Aby uruchomić symulację, zapisz oba fragmenty kodu opublikowane powyżej w dwóch osobnych plikach. Uratowałem je jako forty_game_controller.pyi forty_game_bots.py. Następnie wystarczy użyć python forty_game_controller.pylub w python3 forty_game_controller.pyzależności od konfiguracji Pythona. Postępuj zgodnie z instrukcjami, jeśli chcesz dalej konfigurować symulację, lub jeśli chcesz, majstruj przy kodzie.

Statystyki gry

Jeśli tworzysz bota, który dąży do określonego wyniku bez uwzględnienia innych botów, są to zwycięskie percentyle:

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

Wysokie wyniki

W miarę publikowania kolejnych odpowiedzi postaram się aktualizować tę listę. Zawartość listy zawsze będzie pochodzić z najnowszej symulacji. Boty ThrowTwiceBoti GoToTenBotsą botami z powyższego kodu i są używane jako odniesienie. Przeprowadziłem symulację z 10 ^ 8 grami, co zajęło około 1 godziny. Potem zobaczyłem, że gra osiągnęła stabilność w porównaniu do moich biegów z 10 ^ 7 grami. Ponieważ jednak ludzie nadal publikują boty, nie będę wykonywać żadnych symulacji, dopóki częstotliwość odpowiedzi nie spadnie.

Próbuję dodać wszystkie nowe boty i wszelkie zmiany, które wprowadziłeś w istniejących botach. Jeśli wygląda na to, że przegapiłem twojego bota lub jakieś nowe zmiany, które masz, napisz na czacie, a upewnię się, że w następnej symulacji będę mieć twoją najnowszą wersję.

Mamy teraz więcej statystyk dla każdego bota dzięki AKroell ! Trzy nowe kolumny zawierają maksymalny wynik we wszystkich grach, średni wynik na grę i średni wynik przy wygrywaniu dla każdego bota.

Jak wskazano w komentarzach, wystąpił problem z logiką gry, w wyniku którego boty o wyższym indeksie w grze otrzymywały dodatkową rundę w niektórych przypadkach. Zostało to już naprawione i odzwierciedlają to poniższe wyniki.

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

Następujące boty (oprócz Rebel) są tworzone w celu naginania zasad, a twórcy zgodzili się nie brać udziału w oficjalnym turnieju. Jednak nadal uważam, że ich pomysły są kreatywne i zasługują na wyróżnienie. Rebel jest również na tej liście, ponieważ używa sprytnej strategii, aby uniknąć sabotażu, i faktycznie działa lepiej z botem sabotażowym w grze.

Boty NeoBoti KwisatzHaderachpostępują zgodnie z zasadami, ale wykorzystują lukę, przewidując losowy generator. Ponieważ boty te wymagają wielu zasobów do symulacji, dodałem statystyki z symulacji z mniejszą liczbą gier. Bot HarkonnenBotosiąga zwycięstwo poprzez wyłączenie wszystkich innych botów, co jest niezgodne z zasadami.

    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|
    +----------+-----+
maxb
źródło
2
Może więc zasady byłyby nieco jaśniejsze, gdyby powiedziały: „gdy gracz kończy swoją turę z wynikiem co najmniej 40, wszyscy inni dostają ostatnią turę”. Pozwala to uniknąć pozornego konfliktu, wskazując, że nie osiąga 40, co naprawdę uruchamia ostatnią rundę, zatrzymuje się z co najmniej 40.
aschepler
1
@aschepler to dobre sformułowanie, zredaguję post, gdy będę na komputerze
maxb
2
@maxb Rozszerzyłem kontroler, aby dodać więcej statystyk, które były istotne dla mojego procesu rozwoju: osiągnięty najwyższy wynik, średni wynik osiągnięty i średni wynik zwycięstwa gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell
2
Brzmi bardzo podobnie do bardzo fajnej gry w kości Farkled en.wikipedia.org/wiki/Farkle
Caleb Jay
5
Głosuję za zamknięciem tego pytania, ponieważ jest już de facto zamknięte na nowe odpowiedzi („Turniej się już skończył! Ostateczna symulacja została przeprowadzona w nocy, łącznie 3 ∗ 108 gier”)
pppery

Odpowiedzi:

6

OptFor2X

Bot ten jest zgodny z optymalną strategią dla dwóch graczy w tej grze, wykorzystując jedynie swój wynik i wynik najlepszego przeciwnika. W ostatniej rundzie zaktualizowana wersja uwzględnia wszystkie wyniki.

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
Christian Sievers
źródło
Sprawdzę implementację tak szybko, jak to możliwe. W związku ze świętami Bożego Narodzenia może to być dopiero 25
go
Twój bot jest na czele! Ponadto nie ma potrzeby, aby działał szybciej, jest w przybliżeniu tak szybki, jak wszystkie inne boty w podejmowaniu decyzji.
maxb
Nie chciałem robić tego szybciej. Zrobiłem już to, co chciałem - zainicjowałem tylko raz - ale szukałem lepszego sposobu na zrobienie tego, zwłaszcza bez definiowania funkcji poza klasą. Myślę, że teraz jest lepiej.
Christian Sievers,
Teraz wygląda o wiele lepiej, dobra robota!
maks.
Gratulujemy zdobycia pierwszego i drugiego miejsca!
maxb
20

NeoBot

Zamiast tego spróbuj tylko zrozumieć prawdę - nie ma łyżki

NeoBot zagląda do matrycy (inaczej losowo) i przewiduje, czy następny rzut będzie 6, czy nie - nie może nic poradzić na to, że otrzyma 6, ale z radością unika zakończenia serii.

NeoBot tak naprawdę nie modyfikuje kontrolera ani środowiska uruchomieniowego, po prostu uprzejmie prosi bibliotekę o więcej informacji.

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
W zasadzie niegroźny
źródło
1
Witamy w PPCG! To naprawdę imponująca odpowiedź. Kiedy pierwszy raz go uruchomiłem, niepokoiło mnie to, że używał tej samej ilości czasu działania, co wszystkie inne boty łącznie. Potem spojrzałem na procent wygranej. Naprawdę sprytny sposób omijania zasad. Pozwolę twojemu botowi uczestniczyć w turnieju, ale mam nadzieję, że inni powstrzymają się od stosowania tej samej taktyki, ponieważ narusza to ducha gry.
maxb
2
Ponieważ istnieje tak ogromna różnica między tym botem a drugim miejscem, w połączeniu z faktem, że twój bot wymaga dużo obliczeń, czy zgodziłbyś się, że przeprowadzę symulację z mniejszą liczbą iteracji, aby znaleźć wskaźnik wygranych, a następnie uruchom oficjalny symulacja bez twojego bota?
maxb
3
Świetnie według mnie, pomyślałem, że jest to prawdopodobnie dyskwalifikujące i zdecydowanie nie do końca w duchu gry. To powiedziawszy, było świetną zabawą i fajną wymówką, aby przeszukiwać kod źródłowy Pythona.
Głównie nieszkodliwy
2
dzięki! Nie sądzę, aby jakikolwiek inny bot zbliżył się do twojego wyniku. I dla każdego, kto myśli o wdrożeniu tej strategii, nie rób tego. Odtąd ta strategia jest niezgodna z zasadami, a NeoBot jest jedynym, który może jej używać w celu utrzymania uczciwości turnieju.
maxb
1
No cóż, myBot bije każdego, ale jest to o wiele za dużo lepiej - ale jeśli opublikowałbym bota w ten sposób, dostałbym -100 i nie najlepszy wynik.
Jan Ivan
15

Spółdzielnia rój

Strategia

Nie sądzę, aby ktokolwiek jeszcze zauważył znaczenie tej reguły:

Jeśli gra przejdzie do 200 rund, zwycięzcą jest bot (lub boty) z najwyższym wynikiem, nawet jeśli nie mają 40 punktów lub więcej.

Gdyby każdy bot zawsze toczył się, dopóki nie odpadł, wszyscy mieliby wynik zero na końcu 200 rundy i wszyscy by wygrywali! Tak więc strategia Spółdzielczego Roju polega na współpracy, dopóki wszyscy gracze mają wynik zero, ale normalnej grze, jeśli ktokolwiek zdobędzie jakiekolwiek punkty.

W tym poście przesyłam dwa boty: pierwszy to CooperativeSwarmBot, a drugi to CooperativeThrowTwice. CooperativeSwarmBot służy jako klasa bazowa dla wszystkich botów, które formalnie są częścią roju spółdzielczego, i ma zachowanie zastępcze polegające na zaakceptowaniu pierwszego udanego rzutu, gdy współpraca się nie powiedzie. CooperativeSwarmBot ma CooperativeSwarmBot jako swojego rodzica i jest z nim identyczny pod każdym względem, z wyjątkiem tego, że jego brak współpracy polega na wykonaniu dwóch rzutów zamiast jednego. W ciągu kilku najbliższych dni będę rewidować ten post, aby dodać nowe boty, które wykorzystują znacznie bardziej inteligentne zachowanie, grając przeciwko botom niewspółpracującym.

Kod

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

Analiza

Zdolność do życia

W tej grze bardzo trudno jest współpracować, ponieważ do jej działania potrzebujemy wsparcia wszystkich ośmiu graczy. Ponieważ każda klasa botów jest ograniczona do jednej instancji na grę, jest to trudny cel do osiągnięcia. Na przykład szanse na wybór ośmiu botów kooperacyjnych z puli 100 botów kooperacyjnych i 30 botów kooperacyjnych to:

100130991299812897127961269512594124931230.115

icn

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

i=8n=38

Studium przypadku

Z wielu powodów (patrz przypisy 1 i 2) właściwy rój kooperacyjny nigdy nie będzie konkurował w oficjalnych grach. W związku z tym w tej sekcji podsumuję wyniki jednej z moich własnych symulacji.

W tej symulacji uruchomiono 10000 gier przy użyciu 38 innych botów, które zostały tu zamieszczone podczas ostatniego sprawdzania, oraz 2900 botów, które miały CooperativeSwarmBot jako klasę nadrzędną. Kontroler poinformował, że 9051 z 10000 gier (90,51%) zakończyło się w 200 rundach, co jest dość bliskie prognozie, że 90% gier będzie współpracować. Wdrożenie tych botów było banalne; inne niż CooperativeSwarmBot wszyscy przyjęli tę formę:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

Mniej niż 3% botów miało procent wygranych poniżej 80%, a nieco ponad 11% botów wygrywało każdą grę, w którą grały. Mediana procent wygranych 2900 botów w roju wynosi około 86%, co jest oburzająco dobre. Dla porównania, najlepsze wyniki w bieżącej oficjalnej tabeli liderów wygrywają mniej niż 22% swoich gier. Nie mogę zmieścić pełnej listy roju spółdzielczego w maksymalnej dozwolonej długości dla odpowiedzi, więc jeśli chcesz zobaczyć, że musisz iść tutaj: https://pastebin.com/3Zc8m1Ex

Ponieważ każdy bot grał średnio w około 27 grach, szczęście odgrywa stosunkowo dużą rolę, gdy patrzysz na wyniki dla poszczególnych botów. Ponieważ nie wdrożyłem jeszcze zaawansowanej strategii dla gier niewspółpracujących, większość innych botów skorzystała drastycznie z gry przeciwko rojowi kooperatywnemu, osiągając nawet średni wskaźnik wygranych roju kooperacyjnego wynoszący 86%.

Pełne wyniki dla botów, których nie ma w roju, wymieniono poniżej; są dwa boty, których wyniki, moim zdaniem, zasługują na szczególną uwagę. Po pierwsze, StopBot nie wygrał żadnych gier. Jest to szczególnie tragiczne, ponieważ rój spółdzielczy faktycznie używał dokładnie tej samej strategii, co StopBot; spodziewałbyś się, że StopBot wygra osiem gier przez przypadek, a trochę więcej, ponieważ rój kooperatywny jest zmuszony dać swoim przeciwnikom pierwszy ruch. Drugim interesującym rezultatem jest jednak to, że ciężka praca PointsAreForNerdsBot ostatecznie się opłaciła: współpracowała z rojem i wygrała każdą grę!

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

Wady

Podejście oparte na współpracy ma kilka wad. Po pierwsze, grając przeciwko botom niewspółpracującym, boty kooperacyjne nigdy nie uzyskują przewagi w pierwszej turze, ponieważ kiedy grają jako pierwsze, nie wiedzą jeszcze, czy ich przeciwnicy są skłonni do współpracy, a zatem nie mają wyboru, jak tylko zdobyć wynik zero. Podobnie ta strategia współpracy jest wyjątkowo podatna na wykorzystywanie przez złośliwe boty; na przykład podczas gry kooperacyjnej bot, który gra ostatni w ostatniej rundzie, może natychmiast zatrzymać rzucanie, aby wszyscy inni przegrali (zakładając oczywiście, że ich pierwszy rzut nie był szóstką).

Współpracując, wszystkie boty mogą osiągnąć optymalne rozwiązanie o 100% wygranej. Jako taki, jeśli wskaźnik wygranych byłby jedyną rzeczą, która się liczyła, wówczas współpraca byłaby stabilną równowagą i nie miałaby się czym martwić. Jednak niektóre boty mogą nadawać priorytet innym celom, takim jak osiągnięcie szczytu tabeli liderów. Oznacza to, że istnieje ryzyko, że inny bot może uszkodzić się po twojej ostatniej turze, co stwarza dla ciebie zachętę do tego, by najpierw go wykonać. Ponieważ konfiguracja tych zawodów nie pozwala nam zobaczyć, co zrobili nasi przeciwnicy w swoich poprzednich grach, nie możemy karać osób, które uciekły. Współpraca zatem jest ostatecznie niestabilną równowagą skazaną na niepowodzenie.

Przypisy

[1]: Głównymi powodami, dla których nie chcę przesyłać tysięcy botów zamiast tylko dwóch, jest to, że spowolniłoby to symulację o współczynnik rzędu 1000 [2], a to spowodowałoby znaczny bałagan z wygrać procent, ponieważ inne boty prawie wyłącznie grają przeciwko rojowi, a nie sobie nawzajem. Ważniejsze jest jednak to, że nawet gdybym chciał, nie byłbym w stanie zrobić tylu botów w rozsądnych ramach czasowych bez złamania zasady, że „bot nie może wdrożyć dokładnie takiej samej strategii jak istniejący, umyślnie lub przypadkowo ”.

[2]: Myślę, że istnieją dwa główne powody spowolnienia symulacji podczas prowadzenia roju kooperacyjnego. Po pierwsze, więcej botów oznacza więcej gier, jeśli chcesz, aby każdy bot grał w tej samej liczbie gier (w studium przypadku liczba gier różni się około 77 razy). Po drugie, gry kooperacyjne trwają dłużej, ponieważ trwają przez pełne 200 rund, aw ciągu jednej rundy gracze muszą grać w nieskończoność. W mojej konfiguracji symulowanie gier trwało około 40 razy: studium przypadku zajęło nieco ponad trzy minuty, aby uruchomić 10000 gier, ale po usunięciu roju kooperacyjnego skończy 10000 gier w zaledwie 4,5 sekundy. Szacuję, że pomiędzy tymi dwoma przyczynami dokładne mierzenie wydajności botów zajęłoby około 3100 razy dłużej, gdy rój konkuruje w porównaniu do tego, kiedy nie ma.

Einhaender
źródło
4
Łał. Witamy w PPCG. To jest dość pierwsza odpowiedź. Tak naprawdę nie planowałem takiej sytuacji. Z pewnością znalazłeś lukę w przepisach. Nie jestem do końca pewien, jak powinienem to ocenić, ponieważ twoja odpowiedź to raczej zbiór botów niż pojedynczy bot. Jednak jedyne, co powiem teraz, to to, że to niesprawiedliwe, że jeden uczestnik kontrolowałby 98,7% wszystkich botów.
maks.
2
Tak naprawdę nie chcę, aby zduplikowane boty brały udział w oficjalnej konkurencji; dlatego sam przeprowadziłem symulację, zamiast przesyłać tysiące prawie identycznych botów. Poprawię swoje zgłoszenie, aby było to bardziej jasne.
Einhaender
1
Gdybym spodziewał się takiej odpowiedzi, zmieniłbym gry, które przechodzą do 200 rund, aby nie dawały punktów graczom. Jednak, jak zauważyłeś, istnieje zasada tworzenia identycznych botów, które sprawiłyby, że ta strategia byłaby niezgodna z regułami. Nie zamierzam zmieniać zasad, ponieważ byłoby to niesprawiedliwe dla każdego, kto stworzył bota. Jednak koncepcja współpracy jest bardzo interesująca i mam nadzieję, że zgłoszono inne boty, które wdrażają strategię współpracy w połączeniu z własną unikalną strategią.
maks.
1
Myślę, że twój post jest bardziej czytelny po dokładniejszym przeczytaniu.
maks.
Ile istniejących botów musiałoby owinąć swój kod w ramy współpracy, aby większość z nich uzyskała zysk netto w rankingu? Moje naiwne przypuszczenie wynosi 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

Po prostu spróbuj wszystkich GoToNBot, a 20, 22, 24 gra najlepiej. Nie wiem dlaczego.


Aktualizacja: zawsze zatrzymaj rzut, jeśli uzyskasz wynik 40 lub więcej.

tsh
źródło
Eksperymentowałem również z tego rodzaju botami. Najwyższy średni wynik na rundę występuje, gdy bot przechodzi do 16, ale zakładam, że „gra końcowa” powoduje, że 20-bot wygrywa częściej.
maks.
@maxb Nie tak, 20 nadal będzie najlepszym bez „końca gry” w moim teście. Może przetestowałeś go na starej wersji kontrolera.
tsh
Przed zaprojektowaniem tego wyzwania przeprowadziłem osobny test, w którym obliczyłem średni wynik na rundę dla dwóch taktyk w moim poście („rzut x razy” i „rzut do x wyniku”), a maksimum, które znalazłem, to 15-16 . Chociaż moja próbka mogła być zbyt mała, zauważyłem niestabilność.
maks.
2
Zrobiłem z tym kilka testów i doszedłem do wniosku, że 20 działa dobrze, ponieważ jest 40/2. Chociaż nie jestem do końca pewien. Kiedy ustawiłem end_scorena 4000 (i zmieniłem twojego bota, aby używał tego w targetobliczeniach), 15-16 botów było znacznie lepszych. Ale jeśli w grze chodziło tylko o zwiększenie twojego wyniku, byłoby to banalne.
maxb
1
@ maxb Jeśli end_scorejest 4000, prawie niemożliwe jest zdobycie 4000 przed 200 turami . A gra jest po prostu tym, który uzyskał najwyższy wynik w 200 turach. I przystanek na 15 powinien działać, ponieważ tym razem strategia najwyższego wyniku w jednej turze jest taka sama jak najwyższy wynik w 200 turach.
tsh
10

Wałek adaptacyjny

Zaczyna się bardziej agresywnie i uspokaja pod koniec rundy.
Jeśli wierzy, że wygrywa, rzuć dodatkowy czas na bezpieczeństwo.

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
Emigna
źródło
Świetne pierwsze zgłoszenie! Uruchomię go przeciwko moim botom, które napisałem do testowania, ale zaktualizuję rekord, gdy pojawi się więcej botów.
maks.
Przeprowadziłem kilka testów z niewielkimi modyfikacjami twojego bota. lim = max(min(self.end_score - scores[self.index], 24), 6)podniesienie maksimum do 24 i dodanie minimum 6 zarówno zwiększają procent zwycięstwa samodzielnie, a nawet bardziej łącznie.
AKroell,
@AKroell: Cool! Zamierzałem zrobić coś podobnego, aby upewnić się, że potoczy się kilka razy na końcu, ale nie miałem jeszcze czasu, aby to zrobić. Dziwnie jednak wydaje się, że działa gorzej z tymi wartościami, gdy wykonuję 100 000 biegów. Testowałem jednak tylko z 18 botami. Może powinienem zrobić kilka testów ze wszystkimi botami.
Emigna,
5

Alfa

class Alpha(Bot):
    def make_throw(self, scores, last_round):
        # Throw until we're the best.
        while scores[self.index] + sum(self.current_throws) <= max(scores):
            yield True

        # Throw once more to assert dominance.
        yield True
        yield False

Alfa nigdy nie ustępuje nikomu. Tak długo, jak istnieje bot z wyższym wynikiem, będzie się toczył.

Mnemoniczny
źródło
Ze względu na to, jak yielddziała, jeśli zacznie się toczyć, nigdy się nie zatrzyma. Będziesz chciał zaktualizować my_scorew pętli.
Spitemaster,
@Spitemaster Naprawiono, dziękuję.
Mnemonic
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

Chodzi o to, że inne boty mogą tracić punkty, więc bycie na drugim miejscu nie jest złe - ale jeśli jesteś bardzo w tyle, równie dobrze możesz popsuć się.

Stuart Moore
źródło
1
Witamy w PPCG! Przeglądam twoje zgłoszenie i wydaje się, że im więcej graczy jest w grze, tym niższy procent wygranej dotyczy twojego bota. Nie mogę od razu powiedzieć, dlaczego. Dzięki dopasowaniu botów 1vs1 zyskujesz 10% wygranej. Pomysł brzmi obiecująco, a kod wygląda poprawnie, więc nie mogę tak naprawdę powiedzieć, dlaczego twój współczynnik wygranych nie jest wyższy.
maks.
6
Mam spojrzał na zachowanie, a linia ta nie mylić mnie: 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. Nawet jeśli twój bot jest na prowadzeniu po 7 rzutach, trwa do momentu, aż osiągnie 6. Kiedy piszę to, odkryłem problem! scoresZawierają jedynie łączne wyniki, a nie przypadki umrzeć bieżącej rundzie. Powinieneś go zmodyfikować current_score = scores[self.index] + sum(self.current_throws).
maxb
Dzięki - wprowadzę tę zmianę!
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

Chcemy iść na całość lub do domu, prawda? GoHomeBot przeważnie po prostu wraca do domu. (Ale robi zaskakująco dobrze!)

Mistrz Spitemaster
źródło
Ponieważ ten bot zawsze otrzymuje 40 punktów, nigdy nie będzie miał żadnych punktów na scoresliście. Był już taki bot (GoToEnd), ale David usunął odpowiedź. Zastąpię tego bota twoim.
maks.
1
To całkiem zabawne, widząc rozwinięte statystyki tego bota: Z wyjątkiem punktówAreForNerds i StopBot, ten bot ma najniższą średnią liczbę punktów, a mimo to ma niezły współczynnik wygranych
Belhenix
5

Zapewnić

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

Upewnij się, że pożycza pomysły od GoTo20Bot. Dodaje koncepcję, którą zawsze bierze pod uwagę (gdy jest w Last_round lub osiąga 40), że istnieją inne, które będą miały co najmniej jeden rzut. Tak więc bot próbuje wyprzedzić ich trochę, tak aby musieli nadrobić zaległości.

Dirk Herrmann
źródło
4

Roll6TimesV2

Nie bije obecnie najlepszych, ale myślę, że będzie lepiej z większą liczbą botów w grze.

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

Nawiasem mówiąc, naprawdę świetna gra.

itsmephil12345
źródło
Witamy w PPCG! Bardzo imponujące nie tylko dla pierwszego wyzwania KotH, ale również dla twojej pierwszej odpowiedzi. Cieszę się, że gra ci się podobała! Dużo dyskutowałem na temat najlepszej taktyki w grze po wieczorze, kiedy grałem, więc wydawało się, że jest to idealne wyzwanie. Jesteś obecnie na trzecim miejscu z 18.
maks.
4

StopBot

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

Dosłownie tylko jeden rzut.

Jest to równoważne z Botklasą podstawową .

Zacharý
źródło
1
Nie przepraszam! Przestrzegasz wszystkich zasad, ale obawiam się, że twój bot nie jest strasznie skuteczny ze średnią 2,5 punktu na rundę.
maxb
1
Wiem, że ktoś musiał opublikować tego bota. Zdegenerowane boty do utraty.
Zacharý
5
Powiedziałbym, że jestem pod wrażeniem tego, że bot zapewnił dokładnie jedną wygraną w ostatniej symulacji, co dowodzi, że nie jest to całkowicie bezużyteczne.
maks.
2
TO WYGRAŁO GRĘ ?! To zaskakujące.
Zacharý
3

BringMyOwn_dice (BMO_d)

Ten bot uwielbia kości, przynosi 2 (wydaje się, że wykonuje najlepsze) własne kości. Przed rzuceniem kostkami w rundzie rzuca swoimi 2 kostkami i oblicza ich sumę, jest to liczba rzutów, które zamierza wykonać, rzuca tylko, jeśli nie ma jeszcze 40 punktów.

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
ბიმო
źródło
2
Myślałem o losowym bocie używającym rzutu monetą, ale jest to bardziej zgodne z duchem wyzwania! Myślę, że dwie kości działają najlepiej, ponieważ zdobywasz najwięcej punktów na rundę, gdy rzucisz kostką 5-6 razy, zbliżając się do średniego wyniku przy rzucaniu dwiema kostkami.
maks.
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
Peter Taylor
źródło
# Must throw at least oncejest niepotrzebny - rzuca raz przed wywołaniem bota. Twój bot zawsze rzuci minimum dwa razy.
Spitemaster,
Dzięki. Zostałem wprowadzony w błąd przez nazwę metody.
Peter Taylor
@PeterTaylor Dziękujemy za przesłanie! Nazwałem tę make_throwmetodę wcześnie, kiedy chciałem, aby gracze mogli pominąć swoją turę. Wydaje mi się, że bardziej odpowiednie byłoby keep_throwing. Dzięki za opinie w piaskownicy, naprawdę pomogło uczynić z tego właściwe wyzwanie!
maks.
3

Idź na początku

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

Pomysł: staraj się wygrywać duże na wczesnym rzucie (do 25), a następnie skradaj się stamtąd 2 rzuty na raz.

Stuart Moore
źródło
3

BinaryBot

Stara się zbliżyć do wyniku końcowego, aby jak tylko ktoś uruchomił ostatnią rundę, mógł pokonać swój wynik za zwycięstwo. Cel zawsze znajduje się w połowie drogi między aktualnym wynikiem a wynikiem końcowym.

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
Kain
źródło
Ciekawe, Hesitaterównież odmawia przekroczenia linii jako pierwszej. Musisz otoczyć swoją funkcję classmateriałami.
Christian Sievers,
3

PointsAreForNerdsBot

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

To nie wymaga wyjaśnienia.

OneInFiveBot

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

Toczy się dalej, dopóki nie rzuci piątki na swojej 5-stronnej kości. Pięć to mniej niż sześć, więc MUSI WYGRAĆ!

The_Bob
źródło
2
Witamy w PPCG! Jestem pewien, że jesteś tego świadomy, ale twój pierwszy bot jest dosłownie najgorszym botem w tej konkurencji! To OneInFiveBotfajny pomysł, ale myślę, że cierpi w końcowej fazie gry w porównaniu do niektórych bardziej zaawansowanych botów. Nadal świetne zgłoszenie!
maks.
2
OneInFiveBotjest dość ciekawy sposób, który konsekwentnie ma najwyższy ogólny wynik osiągnięty.
AKroell,
1
Dzięki za podanie StopBotworka treningowego: P. OneInFiveBot jest naprawdę fajną, fajną robotą!
Zacharý
@maxb Tak, tam mam imię. Szczerze mówiąc, nie testowałem OneInFiveBoti robi to znacznie lepiej niż się spodziewałem
The_Bob
3

LizduadacBot

Próbuje wygrać w 1 kroku. Warunek końcowy jest nieco arogancki.

To także mój pierwszy post (i jestem nowy w Pythonie), więc jeśli pobiję „PointsAreForNerdsBot”, będę szczęśliwy!

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
lizduadac
źródło
Witamy w PPCG (i witamy w Python)! Trudno ci byłoby przegrać PointsAreForNerdsBot, ale twój bot radzi sobie całkiem dobrze. Zaktualizuję wynik albo dziś wieczorem, albo jutro, ale twoja wygrana wynosi około 15%, czyli więcej niż średnio 12,5%.
maxb
Przez „trudny czas” rozumieją, że jest to niemożliwe (chyba że bardzo źle zrozumiałem)
Zacharý
@maxb Właściwie nie sądziłem, że wskaźnik wygranych będzie tak wysoki! (Nie przetestowałem tego lokalnie). Zastanawiam się, czy zmiana 50 na nieco wyższą / niższą zwiększyłaby współczynnik wygranych.
lizduadac
3

SlowStart

Ten bot implementuje algorytm TCP Slow Start. Dostosowuje liczbę rzutów ( ani ) zgodnie ze swoją poprzednią turą: jeśli nie rzucił 6 w poprzedniej turze, zwiększa wartość nor dla tej tury; podczas gdy zmniejsza, a nawet jeśli tak.

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
całkiem proste
źródło
Witamy w PPCG! Ciekawe podejście, nie wiem, jak wrażliwe jest na przypadkowe fluktuacje. Dwie rzeczy, które są potrzebne, aby uruchomić to: def updateValues():powinny byćdef updateValues(self): (lub def update_values(self):jeśli chcesz śledzić PEP8). Po drugie, wywołanie updateValues()powinno zamiast tego być self.updateValues()(lub self.update_vales()).
maks.
2
Myślę też, że musisz zaktualizować izmienną w pętli while. W tej chwili twój bot całkowicie przechodzi przez pętlę while lub utknął w pętli while, dopóki nie osiągnie 6.
maks.
W bieżącym rankingu pozwoliłem sobie na wprowadzenie tych zmian. Myślę, że możesz eksperymentować z wartością początkową self.nori zobaczyć, jak wpływa ona na wydajność twojego bota.
maks.
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                                               

Prescience zwykle wygrywa - ale przeznaczenia nie zawsze można uniknąć.
Wielkie i tajemnicze są sposoby Shai-Huluda!


We wczesnych dniach tego wyzwania (tj. Przed NeoBotopublikowaniem) napisałem prawie trywialnego Oraclebota:

    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

ale nie opublikowałem tego, ponieważ nie sądziłem, że to wystarczająco interesujące;) Ale kiedy NeoBotprzeszedłem na prowadzenie, zacząłem myśleć o tym, jak pokonać swoją doskonałą zdolność przewidywania przyszłości. Oto cytat z Dune; właśnie wtedy Paul Atreides, Kwisatz Haderach, stoi w miejscu, z którego może rozwinąć się nieskończona liczba różnych przyszłości:

Zdał sobie sprawę, że przewidywanie było iluminacją, która obejmowała granice tego, co ujawniało - jednocześnie źródło dokładności i znaczącego błędu. Wtrącił się rodzaj nieokreśloności Heisenberga: wydatek energii, która ujawniła to, co zobaczył, zmienił to, co zobaczył…… najdrobniejsze działanie - mrugnięcie okiem, nieostrożne słowo, niewłaściwe ziarno piasku - przesunęło gigantyczną dźwignię w poprzek znany wszechświat. Widział przemoc, której wynik podlegał tak wielu zmiennym, że jego najmniejszy ruch spowodował ogromne zmiany we wzorach.

Wizja sprawiła, że ​​chciał zamarznąć w bezruchu, ale to też było działanie z jego konsekwencjami.

Oto odpowiedź: przewidzieć przyszłość to ją zmienić; a jeśli jesteś bardzo ostrożny, to poprzez selektywne działanie lub bezczynność możesz to zmienić w korzystny sposób - przynajmniej przez większość czasu. Nawet ci, którzy KwisatzHaderachnie mogą uzyskać 100% wygranej!

Dani O
źródło
Wygląda na to, że bot ten zmienia stan generatora liczb losowych, aby uniknąć rzucania 6 lub przynajmniej go przewidzieć. To samo dotyczy HarkonnenBot. Jednak zauważam, że wskaźnik wygranych tych botów jest znacznie wyższy niż NeoBota. Czy aktywnie manipulujesz generatorem liczb losowych, aby zapobiec wyrzuceniu 6?
maxb
Och, w pierwszym czytaniu nie zauważyłem, że jest to nie tylko ładniejsze, NeoBotale i lepsze! Podoba mi się również, jak podajesz przykład tego, co powinno zrobić wszystko, używając losowości (szczególnie kontrolera): użyj własnej random.Randominstancji. Jak NeoBotwydaje się to nieco wrażliwy na zmiany nieokreślonych szczegółów implementacji regulatora.
Christian Sievers
@ maxb: HarkonnenBotnie dotyka RNG; nie przejmuje się w ogóle liczbami losowymi. Po prostu zatruwa wszystkie inne boty, a następnie podchodzi do linii mety tak wolno, jak to możliwe. Podobnie jak wiele kulinarnych przysmaków, zemsta jest potrawą najlepiej smakującą powoli, po długim i delikatnym przygotowaniu.
Dani O
@ChristianSievers: w przeciwieństwie do NeoBot (i HarkonnenBot), KwisatzHaderachopiera się tylko na jednym szczególe wdrożenia; w szczególności nie musi wiedzieć, w jaki sposób jest implementowany random.random (), tylko to, że kontroler go używa; D
Dani O
1
Przejrzałem wszystkie twoje boty. Postanowiłem traktować KwisatzHaderachi HarkonnenBottak samo jak NeoBot. Otrzymają swoje wyniki z symulacji z mniejszą liczbą gier i nie będą w oficjalnej symulacji. Jednak podobnie jak oni znajdą się na liście najlepszych wyników NeoBot. Głównym powodem, dla którego nie biorą udziału w oficjalnej symulacji, jest to, że zepsują inne strategie botów. Jednak. WisdomOfCrowdspowinien być odpowiedni do uczestnictwa, a ja jestem ciekawy nowych zmian, które wprowadziłeś!
maxb
2
class ThrowThriceBot(Bot):

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

Cóż, to jest oczywiste

michi7x7
źródło
Przeprowadziłem kilka eksperymentów z tą klasą botów (była to taktyka, której użyłem, gdy grałem w grę po raz pierwszy). Poszedłem wtedy z 4 rzutami, chociaż 5-6 ma wyższy średni wynik na rundę.
maks.
Gratulujemy również pierwszej odpowiedzi KotH!
maks.
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 działa tak, jakby zawsze była to ostatnia runda i ostatni bot: toczy się dalej, aż znajdzie się na czele. Nie chce też zadowolić się mniej niż 15 punktami, chyba że faktycznie jest to ostatnia runda lub nie osiągnie 40 punktów.

Mistrz Spitemaster
źródło
Ciekawe podejście Myślę, że twój bot cierpi, jeśli zacznie się opóźniać. Ponieważ szanse na zdobycie> 30 punktów w jednej rundzie są niskie, Twój bot ma większe szanse na utrzymanie obecnego wyniku.
maks.
1
Podejrzewam, że to cierpi z powodu tego samego błędu, który popełniłem (patrz komentarze NotTooFarBehindBot) - tak jak w ostatniej rundzie, jeśli nie wygrywasz, będziesz rzucał, dopóki nie otrzymasz 6 (wyniki [self.index] nigdy się nie aktualizują) Właściwie - czy masz tę nierówność w niewłaściwy sposób? max (wyniki) zawsze będzie wynosić> = wyniki [self.index]
Stuart Moore,
@StuartMoore Haha, tak, myślę, że masz rację. Dzięki!
Spitemaster,
Podejrzewam, że chcesz „i last_round” na 2. etapie, aby robić to, co chcesz - w przeciwnym razie 2. moment zostanie wykorzystany, niezależnie od tego, czy last_round jest prawdą
Stuart Moore,
3
To celowe. Zawsze stara się być na prowadzeniu, kiedy kończy swoją turę.
Spitemaster,
2

QuotaBot

Naiwny system „kwot”, który wdrożyłem, który w rzeczywistości wydawał się ogólnie dość wysoki.

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

FlipTack
źródło
if own_score mean + 5:daje mi błąd. Takżewhile sum(self.current_throws)
Spitemaster,
@Spitemaster błąd wklejenia do wymiany stosu, powinien działać teraz.
FlipTack,
@Spitemaster to dlatego, że były <i >symbole, które kolidowały z <pre>tagami, których
używałem
2

Oczekiwania Bot

Po prostu gra prosto, oblicza oczekiwaną wartość rzutu kostką i robi to tylko wtedy, gdy jest dodatnia.

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

Miałem problem z uruchomieniem kontrolera, dostałem „NameError: nazwa„ bots_per_game ”nie jest zdefiniowana” na wielowątkowym, więc naprawdę nie mam pojęcia, jak to działa.

Kain
źródło
1
Myślę, że w końcu jest to odpowiednik bota „Idź do 16”, ale nie mamy jeszcze jednego z nich
Stuart Moore,
1
@StuartMoore That ... to bardzo prawdziwy punkt, tak
Cain,
Napotkałem problemy z kontrolerem, gdy uruchomiłem go na moim komputerze z systemem Windows. Jakoś działało dobrze na moim komputerze z systemem Linux. Aktualizuję kontroler i zaktualizuję post po zakończeniu.
maks.
@maxb Dzięki, prawdopodobnie coś o tym, które zmienne są dostępne w innym procesie. FYI również to zaktualizowało, popełniłem głupi błąd w związku z wydajnością: /
Cain
2

BlessRNG

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

BlessRNG FrankerZ GabeN BlessRNG

Dice Mastah
źródło
2

Czterdzieści jeden

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

Spróbuj zdobyć 14 punktów do ostatniej rundy, a następnie załóż, że wszyscy będą próbować zdobyć 14 punktów i spróbować związać ten wynik.

histocrat
źródło
Mam TypeError: unsupported operand type(s) for -: 'list' and 'int'twojego bota.
tsh,
Zakładam, że max_projected_scorepowinieneś być maksimum listy, a nie całej listy, czy mam rację? W przeciwnym razie dostaję ten sam problem co tsh.
maks.
Ups, edytowane do naprawy.
histocrat
2

Wahać się

Wykonuje dwa skromne kroki, a następnie czeka, aż ktoś przekroczy linię. Zaktualizowana wersja nie próbuje już pobić rekordu, chce go tylko osiągnąć - poprawiając wydajność, usuwając dwa bajty kodu źródłowego!

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
Christian Sievers
źródło
2

Buntownik

Ten bot łączy prostą strategię Hesitate z zaawansowaną strategią ostatniej rundy BotFor2X, próbuje zapamiętać, kim jest i szaleje, gdy stwierdzi, że żyje w iluzji.

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)
Christian Sievers
źródło
Cóż, to dość eleganckie :) Gratulujemy zdobycia zarówno pierwszego, jak i drugiego miejsca w konkursie głównym!
Dani O
Oczywiście poprawiłem, HarkonnenBotaby Rebelnie można było już samemu uwolnić się od trucizny;) i poprawiłem, TleilaxuBotaby Rebelnie wykrywał już więcej!
Dani O
1

Weź pięć

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

W połowie czasu wyrzucimy 5 przed 6. Kiedy to zrobimy, wypłacimy pieniądze.

Mnemoniczny
źródło
Jeśli zamiast tego zatrzymamy się na 1, spowoduje to wolniejszy postęp, ale jest większe prawdopodobieństwo, że dojdzie do 40 w jednym obrocie.
Mnemoniczny
W moich testach TakeOne zdobył 20,868 punktów na rundę w porównaniu do 24,262 punktów TakeFive na rundę (a także przyniósł współczynnik wygranych z 0,291 do 0,259). Więc nie sądzę, żeby było warto.
Spitemaster,
1

Pościgowy

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 próbuje dogonić pozycję pierwszą. Jeśli to ostatnia runda, desperacko stara się zdobyć co najmniej 50 punktów. Na wszelki wypadek rzuca co najmniej cztery razy, bez względu na wszystko

[edytuj 1: dodano strategię zdobywania złota w ostatniej rundzie]

[edycja 2: zaktualizowana logika, ponieważ błędnie myślałem, że bot uzyska wynik 40, a nie tylko najwyższy wynik]

[edytuj 3: Sprawił, że ścigający był trochę bardziej defensywny w końcowej grze]

AKroell
źródło
Witamy w PPCG! Świetny pomysł, aby nie tylko spróbować nadrobić zaległości, ale także zaliczyć pierwsze miejsce. W tej chwili przeprowadzam symulację i życzę powodzenia!
maks.
Dzięki! Początkowo próbowałem przekroczyć poprzedniego lidera o ustaloną kwotę (wypróbowane wartości od 6 do 20), ale okazuje się, że po prostu lepiej rzucam dwa razy więcej jarmarków.
AKroell,
@JonathanFrech dzięki, naprawiono
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

Para botów przynosi własne zestawy kości i rzuca nimi, aby przewidzieć przyszłość. Jeśli jeden ma 6, zatrzymują się, FutureBot nie pamięta, która z dwóch kości była na następny rzut, więc się poddaje.

Zastanawiam się, co da radę.

Jak na mój gust OneStepAhead jest trochę zbyt podobny do OneInFive, ale chcę też zobaczyć, jak się ma do FutureBot i OneInFive.

Edycja: Teraz przestają działać po trafieniu 45

William Porter
źródło
Witamy w PPCG! Twój bot zdecydowanie gra w duchu gry! Później wieczorem przeprowadzę symulację.
maks.
Dzięki! Jestem ciekawy, jak dobrze sobie poradzi, ale domyślam się, że będzie to słaba strona.
William Porter