骰子游戏,但避免数字6 [关闭]


58

比赛结束了!

比赛结束了!最终模拟是在夜间进行的,总共场比赛。获奖者是Christian Sievers和他的机器人OptFor2X。克里斯蒂安·西弗斯(Christian Sievers)也与Rebel一起获得第二名。恭喜你!您可以在下面看到比赛的官方高分列表。3108

如果您仍然想玩游戏,欢迎使用下面发布的控制器,并使用其中的代码来创建自己的游戏。

骰子

我受邀玩从未听说过的骰子游戏。规则很简单,但我认为这对于KotH挑战将是完美的。

规则

游戏开始

骰子绕着桌子转,每次轮到您时,您都可以随意扔骰子多次。但是,您必须至少抛出一次。您可以跟踪本轮的所有掷出的总和。如果您选择停止,则该回合的分数将添加到您的总分数中。

那么,为什么要停止扔骰子呢?因为如果您得到6,则整个回合的分数将变为零,并且骰子会继续传递。因此,最初的目标是尽快提高您的分数。

谁是赢家?

当桌子周围的第一个玩家达到40分或以上时,最后一轮开始。一旦上一轮开始,除了发起上一轮的人以外的所有人都将获得另一回合。

上一轮的规则与其他任何轮次相同。您选择继续投掷或停止。但是,您知道,如果您没有获得比上一轮比赛之前更高的分数,那么您就没有机会获胜。但是,如果您走得太远,那么您可能会得到6。

但是,还有另一条规则需要考虑。如果您的当前总得分(您的上一得分+您当前的总得分)为40或更高,并且您打6,则您的总得分将设置为0。这意味着您必须重新开始。如果您在当前总得分为40或更高时达到6,则游戏将继续正常进行,除非您现在排在最后。重设总分时不会触发最后一轮。您仍然可以赢得一轮比赛,但这确实更具挑战性。

胜者是最后一轮结束后得分最高的球员。如果两个或两个以上的玩家共享相同的分数,则将全部计为胜利者。

附加规则是游戏最多可以持续200回合。这是为了防止多个机器人基本上一直抛出直到它们达到6保持当前分数的情况。第199轮通过后,last_round将其设置为true,然后再玩一轮。如果游戏进行200轮,则得分最高的机器人(即使机器人得分不超过40分)也将是获胜者。

概括

  • 每回合您都会掷骰子直到选择停止或得到6
  • 您必须掷一次骰子(如果您的第一掷是6,则您的回合立即结束)
  • 如果您得到6,则您的当前分数将设置为0(而不是总分数)
  • 您在每个回合后将当前分数添加到总分数中
  • 当漫游器结束转弯时,其总分至少达到40分时,其他所有人将获得最后一转
  • 如果您当前的分为而您得到6,则您的总得分将设为0,并且您的回合结束了40
  • 发生上述情况时,不会触发最后一轮
  • 最后一轮比赛总得分最高的人是获胜者
  • 如果有多个获胜者,则全部计为获胜者
  • 游戏最多持续200回合

澄清分数

  • 总分:您在前几轮中保存的分数
  • 当前分数:当前回合的分数
  • 当前总分:以上两个分数之和

你如何参加

要参加KotH挑战,您应该编写一个Python类,该类继承自Bot。您应该实现以下功能:make_throw(self, scores, last_round)。轮到您时该函数将被调用,并且您的第一个掷球不是6。要继续掷球,您应该yield True。要停止投掷,您应该yield False。每次抛出之后,update_state都会调用父函数。因此,您可以使用变量访问当前回合的掷球self.current_throws。您还可以使用来访问自己的索引self.index。因此,要查看自己的总成绩,您可以使用scores[self.index]。您也可以使用来访问end_score游戏的self.end_score,但您可以放心地假定此挑战将是40。

您可以在类中创建帮助器函数。您也可以覆盖Bot父类中现有的函数,例如,如果要添加更多的类属性。除yield True或之外,您不得以任何其他方式修改游戏的状态False

您可以从这篇文章中自由地寻求灵感,并复制我在此处包括的两个机器人中的任何一个。但是,恐怕它们并不是特别有效...

关于允许其他语言

在沙盒和第十九字节中,我们都讨论了有关允许使用其他语言提交的问题。在阅读了这样的实现并听取了双方的争论之后,我决定将这一挑战仅限于Python。这是由于两个因素造成的:支持多种语言所需的时间,以及此挑战的随机性,需要大量的迭代才能达到稳定性。我希望您仍然会参与其中,如果您想学习一些Python来应对这一挑战,我将尽可能多地在聊天中提供帮助。

如有任何疑问,您可以在聊天室中写下此挑战。到时候那里见!

规则

  • 允许并鼓励破坏行为。也就是说,对其他玩家的破坏
  • 任何试图修改控制器,运行时或其他提交内容的尝试都将被取消资格。所有提交都应仅使用输入的输入和存储。
  • 任何使用超过500MB内存做出决定的漫游器都会被取消资格(如果您需要那么多内存,则应重新考虑选择)
  • 机器人不得有意或无意地实施与现有机器人完全不同的策略。
  • 您可以在挑战期间更新您的机器人。但是,如果您的方法不同,也可以发布另一个bot。

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

这个机器人会继续下去,直到它有圆形的得分至少为10,或者它抛出一个6.注意你不需要任何逻辑来处理投掷6.还请注意,如果你第一次投掷是6,make_throw是从未致电,因为您的回合马上结束了。

对于那些不熟悉Python(yield但不熟悉该概念)但又想尝试一下的人来说,yield关键字在某些方面类似于return,但在其他方面有所不同。您可以在此处阅读有关此概念的信息。基本上,一旦您yield,您的功能就会停止,并且您所yield编辑的值将被发送回控制器。在那里,控制器将处理其逻辑,直到您的机器人做出另一个决定为止。然后,控制器将掷骰子发送给您,您的make_throw函数将继续执行(如果在此位置之前停止,基本上在上一条yield语句之后的行)。

这样,游戏控制器可以更新状态,而无需为每个骰子投掷单独的机器人功能调用。

规格

您可以使用中提供的任何Python库pip。为了确保我能获得一个很好的平均值,您每轮有100毫秒的时间限制。如果您的脚本比以前更快,我将非常高兴,以便我可以进行更多的回合。

评价

为了找到获胜者,我将所有机器人以8人为一组随机运行。如果提交的课程少于8个,我将以4人为一组随机运行它们,以避免始终在每个回合中拥有所有机器人。我将进行大约8个小时的模拟,获胜者将是获胜率最高的机器人。我将在2019年初开始最终模拟,给您整个圣诞节编写机器人代码!初步的最终日期是1月4日,但是如果时间太短,我可以将其更改为以后的日期。

在此之前,我将尝试使用30-60分钟的CPU时间进行每日模拟,并更新记分板。这不是官方的分数,但可以作为指南,指导哪些机器人表现最好。但是,随着圣诞节的到来,我希望您能了解到我不会一直有空。我将尽力进行模拟并回答与挑战有关的任何问题。

自己测试

如果您想运行自己的仿真,这是运行仿真的控制器的完整代码,其中包括两个示例机器人。

控制者

这是此挑战的更新控制器。它支持ANSI输出,多线程并通过AKroell收集其他统计信息!当我对控制器进行更改时,一旦文档完成,我将更新该帖子。

多亏了BMO,控制器现在可以使用-d标记从这篇文章中下载所有机器人了。此版本中的其他功能未更改。这应该确保尽快模拟所有最新更改!

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

如果要访问此挑战的原始控制器,则可在编辑历史记录中找到它。新的控制器具有与运行游戏完全相同的逻辑,唯一的区别是性能,统计信息收集和更漂亮的打印。

机器人

在我的机器上,僵尸程序保存在file中forty_game_bots.py。如果对文件使用其他任何名称,则必须更新import控制器顶部的语句。

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

运行模拟

要运行模拟,请将上面发布的两个代码段保存到两个单独的文件中。我将它们另存为forty_game_controller.pyforty_game_bots.py。然后,您只需使用python forty_game_controller.pypython3 forty_game_controller.py取决于您的Python配置。如果要进一步配置仿真,请按照此处的说明进行操作,或者根据需要尝试修改代码。

游戏统计

如果您要制作的目标是特定分数的机器人,而没有考虑其他机器人,则以下是获胜分数百分比:

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

高分数

随着更多答案的发布,我将尝试使此列表保持更新。列表的内容将始终来自最新的模拟。僵尸程序ThrowTwiceBotGoToTenBot是以上代码中的僵尸程序,并用作参考。我用10 ^ 8个游戏进行了模拟,大约花了1个小时。然后我发现与我的10 ^ 7游戏相比,游戏达到了稳定性。但是,由于人们仍然在发布机器人程序,因此在响应频率下降之前,我不再做任何模拟。

我尝试添加所有新的漫游器,并添加对现有漫游器所做的任何更改。如果似乎我错过了您的漫游器或您进行的任何新更改,请在聊天中编写内容,并确保在下一次仿真中使用您的最新版本。

现在,有了AKroell,我们可以为每个机器人提供更多统计信息!三个新列包含所有游戏的最高分数,每场游戏的平均分数以及每个机器人获胜时的平均分数。

正如评论中指出的那样,游戏逻辑存在问题,在某些情况下,游戏中具有较高索引的机器人将获得一轮额外的回合。现在,此问题已得到解决,下面的分数反映了这一点。

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

以下机器人(除外Rebel)是为了规矩而制作的,创作者已同意不参加正式比赛。但是,我仍然认为他们的想法富有创造力,值得一提。Rebel也在此列表中,因为它使用了一种聪明的策略来避免破坏活动,并且实际上在使用破坏性机器人时表现更好。

该机器人NeoBotKwisatzHaderach不遵守规则,但预测的随机生成器使用一个漏洞。由于这些机器人需要大量资源来进行模拟,因此我通过减少游戏数量的模拟来添加了其统计信息。该机器人HarkonnenBot通过禁用所有其他机器人来取得胜利,这完全违反了规则。

    Simulation or 300000 games between 52 bots completed in 66.2 seconds
    Each game lasted for an average of 4.82 rounds
    20709 games were tied between two or more bots
    0 games ran until the round limit, highest round was 31

    +-----------------------+----+--------+--------+------+------+-------+------+--------+
    |Bot                    |Win%|    Wins|  Played|   Max|   Avg|Avg win|Throws|Success%|
    +-----------------------+----+--------+--------+------+------+-------+------+--------+
    |KwisatzHaderach        |80.4|   36986|   46015|   214| 58.19|  64.89| 11.90|   42.09|
    |HarkonnenBot           |76.0|   35152|   46264|    44| 34.04|  41.34|  1.00|   83.20|
    |NeoBot                 |39.0|   17980|   46143|   214| 37.82|  59.55|  5.44|   50.21|
    |Rebel                  |26.8|   12410|   46306|    92| 20.82|  43.39|  3.80|   35.84|
    +-----------------------+----+--------+--------+------+------+-------+------+--------+

    +----------+-----+
    |Percentile|Score|
    +----------+-----+
    |     50.00|   45|
    |     75.00|   50|
    |     90.00|   59|
    |     95.00|   70|
    |     99.00|   97|
    |     99.90|  138|
    |     99.99|  214|
    +----------+-----+

2
因此,如果他们说“当玩家以至少40分的成绩结束转牌时,其他所有人都获得最后一轮”,则规则可能会更加清晰。通过指出并没有真正触发最后一轮的40来避免明显的冲突,它至少以40
结束。– aschepler

1
@aschepler,这是一个很好的公式,当我在计算机上时,我将编辑该帖子
maxb

2
@maxb我已经扩展了控制器,以添加与我的开发过程相关的更多统计信息:达到最高分数,达到平均分数和平均获胜分数gist.github.com/AwK/91446718a46f3e001c19533298b5756c
AKroell

2
这听起来很像一个非常有趣的骰子游戏,叫做Farkled en.wikipedia.org/wiki/Farkle
Caleb Jay

5
我投票结束这个问题,因为它实际上已经拒绝了新的答案(“比赛现在结束了!最终模拟在夜间进行,总共3 * 108场比赛”)
pppery

Answers:


6

OptFor2X

该机器人仅使用分数和最佳对手的分数来遵循该游戏两个玩家版本的最佳策略。在最后一轮中,更新的版本考虑了所有分数。

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

我将尽快检查实施情况。随着圣诞节的庆祝活动,可能要等到25日
maxb

您的机器人处于领先地位!同样,也没有必要使其运行得更快,它几乎与其他所有机器人做出决定一样快。
maxb

我不想让它更快。我已经做了我想做的事情-只能初始化一次-但一直在寻找一种更好的方式来做到这一点,尤其是在类外没有定义函数的情况下。我认为现在更好。
Christian Sievers

现在看起来好多了,好的工作!
maxb

祝贺您获得第一名和第二名!
maxb

20

NeoBot

相反,只能尝试去了解真相-没有汤匙

NeoBot偷看矩阵(也称为随机数),并预测下一个掷骰是否为6-开始做为6并不能做任何事情,但对躲避连胜者感到非常高兴。

NeoBot实际上并没有修改控制器或运行时,只是礼貌地要求库提供更多信息。

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

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

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

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

1
欢迎来到PPCG!这是一个非常令人印象深刻的答案。当我第一次运行它时,我为它使用的运行时间与所有其他机器人的运行时间相同而感到困扰。然后我看了赢率。规避规则真的很聪明。我将允许您的机器人参加比赛,但我希望其他人不要使用与此相同的战术,因为这违反了游戏的精神。
maxb

2
由于该机器人与第二名之间的差距非常大,再加上您的机器人需要大量计算这一事实,您是否接受我以较少的迭代次数进行仿真来找到您的获胜率,然后运行官方没有您的机器人进行模拟?
maxb

3
对我而言,我认为这很可能被取消资格,而且绝对不是游戏精神。话虽如此,这是上班的高潮,也是在python源代码中四处寻找乐趣的借口。
大部分时间为无害

2
谢谢!我认为其他任何机器人都不会接近您的得分。对于考虑实施此策略的其他人,请不要这样做。从现在开始,这种策略是违规的,NeoBot是唯一为了使比赛公平而允许使用的策略。
maxb

1
好吧,myBot击败了每个人,但这要好得多-我虽然如果我发布这样的机器人,我会得到-100而不是最佳成绩。
Jan Ivan

15

合作群

战略

我认为还没有其他人注意到此规则的重要性:

如果游戏进行200轮,则得分最高的机器人(即使机器人得分不超过40分)也将是获胜者。

如果每个机器人在崩溃之前一直滚动,那么在第200轮结束时每个人的得分都为零,每个人都将获胜!因此,“合作群”的策略是只要所有玩家的得分都为零,便进行合作,但如果有人得分,则正常比赛。

在这篇文章中,我将提交两个机器人:第一个是CooperativeSwarmBot,第二个是CooperativeThrowTwice。CooperativeSwarmBot是正式成为合作群的一部分的所有bot的基类,并且具有占位符行为,可以在合作失败时简单地接受其首次成功滚动。CooperativeSwarmBot具有CooperativeSwarmBot作为其父对象,并且在各方面都与之相同,除了它的非合作行为是制作两卷而不是一卷。在接下来的几天中,我将对这篇文章进行修改,以添加新的机器人,这些机器人使用的行为要比非合作型机器人更聪明。

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

分析

可行性

在这个游戏中很难合作,因为我们需要所有八名玩家的支持才能正常工作。由于每个机器人类别每个游戏仅限于一个实例,因此这是一个很难实现的目标。例如,从100个合作机器人和30个非合作机器人中选择8个合作机器人的几率是:

100130991299812897127961269512594124931230.115

icn

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

i=8n=38

案例分析

由于多种原因(请参见脚注1和2),适当的合作群永远不会参加正式比赛。因此,本节将总结自己的模拟结果之一。

使用我上次检查时在此处发布的其他38个机器人以及以CooperativeSwarmBot作为其父类的2900个机器人,该模拟运行了10000个游戏。管制员报告说,在10000场比赛中,有9051场比赛(占90.51%)以200局结束,这非常接近90%的比赛会进行合作的预测。这些机器人的实现很简单。除了CooperativeSwarmBot以外,它们都采用以下形式:

class CooperativeSwarm_1234(CooperativeSwarmBot):
    pass

不到3%的漫游器的胜率低于 80%,而刚过11%的漫游器赢得了他们玩过的每一场游戏。群中2900个机器人的中位获胜率约为86%,这是非常好的。相比之下,当前官方排行榜上表现最好的球员所赢得的比赛不到22%。我无法在允许的最大长度范围内容纳合作群的完整列表,因此,如果您要查看,则必须转到此处:https : //pastebin.com/3Zc8m1Ex

由于每个机器人平均玩了27场游戏,因此当您查看单个机器人的结果时,运气会发挥相对较大的作用。由于我尚未针对非合作游戏实施高级策略,因此大多数其他机器人都从与合作群竞争中受益匪浅,甚至实现了合作群的中位数胜率达到86%。

下面列出了不在群中的bot的完整结果;我认为有两个机器人的结果值得特别关注。首先,StopBot根本没有赢得任何比赛。这是特别悲惨的,因为合作群实际上使用了与StopBot完全相同的策略;您会期望StopBot会偶然赢得其八场比赛,还有更多,因为合作社团队被迫让对手采取第一步。然而,第二个有趣的结果是,PointsAreForNerdsBot的辛勤工作终于得到了回报:它与众多团队合作并成功赢得了每场比赛!

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

瑕疵

这种合作方式有两个缺点。首先,当与非合作型机器人对抗时,合作型机器人永远不会获得首轮优势,因为当他们先玩时,他们还不知道对手是否愿意合作,因此别无选择,只能获得分数为零。同样,这种合作策略极易受到恶意机器人的利用。例如,在合作游戏中,在最后一轮比赛中最后一局的机器人可以选择立即停止滚动,以使其他所有人都输掉(当然,假设他们的第一轮不是6)。

通过合作,所有漫游器都可以实现100%成功率的最佳解决方案。这样,如果赢率是唯一重要的事情,那么合作将是一个稳定的平衡,而不必担心。但是,某些漫游器可能会优先考虑其他目标,例如达到排行榜的顶部。这意味着在最后一轮之后,另一个机器人有可能出现缺陷,这有可能促使您首先缺陷。由于本次比赛的设置不允许我们看到对手在以前的比赛中所做的事情,因此我们不能惩罚有缺陷的个人。因此,合作最终是注定要失败的不稳定平衡。

脚注

[1]:为什么我不想提交数千个机器人,而不仅仅是两个机器人,主要原因是这样做会使仿真速度降低1000倍左右[2],并且这样做会严重干扰胜率与其他机器人几乎完全是在与群体竞争,而不是彼此竞争。但是,更重要的事实是,即使我愿意,我也无法在合理的时间内制造出许多机器人,而又不会违反“机器人不得实施与机器人完全相同的策略”这一原则的精神。有意或无意地存在”。

[2]:我认为有两个主要原因导致运行协作群时模拟速度变慢。首先,如果您希望每个机器人玩相同数量的游戏,则更多的机器人意味着更多的游戏(在案例研究中,游戏数量相差约77倍)。其次,合作游戏需要花费更长的时间,因为它们可以持续整整200轮比赛,而在一轮比赛中,玩家必须无限滚动。对于我的设置,模拟游戏需要花费大约40倍的时间:案例研究花了3分钟多的时间来运行10000场游戏,但是在删除合作团队之后,它仅需4.5秒即可完成10000场游戏。在这两个原因之间,我估计在没有竞争的情况下,准确衡量机器人的性能将花费大约3100倍的时间。


4
哇。欢迎来到PPCG。这是第一个答案。我不是真的在计划这种情况。您当然在规则中发现了漏洞。我不太确定该如何评分,因为您的回答是一系列机器人而不是一个机器人。但是,我现在唯一要说的是,一个参与者控制所有机器人的98.7%感觉不公平。
maxb

2
我实际上不希望重复的机器人参加正式比赛;这就是为什么我自己运行模拟而不是提交数千个几乎完全相同的机器人的原因。我将修改提交的内容以使其更加清晰。
Einhaender

1
如果我希望得到这样的答案,我将把比赛改为200回合,这样就不会给玩家得分。但是,正如您所注意到的,有一个创建相同机器人的规则,这会使该策略违反规则。我不会更改规则,因为这对制造机器人的每个人都是不公平的。但是,合作的概念非常有趣,我希望提交的其他Bot能够结合其自身的独特策略来实施合作策略。
maxb

1
我认为您的文章经过更彻底的阅读后很清晰。
maxb

为了让大多数机器人在排行榜中获得净收益,需要多少个现有机器人将其代码包装在此合作框架中?我天真的猜测是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

尝试一下全能GoToNBot,而20、22、24的表现最好。我不知道为什么


更新:如果得分达到40或更高,则始终停止投掷。


我也尝试过这类机器人。当机器人达到16时,发现每轮最高的平均得分,但是我假设“最终比赛”使20机器人赢得比赛的频率更高。
maxb

@maxb并非如此,在我的测试中,如果没有“终结游戏”,20仍然是最好的。也许您已经在旧版本的控制器上进行了测试。
tsh

在设计此挑战之前,我进行了一项单独的测试,在该计算中,我计算了帖子中两种战术(“掷x次”和“掷至x分数”)的每轮平均得分,发现的最高值为15-16 。尽管我的样本量可能太小,但我确实注意到了不稳定。
maxb

2
我对此进行了一些测试,我的结论很简单,因为20是40/2,所以效果很好。虽然我不确定。当我设置end_score为4000(并更改了您的机器人以在target计算中使用它)时,15-16机器人就好得多了。但是,如果游戏只是增加分数,那将是微不足道的。
maxb

1
@maxb如果end_score为4000,则几乎不可能在200转之前获得4000。而游戏就是谁在200回合中获得最高分。从此时开始,在15点停下应该会起作用的策略,因为这一回合中最高得分的策略与200回合中最高得分的策略相同。
tsh

10

自适应压路机

开始更具侵略性,并在回合结束前保持冷静。
如果它认为自己赢了,那就多花一点时间来保证安全。

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

伟大的第一次提交!我将针对编写用于测试的我的机器人运行它,但是当发布了更多的机器人时,我将更新高分。
maxb

我进行了一些测试,对您的漫游器进行了一些修改。lim = max(min(self.end_score - scores[self.index], 24), 6)将最高赔率提高到24,将最低赔率增加到6,这两个因素都会提高获胜率,甚至加在一起。
AKroell

@AKroell:太酷了!我打算做类似的事情以确保它在末尾滚动几次,但是我还没有花时间去做。不过,奇怪的是,当我进行100k运行时,这些值的表现似乎更差。我只测试了18个机器人。也许我应该对所有机器人进行一些测试。
Emigna '18

5

Α

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

阿尔法拒绝任何人都屈居第二。只要有一个分数更高的机器人,它就会持续滚动。


由于yield工作原理,如果它开始滚动,它将永远不会停止。您将要my_score在循环中进行更新。
Spitemaster '18

@Spitemaster固定,谢谢。
助记符

5

不是太远了

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

这个想法是其他机器人可能会丢分,因此获得第二名还不错-但是如果您落后很多,那您最好还是破产。


1
欢迎来到PPCG!我正在查看您提交的内容,看来游戏中的玩家越多,您的机器人的获胜率就越低。我不知道为什么马上就说。与机器人匹配1vs1,您将获得10%的获胜率。这个想法听起来很有希望,而且代码看起来正确,所以我不能真正说出为什么您的获胜率不高。
maxb

6
我已经调查的行为,这条线把我迷惑: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。即使您的机器人在投掷7球后仍处于领先地位,它仍会持续到击中6时为止。当我键入此代码时,我发现了问题所在!在scores只包含总分,而不是本轮模具的情况。您应该将其修改为current_score = scores[self.index] + sum(self.current_throws)
maxb

谢谢-会做出改变的!
斯图尔特·摩尔

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

我们想变大还是回家?GoHomeBot通常只是回家。(但效果很好!)


由于该机器人总是获得40分,因此scores列表中永远不会有任何分数。以前有一个这样的机器人(GoToEnd机器人),但大卫删除了他们的答案。我将用您的机器人代替。
maxb

1
看到这个机器人的扩展统计数据,这很有趣:除pointsAreForNerds和StopBot之外,该机器人的平均得分最低,但获胜率
很高

5

确保领先

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

确保领导者借鉴了GoTo20Bot的想法。它增加了它始终认为的概念(当进入last_round或达到40时),还有其他的至少要掷出一卷。因此,机器人试图领先于他们,以便他们必须赶上。


4

Roll6TimesV2

并没有超过目前的最好水平,但我认为如果有更多的机器人在玩,它会更好。

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

顺便说一句,真是太棒了。


欢迎来到PPCG!不仅对您的第一个KotH挑战,而且对于您的第一个答案,都给人留下深刻的印象。很高兴您喜欢这款游戏!晚上玩完游戏后,我就该游戏的最佳策略进行了很多讨论,因此它似乎很适合挑战。目前排在第三位你出的18
MAXB

4

StopBot

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

从字面上看只有一掷。

这等效于基Bot类。


1
不要后悔!您正在遵循所有规则,尽管我担心您的机器人在每轮平均2.5分的情况下并不十分有效。
maxb

1
我知道,尽管有人必须发布该机器人。退化的机器人的损失。
扎卡里

5
我要说的是,您的机器人在上一次模拟中获得了一次胜利,这给我留下了深刻的印象,证明这并不是完全没有用的。
maxb

2
它赢了游戏吗?令人惊讶。
扎卡里

3

BringMyOwn_dice(BMO_d)

这个机器人喜欢骰子,它拥有2个(似乎表现最好的骰子)。在掷骰子之前,它会掷出自己的2个骰子并计算其总和,这是它将执行的掷骰数,只有在尚未获得40分时才会掷骰。

class BringMyOwn_dice(Bot):

    def __init__(self, *args):
        import random as rnd
        self.die = lambda: rnd.randint(1,6)
        super().__init__(*args)

    def make_throw(self, scores, last_round):

        nfaces = self.die() + self.die()

        s = scores[self.index]
        max_scores = max(scores)

        for _ in range(nfaces):
            if s + sum(self.current_throws) > 39:
                break
            yield True

        yield False

2
我当时在考虑使用硬币翻转的随机机器人,但这对挑战来说更是精神!我认为两个骰子的表现最佳,因为当您掷骰5-6次时,每轮得分最高,接近两个骰子的平均得分。
maxb

3

足球

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

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

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

            yield True

        yield False

# Must throw at least once不需要-在调用您的漫游器之前会抛出一次。您的漫游器将始终至少抛出两次。
Spitemaster '18

谢谢。我被方法的名称误导了。
彼得·泰勒

@PeterTaylor感谢您的提交!make_throw当我希望玩家能够跳过转弯时,我便为该方法命名。我想一个更合适的名字是keep_throwing。感谢您在沙箱中提供的反馈意见,它确实使这成为一个适当的挑战!
maxb

3

早日大胆

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

概念:尝试在早期掷骰中获得大胜利(获得25分),然后一次从那里爬升2掷。


3

BinaryBot

尝试接近最终得分,以便一旦其他人触发了最后一轮,就可以击败他们的得分以获得胜利。目标始终在当前分数和结束分数之间。

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

有趣的是,Hesitate也拒绝先越线。您需要用这些class东西包围您的功能。
Christian Sievers

3

PointsAreForNerdsBot

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

这个不需要解释。

OneInFiveBot

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

一直滚动直到在自己的5面骰子上滚动5。五个小于六个,所以它必须赢!


2
欢迎来到PPCG!我确定您知道,但是您的第一个机器人实际上是这场比赛中最糟糕的机器人!这OneInFiveBot是一个很好的主意,但与某些更高级的机器人相比,它在最终游戏中会遭受损失。还是很棒的提交!
maxb

2
OneInFiveBot是,他始终具有达到总分最高的方式很有趣。
AKroell

1
感谢您给StopBot一个出气筒:P。OneInFiveBot实际上非常整洁,做得很好!
扎卡里

@maxb是的,这就是我的名字。老实说,我没有进行测试OneInFiveBot,它的性能比我预期的好得多
The_Bob


3

LizduadacBot

力争在第一步中获胜。结束条件有些复杂。

这也是我的第一篇文章(并且是Python的新手),所以,如果我击败“ PointsAreForNerdsBot”,我会很高兴!

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

欢迎使用PPCG(也欢迎使用Python)!您将很难输掉PointsAreForNerdsBot,但是您的机器人实际上表现不错。我会在今晚或明天更新分数,但您的获胜率约为15%,高于平均水平的12.5%。
maxb

所谓“困难时期”,他们的意思是这是不可能的(除非我误解了极大)
扎卡里

@maxb我实际上不认为胜率会那么高!(我没有在本地进行测试)。我不知道将50更改为更高/更低会提高胜率。
lizduadac

3

慢启动

该机器人实现了TCP慢启动算法。它根据上一轮调整掷骰数(nor):如果上一轮未掷出6,则增加本轮的nor;而它减少了没有减少。

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

欢迎来到PPCG!有趣的方法,我不知道它对随机波动有多敏感。进行此运行需要完成两件事:def updateValues():应该是def updateValues(self):(或者def update_values(self):如果您想遵循PEP8)。其次,呼叫updateValues()应改为self.updateValues()(或self.update_vales())。
maxb

2
另外,我认为您需要i在while循环中更新变量。现在,您的机器人要么完全通过了while循环,要么被困在while循环中,直到达到
6。– maxb

在当前的高分榜上,我自由地实施了这些更改。我认为您可以试验的初始值self.nor,看看它如何影响您的漫游器性能。
maxb

3

夸萨兹·哈德拉奇

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                                               

有先见之明通常会取胜-但命运总是无法避免的。
Shai-Hulud的方式真是伟大而神秘!


早在挑战的初期(即NeoBot发布之前),我就编写了一个几乎平凡的Oracle机器人:

    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

但是没有发布它,因为我认为它不够有趣;)但是一旦NeoBot成为领先者,我就开始考虑如何超越其完美的预测未来的能力。所以这是沙丘的报价;保罗·阿特雷德斯(Katisatz Haderach)站在一个联系上,无穷无尽的未来可以从这里展开:

他意识到,先知是结合了所揭示内容的局限性的照明-立即成为准确性和有意义的错误的来源。海森堡的一种不确定性介入了:能量消耗揭示了他所看到的东西,改变了他所看到的东西…………最微小的动作-眨眼,粗心的话语,放错了地方的沙粒-在巨大的杠杆上移动了巨大的杠杆已知的宇宙。他看到暴力行为的结果受多种变数的影响,以至于他的丝毫动作造成了模式的巨大转变。

异象使他想陷入僵局,但这也是行动及其后果。

因此,答案就在这里:预见未来就是改变它。如果您非常谨慎,则可以通过选择性的行动或不采取行动,以一种有利的方式进行更改-至少在大多数情况下。甚至KwisatzHaderach无法获得100%的获胜率!


看来该漫游器会更改随机数生成器的状态,以确保避免滚动6或至少会预期滚动。HarkonnenBot也是如此。但是,我注意到这些机器人的获胜率比NeoBot高得多。您是否正在积极操纵随机数生成器以防止其滚动6?
maxb

哦,在我的初读时,我没有注意到这不仅好于NeoBot而且还更好!我也喜欢您如何给出一个示例,说明使用随机性的一切(尤其是控制器)在此处应做的事情:使用您自己的random.Random实例。像一样NeoBot,这似乎对控制器未指定的实现细节的更改敏感。
Christian Sievers

@maxb:HarkonnenBot不触碰RNG;它根本不关心随机数。它只会毒害所有其他机器人,然后尽可能缓慢地爬到终点。像许多烹饪美食一样,复仇是经过长时间而精致的准备后最好慢慢品尝的菜。
Dani O

@ChristianSievers: NeoBot(和HarkonnenBot不同KwisatzHaderach它仅依赖于实现的一个细节;特别是,它不需要知道random.random()的实现方式,只需要控制器使用它即可; D
Dani O

1
我浏览了所有您的机器人。我已决定以KwisatzHaderachHarkonnenBot相同的方式对待NeoBot。他们将通过较少的游戏从模拟中获得分数,并且不会出现在官方模拟中。但是,它们最终会像一样进入高分列表NeoBot。他们没有出现在官方模拟中的主要原因是他们会弄乱其他机器人策略。然而。WisdomOfCrowds应该非常适合参与,我很好奇您对此所做的新更改!
maxb

2
class ThrowThriceBot(Bot):

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

好吧,那很明显


我已经用这类机器人进行了一些实验(这是我第一次玩游戏时使用的策略)。然后我投了4球,尽管5-6的平均得分更高。
maxb

另外,恭喜您首次回答KotH!
maxb

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

LastRound的行为就像永远都是最后一轮,也是最后一个机器人:它一直滚动直到领先。除非它实际上是最后一轮或达到40点,否则它也不想低于15分。


有趣的方法。我认为,如果您的机器人开始落后,那就会遭受重创。由于单轮获得> 30分的几率很低,因此您的机器人更有可能保持当前得分。
maxb

1
我怀疑这是因为我犯了同样的错误(请参阅NotTooFarBehindBot评论)-与上一轮一样,如果您没有赢球,您将继续投掷直到获得6分(分数[self.index]从未更新)-您是否有错误的不平等方式?max(scores)总是> = scores [self.index]
Stuart Moore

@StuartMoore哈哈,是的,我认为你是对的。谢谢!
Spitemaster '18

我怀疑您想在第二天使用“ and last_round”做您想要的事情-否则无论last_round是否为真,都会使用第二个while
Stuart Moore

3
那是故意的。当结束回合时,它总是试图处于领先地位。
Spitemaster '18

2

配额

我实施的一个简单的“配额”系统,实际上似乎在整体上得分很高。

class QuotaBot(Bot):
    def __init__(self, *args):
        super().__init__(*args)
        self.quota = 20
        self.minquota = 15
        self.maxquota = 35

    def make_throw(self, scores, last_round):
        # Reduce quota if ahead, increase if behind
        mean = sum(scores) / len(scores)
        own_score = scores[self.index]

        if own_score < mean - 5:
            self.quota += 1.5
        if own_score > mean + 5:
            self.quota -= 1.5

        self.quota = max(min(self.quota, self.maxquota), self.minquota)

        if last_round:
            self.quota = max(scores) - own_score + 1

        while sum(self.current_throws) < self.quota:
            yield True

        yield False


if own_score mean + 5:给我一个错误。还while sum(self.current_throws)
Spitemaster '18

@Spitemaster是粘贴到堆栈交换中的错误,现在应该可以正常工作。
FlipTack

@Spitemaster这是因为有<>符号干扰了<pre>我正在使用的标签
FlipTack

2

期望

只是直接玩,计算掷骰子的期望值,只有在它为正数时才进行。

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

我在运行控制器时遇到了麻烦,在多线程控制器上出现了“ NameError:名称'bots_per_game'未定义”的消息,所以实际上不知道它如何执行。


1
我认为这最终等同于“转到16”机器人,但我们还没有一个机器人
Stuart Moore

1
@StuartMoore这是一个非常正确的观点,是的
该隐

在Windows计算机上运行控制器时,我遇到了控制器问题。不知何故,它可以在我的Linux机器上正常运行。我正在更新控制器,完成后将对其进行更新。
maxb

@maxb谢谢,可能是关于在不同过程中哪些变量可用的信息。仅供参考,我也对此进行了更新,我在屈服时犯了一个愚蠢的错误:/
Cain

2

BlessRNG

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

BlessRNG FrankerZ GabeN BlessRNG


2

四十岁

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

尝试14分直到最后一轮,然后假设其他所有人都将尝试获得14分并尝试得分。


我有TypeError: unsupported operand type(s) for -: 'list' and 'int'你的机器人。
tsh

我假设您max_projected_score应该是列表的最大值而不是整个列表,对吗?否则我会得到与tsh相同的问题。
maxb

糟糕,已修改以进行修复。
histocrat

2

犹豫

请执行两个适度的步骤,然后等待其他人越线。更新的版本不再试图超越高分,只想达到它-通过删除源代码的两个字节来提高性能!

class Hesitate(Bot):
    def make_throw(self, scores, last_round):
        myscore = scores[self.index]
        if last_round:
            target = max(scores)
        elif myscore==0:
            target = 17
        else:
            target = 35
        while myscore+sum(self.current_throws) < target:
            yield True
        yield False

2

反叛

该机器人将的简单策略Hesitate 与的高级上一轮策略结合在一起BotFor2X,试图记住它是谁,并在发现它生活在幻想中时变得疯狂。

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)

好吧,这很优雅:)另外,恭喜您在主要比赛中获得第一和第二名!
Dani O

当然,我已经进行了调整,HarkonnenBotRebel使其本身不再能中毒;)并且我也进行了调整,TleilaxuBot以致Rebel不再检测到它!
Dani O

1

拿五

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

在一半的时间里,我们将5投在6之前。完成后,兑现。


如果我们改为停在1,则进度会变慢,但更可能一次达到40。
助记符

在我的测试中,TakeOne每回合获得20.868点,而TakeFive每回合则获得24.262点(并且将胜利率从0.291提升至0.259)。所以我认为这不值得。
Spitemaster '18

1

追逐者

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

追逐者试图追赶到第一个位置。如果这是最后一轮,他拼命试图达到至少50分。

[编辑1:在上一轮中加入了黄金换手策略]

[编辑2:更新了逻辑,因为我错误地认为一个机器人的得分会是40,而不是最高的机器人得分]

[编辑3:在最终游戏中,追逐者的防守更具防御性]


欢迎来到PPCG!整洁的想法不仅试图赶上,而且还超越了第一名。我现在正在运行模拟,祝您好运!
maxb

谢谢!最初,我试图超过前一个领导者一个固定的数量(尝试值介于6到20之间),但事实证明,抛出更多的公平交易会更好。
AKroell

@JonathanFrech感谢,已解决
-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

一对机器人,他们带来了自己的骰子并掷骰子来预测未来。如果一个是6,他们会停下来,FutureBot记不清哪一个是2个骰子用于下一掷,所以它放弃了。

我不知道哪个会更好。

就我的喜好而言,OneStepAhead与OneInFive有点相似,但我也想看看它与FutureBot和OneInFive的比较。

编辑:现在他们击中45后停止


欢迎来到PPCG!您的机器人绝对可以发挥游戏精神!我今晚晚些时候进行模拟。
maxb

谢谢!我很好奇它的性能,但是我猜它会处于低位。
威廉波特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.