サイコロのゲーム、ただし6番は避けます[終了]


58

トーナメント終了!

トーナメントは終了しました!最終シミュレーションは夜に実行され、合計ゲームでした。勝者は、ボットOptFor2Xを使用したChristian Sievers です。クリスチャンシーバーズは反乱軍と2位を確保することもできました。おめでとうございます!以下に、トーナメントの公式ハイスコアリストを見ることができます。3108

それでもゲームをプレイしたい場合は、以下に掲載されているコントローラーを使用し、その中のコードを使用して独自のゲームを作成してください。

サイコロ

私は聞いたことがなかったサイコロのゲームをプレイするように招待されました。ルールはシンプルでしたが、KotHチャレンジには最適だと思います。

ルール

ゲームの始まり

ダイスはテーブルを一周し、自分の番になるたびに、好きなだけダイスを投げることができます。ただし、少なくとも1回はスローする必要があります。ラウンドのすべてのスローの合計を追跡します。停止することを選択した場合、ラウンドのスコアが合計スコアに追加されます。

だから、なぜあなたはダイを投げるのをやめるのですか?あなたが6を取得した場合、ラウンド全体のスコアがゼロになり、ダイスが渡されるためです。したがって、最初の目標は、できるだけ早くスコアを上げることです。

誰が勝ちましたか?

テーブルの周りの最初のプレーヤーが40ポイント以上になると、最後のラウンドが始まります。最後のラウンドが始まると、最後のラウンドを開始した人以外の全員がもう1ターンを獲得します。

最終ラウンドのルールは、他のラウンドと同じです。投げ続けるか、停止するかを選択します。ただし、最終ラウンドの前のスコアよりも高いスコアを取得しないと、勝つチャンスがないことを知っています。しかし、あなたがあまりにも遠くに行き続けるなら、あなたは6を得るかもしれません。

ただし、考慮すべきルールがもう1つあります。現在の合計スコア(以前のスコア+ラウンドの現在のスコア)が40以上で、6をヒットした場合、合計スコアは0に設定されます。つまり、最初からやり直す必要があります。現在の合計スコアが40以上のときに6を押した場合、ゲームは通常通り続行しますが、現在は最後の場所にいます。合計スコアがリセットされても、最終ラウンドはトリガーされません。あなたはまだラウンドに勝つことができますが、それはより挑戦的になります。

勝者は、最後のラウンドが終了した時点で最高のスコアを獲得したプレーヤーです。2人以上のプレイヤーが同じスコアを共有する場合、それらはすべて勝利者としてカウントされます。

追加のルールは、ゲームが最大200ラウンド継続することです。これは、複数のボットが現在のスコアを維持するために6を押すまで基本的に投げ続けるケースを防ぐためです。199回目のラウンドに合格すると、last_roundtrueに設定され、さらに1ラウンドがプレイされます。ゲームが200ラウンドになった場合、最高得点のボット(またはボット)が勝者となります(40ポイント以上を持っていなくても)。

要約

  • 停止するか6を得るまで、各ラウンドでダイスを投げ続けます。
  • ダイスを1回投げる必要があります(最初の投げが6の場合、ラウンドはすぐに終わります)。
  • 6を取得した場合、現在のスコアは0に設定されます(合計スコアではありません)
  • 各ラウンドの後に、現在のスコアを合計スコアに追加します
  • ボットがターンを終了し、合計スコアが少なくとも40になると、他の全員が最後のターンを獲得します
  • 現在の合計スコアがで6を獲得した場合、合計スコアは0に設定され、ラウンドは終了します。40
  • 上記の場合、最終ラウンドはトリガーされません
  • 最終ラウンド後の合計スコアが最も高い人が勝者です
  • 複数の勝者がいる場合、すべてが勝者としてカウントされます
  • ゲームは最大200ラウンド続きます

スコアの明確化

  • 合計スコア:前のラウンドで保存したスコア
  • 現在のスコア:現在のラウンドのスコア
  • 現在の合計スコア:上記の2つのスコアの合計

どうやって参加しますか

この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クラスプロパティをさらに追加する場合など、親クラスに存在する関数をオーバーライドすることもできます。譲歩Trueまたは以外の方法でゲームの状態を変更することはできませんFalse

この投稿から自由にインスピレーションを求め、ここに含めた2つのボットのいずれかをコピーしてください。しかし、私はそれらが特に効果的ではないことを恐れています...

他の言語の許可について

サンドボックスとThe Nineteenth Byteの両方で、他の言語での投稿を許可することについて議論しました。そのような実装について読んで、両側から議論を聞いた後、私はこの挑戦をPythonだけに制限することに決めました。これは、2つの要因によるものです。複数の言語をサポートするのに必要な時間と、安定性に達するまでに多数の反復を必要とするこの課題のランダム性です。引き続き参加してください。このチャレンジのためにPythonを学びたい場合は、できる限り頻繁にチャットに参加できるようにします。

質問がある場合は、このチャレンジのチャットルームに書き込むことができます。また会いましょう!

ルール

  • 妨害行為は許可され、奨励されています。つまり、他のプレイヤーに対する妨害行為
  • コントローラー、ランタイム、またはその他のサブミットをいじくり回そうとする試みはすべて失格となります。すべての提出物は、与えられたインプットとストレージでのみ機能します。
  • 500MB以上のメモリを使用して決定を下すボットは失格になります(その量のメモリが必要な場合は、選択を見直してください)
  • ボットは、意図的または偶然に既存の戦略とまったく同じ戦略を実装してはなりません。
  • チャレンジ中にボットを更新できます。ただし、アプローチが異なる場合は、別のボットを投稿することもできます。

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キーワードはある意味でリターンに似ていますが、他の方法では異なります。コンセプトについてはこちらをご覧ください。基本的に、一度を実行yieldすると関数が停止し、yield編集した値がコントローラーに返送されます。コントローラーは、ボットが別の決定を下すときまで、そのロジックを処理します。その後、コントローラーはサイコロを送信し、make_throw関数は前に停止した場合、基本的に前のyieldステートメントの後の行で実行を続けます。

このようにして、ゲームコントローラは、サイコロを投げるたびに個別のボット関数呼び出しを必要とせずに状態を更新できます。

仕様

で利用可能な任意のPythonライブラリを使用できますpip。良い平均を得るために、ラウンドごとに100ミリ秒の時間制限があります。あなたのスクリプトがそれよりもはるかに高速で、もっと多くのラウンドを実行できるようになれば本当に嬉しいです。

評価

勝者を見つけるために、すべてのボットを8個のランダムグループで実行します。送信されるクラスが8個未満の場合、各ラウンドですべてのボットが常に存在することを避けるために、4個のランダムグループで実行します。約8時間シミュレーションを実行し、勝者は勝率が最も高いボットになります。2019年の初めに最終シミュレーションを開始し、クリスマスのすべてをボットのコーディングに提供します!暫定最終日は1月4日ですが、時間が足りない場合は後日に変更できます。

それまでは、30〜60分のCPU時間を使用し、スコアボードを更新して、毎日のシミュレーションを試みます。これは公式のスコアではありませんが、どのボットが最高のパフォーマンスを発揮するかを知るためのガイドとして役立ちます。しかし、クリスマスが近づいているので、私がいつでも利用できるわけではないことを理解していただければ幸いです。シミュレーションを実行し、チャレンジに関連する質問に答えるために最善を尽くします。

自分でテストする

独自のシミュレーションを実行する場合、2つのサンプルボットを含む、シミュレーションを実行するコントローラーの完全なコードを次に示します。

コントローラ

このチャレンジ用に更新されたコントローラーは次のとおりです。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)

このチャレンジの元のコントローラーにアクセスしたい場合は、編集履歴で利用できます。新しいコントローラーには、ゲームを実行するためのまったく同じロジックがありますが、唯一の違いはパフォーマンス、統計収集、きれいな印刷です。

ボット

私のマシンでは、ボットはファイルに保存されています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

シミュレーションを実行する

シミュレーションを実行するには、上記の両方のコードスニペットを2つの個別のファイルに保存します。forty_game_controller.pyおよびとして保存しましたforty_game_bots.py。次に、Pythonの構成を使用するpython forty_game_controller.pyか、またはpython3 forty_game_controller.pyPythonの構成に依存します。シミュレーションをさらに構成する場合は、そこから指示に従ってください。必要に応じて、コードをいじってみてください。

ゲームの統計

他のボットを考慮せずに特定のスコアを狙うボットを作成している場合、これらが勝率パーセンタイルです。

+----------+-----+
|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のおかげで、各ボットの統計情報が増えました!3つの新しい列には、すべてのゲームの最大スコア、ゲームごとの平均スコア、および各ボットで勝ったときの平均スコアが含まれています。

コメントで指摘されているように、ゲーム内でより高いインデックスを持っているボットが場合によっては余分なラウンドを取得するゲームロジックに問題がありました。これは現在修正されており、以下のスコアはこれを反映しています。

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もこのリストに載っています。これは、巧妙な戦略を使用して妨害行為を回避し、実際に妨害行為を行うボットのパフォーマンスが向上するためです。

ボットNeoBotとボットKwisatzHaderachはルールに従いますが、ランダムジェネレーターを予測して抜け穴を使用します。これらのボットはシミュレーションに多くのリソースを必要とするため、より少ないゲームでのシミュレーションからの統計を追加しました。ボット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
カレブジェイ

5
この質問はすでに事実上新しい回答に近づいているため、この質問を終了することに投票します(「トーナメントは終了しました!最終シミュレーションは夜中に実行され、合計3 ∗ 108ゲーム」)
pppery

回答:


6

OptFor2X

このボットは、スコアと最高の対戦相手のスコアのみを使用して、このゲームの2人用バージョンの最適な戦略の近似に従います。最後のラウンドでは、更新されたバージョンはすべてのスコアを考慮します。

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

早くしたくありませんでした。私はすでにやりたいこと-一度だけ初期化-を行いましたが、特にクラスの外部で関数を定義せずに、より良い方法を探していました。今はもっといいと思う。
クリスチャンシーバーズ

それは今、ずっと良く見えます、良い仕事です!
maxb

1位と2位の両方を確保できて、おめでとうございます!
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
このボットと2番目の場所の間に大きなギャップがあるため、ボットに大量のコンピューティングが必要になるという事実と相まって、より少ない反復でシミュレーションを実行して勝率を見つけ、公式を実行することを受け入れますかボットなしのシミュレーション?
maxb

3
私は元気で、これは失格となる可能性が高く、ゲームの精神に沿ったものではないことを確信しました。そうは言っても、仕事をするのは大変でしたし、Pythonのソースコードをいじるのは楽しい言い訳でした。
ほとんど無害

2
ありがとう!他のボットがあなたのスコアに近づくとは思いません。そして、この戦略を実装することを考えている人は、そうしないでください。これ以降、この戦略はルールに反し、NeoBotがトーナメントフェアを維持するためにそれを使用することを許可されている唯一の戦略です。
maxb

1
まあ、myBotはすべてを打ち負かしていますが、これははるかに優れています-私はこのようなボットを投稿すると、-100を得て最高のスコアではありません。
ヤンイヴァン1

15

協力的な群れ

戦略

このルールの重要性に気付いた人はまだいないと思います。

ゲームが200ラウンドになった場合、最高得点のボット(またはボット)が勝者となります(40ポイント以上を持っていなくても)。

すべてのボットが常にバストするまで転がった場合、ラウンド200の終わりに全員のスコアがゼロになり、全員が勝ちます!したがって、Cooperative Swarmの戦略は、すべてのプレイヤーのスコアが0である限り協力することですが、誰かがポイントを獲得した場合は通常どおりプレイすることです。

この投稿では、2つのボットを送信しています。1つ目はCooperativeSwarmBot、2つ目はCooperativeThrowTwiceです。CooperativeSwarmBotは、正式には協調スウォームの一部であるすべてのボットの基本クラスとして機能し、協調が失敗した場合に最初の成功したロールを単純に受け入れるというプレースホルダーの動作があります。CooperativeSwarmBotには、CooperativeSwarmBotが親としてあり、1つではなく2つのロールを作成するという非協力的な動作を除いて、あらゆる点で同じです。今後数日のうちに、この投稿を改訂して、非協調型ボットと対戦するはるかにインテリジェントな動作を使用する新しいボットを追加します。

コード

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

分析

生存率

このゲームで協力するのは非常に困難です。ゲームを機能させるには8人のプレイヤー全員のサポートが必要だからです。各ボットクラスはゲームごとに1つのインスタンスに制限されているため、これは達成が難しい目標です。たとえば、100の協調型ボットと30の非協調型ボットのプールから8つの協調型ボットを選択する確率は次のとおりです。

100130991299812897127961269512594124931230.115

より一般的には、協調型ボットと非協調型ボットのプールから協調型ボットを選択する確率は次のとおりです。icn

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

この式から、ゲームの50%が協調して終了するために約430の協調ボット、または90%で約2900のボットが必要であることを簡単に示すことができます(ルールに従って、)。i=8n=38

ケーススタディ

いくつかの理由により(脚注1および2を参照)、適切な協同組合の群れが公式ゲームで競うことはありません。そのため、このセクションでは、自分のシミュレーションの1つの結果を要約します。

このシミュレーションでは、前回ここで確認した他の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%を達成しました。

群れに含まれていないボットの完全な結果を以下に示します。その結果が特に注目に値すると思うボットが2つあります。まず、StopBotはゲームにまったく勝てませんでした。協同的な群れがStopBotとまったく同じ戦略を実際に使用していたため、これは特に悲劇的です。StopBotが偶然8つのゲームに勝つことを期待していましたが、協力的な群れは対戦相手に最初の動きを与えることを余儀なくされるため、もう少し多くなります。しかし、2番目の興味深い結果は、PointsAreForNerdsBotのハードワークがついに報われたことです。Swarmと連携して、プレイしたすべてのゲームに勝ちました!

+---------------------+----+--------+--------+------+------+-------+------+--------+
|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]:2つだけではなく、何千ものボットを送信したくない主な理由は、そうするとシミュレーションが1000倍の速度で遅くなることです[2]。他のボットがほとんど排他的に群れと対戦するので、パーセンテージを獲得します。ただし、もっと重要なのは、私が望んでいたとしても、「ボットは、既存のもの、意図的または偶然」。

[2]:協調スウォームを実行すると、シミュレーションが遅くなる2つの主な理由があると思います。まず、各ボットを同じゲーム数でプレイしたい場合、ボットが多いほどゲームが多くなります(ケーススタディでは、ゲーム数は約77倍異なります)。第二に、協力的なゲームは200ラウンド続くため、時間がかかり、ラウンド内ではプレイヤーは無期限にローリングを続ける必要があります。私のセットアップでは、ゲームのシミュレーションに約40倍の時間がかかりました。ケーススタディでは10000ゲームを実行するのに3分強かかりましたが、協調スウォームを削除すると、わずか4.5秒で10000ゲームを終了しました。これら2つの理由の間で、群れが競合している場合と競合していない場合に比べて、ボットのパフォーマンスを正確に測定するには、約3100倍の時間がかかると推定します。


4
ワオ。PPCGへようこそ。これが最初の答えです。私はこのような状況を実際に計画していませんでした。確かにルールに抜け穴が見つかりました。あなたの答えは単一のボットではなくボットのコレクションであるため、私はこれをどのように採点すべきか本当に分かりません。ただし、ここで言う唯一のことは、1人の参加者がすべてのボットの98.7%を制御するのは不公平だと感じることです。
maxb

2
実際には、重複したボットが公式のコンテストに参加することは望ましくありません。そのため、ほとんど同じボットを何千も送信する代わりに、自分でシミュレーションを実行しました。それをより明確にするために、提出物を修正します。
アインハーダー

1
このような答えが予想されていたら、200ラウンドのゲームを変更して、プレーヤーにスコアを与えないようにしました。ただし、ご指摘のとおり、この戦略がルールに反するような同一のボットの作成に関するルールがあります。ボットを作成したすべての人にとって不公平になるため、ルールを変更するつもりはありません。ただし、協力の概念は非常に興味深いものであり、独自の戦略と組み合わせて協力戦略を実装する他のボットが提出されることを願っています。
maxb

1
あなたの投稿は、より徹底的に読んだ後、明確になると思います。
maxb

ほとんどのボットがリーダーボードの配置で正味の利益を得るために、この協力フレームワークでコードをラップする必要がある既存のボットはいくつですか?私の素朴な推測は50%です。
スパー

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

このチャレンジを設計する前に別のテストを実行し、ポストの2つの戦術のラウンドごとの平均スコア(「x回投げる」と「xスコアまで投げる」)を計算しました。 。サンプルサイズが小さすぎる可能性がありますが、不安定に気付きました。
maxb

2
私はこれを使っていくつかのテストを行ってきましたが、私の結論は、20は40/2なので20がうまく機能するということです。私は完全にはわかりませんが。end_score4000 に設定したとき(およびtarget計算でこれを使用するようにボットを変更したとき)、15〜16のボットは非常に優れていました。しかし、ゲームがあなたのスコアを増やすことだけであるならば、それは簡単です。
maxb

1
@maxb end_score4000の場合、200ターン前に4000を得るのはほとんど不可能です。そして、ゲームは単純に200ターンで最高のスコアを獲得した人です。そして、この時点で1ターンの最高得点の戦略は200ターンの最高得点と同じであるため、15で停止する必要があります。
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:クール!似たようなことをして、最後に数回はロールバックされるようにするつもりでしたが、まだ時間がかかりませんでした。奇妙なことに、100kで実行すると、これらの値でパフォーマンスが低下するようです。ただし、テストしたボットは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

@Spitemaster修正、ありがとう。
ニーモニック

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

アイデアは、他のボットがポイントを失う可能性があるため、2番目であることは悪いことではありません。


1
PPCGへようこそ!あなたの提出物を調べていますが、ゲームに参加しているプレイヤーが多いほど、ボットの勝率は低くなるようです。理由はすぐにはわかりません。ボットが1vs1と一致すると、10%の勝率が得られます。アイデアは前途有望に聞こえ、コードは正しいように見えるので、なぜあなたのwinrateが高くないのか、私には本当にわかりません。
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ボット)がありましたが、davidは回答を削除しました。そのボットをあなたのものに置き換えます。
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

EnsureLeadはGoTo20Botからアイデアを借りています。(last_roundまたは40に達したとき)少なくとも1つ以上のロールを持つ他のものがあると常に考えるという概念を追加します。したがって、ボットは追いつく必要があるように、少し先に行こうとします。


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のうち3位にいます
。– maxb

4

StopBot

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

文字通り1投のみ。

これは基本Botクラスと同等です。


1
ごめんなさい!あなたはすべてのルールに従っていますが、あなたのボットはラウンドあたり平均2.5ポイントというひどく効果的ではないのではないかと心配しています。
maxb

1
しかし、誰かがそのボットを投稿する必要がありました。損失のためにボットを退化させます。
ザカリー

5
ボットが前回のシミュレーションでちょうど1つの勝利を獲得したことに感銘を受け、完全に役に立たないわけではないことを証明したいと思います。
maxb

2
ITがゲームに勝った?!それは驚くべきことです。
ザカリー

3

BringMyOwn_dice(BMO_d)

このボットはサイコロが大好きで、2個のサイコロ(最高のパフォーマンスを発揮するようです)を持っています。ラウンドでサイコロを投げる前に、それはそれ自身の2つのサイコロを投げて、それらの合計を計算します。これは、それが実行しようとしているスローの数です。

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回投げるとラウンドあたりの得点が最も多く、2つのサイコロを投げたときの平均スコアに近いため、2つのサイコロのパフォーマンスが最高だと思います。
maxb

3

FooBot

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

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

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

            yield True

        yield False

# Must throw at least once不要-ボットを呼び出す前に1回スローされます。ボットは常に最低2回スローします。
Spitemaster

ありがとう。メソッドの名前に惑わされました。
ピーターテイラー

@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もので囲む必要があります。
クリスチャンシーバーズ

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を振るまで回転し続けます。5は6未満なので、勝つ必要があります!


2
PPCGへようこそ!知っていると思いますが、最初のボットはこのコンテストで最悪のボットです!これOneInFiveBotはきちんとしたアイデアですが、いくつかのより高度なボットと比較して、エンドゲームで苦しむと思います。まだ素晴らしい投稿です!
maxb

2
これOneInFiveBotは、彼が一貫して最高の総合スコアに達するという点で非常に興味深いものです。
アクロエル

1
StopBotサンドバッグをくれてありがとう:P。OneInFiveBotは実際、かなりきちんとした素晴らしい仕事です!
ザカリー

@maxbうん、そこに名前がついた。私は正直にテストしていないOneInFiveBot、それは私が予想よりはるかに良いをやっている
The_Bob


3

LizduadacBot

1ステップで勝つことを試みます。終了条件は多少不定です。

これは私の最初の投稿でもあり(そして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スロースタートアルゴリズムを実装しています。前のターンに従ってロールの数(または)を調整します。前のターンで6をロールしなかった場合、このターンのノルを増やします。一方、減少した場合でも減少した場合でも

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へようこそ!興味深いアプローチですが、ランダムな変動にどれほど敏感かはわかりません。この実行を行うために必要な2つのこと:するdef updateValues():必要がありますdef updateValues(self):(またはdef update_values(self):PEP8に従う場合)。次に、呼び出しupdateValues()self.updateValues()(またはself.update_vales())である必要があります。
maxb

2
また、iwhileループで変数を更新する必要があると思います。今、あなたのボットは完全にwhileループを渡すか、それが6当たるまで、whileループでスタックしているいずれか
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、リードになったら、将来を予測する完璧な能力を打ち負かす方法について考え始めました。だからここにデューンの引用があります。クウィザッツ・ハデラッハのポール・アトレイデスは、無限の異なる未来が繰り広げられるネクサスに立つときです。

彼は、先見の明は、それが明らかにしたものの限界を取り入れた照明であり、一度に正確さと意味のあるエラーの原因となったことを理解した。ハイゼンベルグの不確定性の一種が介入しました。彼が見たものを明らかにし、見たものを変えたエネルギーの消費……瞬き、不注意な言葉、見当違いの砂粒が、巨大なレバーを動かしました。既知の宇宙。彼は、結果に対する暴力が非常に多くの変数の影響を受けているのを見て、彼のわずかな動きがパターンに大きな変化をもたらしました。

ビジョンは彼を不動に凍結させたいと思ったが、これも結果を伴う行動であった。

答えはここにありました。未来を予測することはそれを変えることです。そして、あなたが非常に慎重であれば、選択的なアクションまたは非アクティブによって、少なくともほとんどの場合、有利な方法でそれを変更できます。でもKwisatzHaderach100%の勝率は得られません!


このボットは、乱数発生器の状態を変更して、6のローリングを回避するか、少なくとも予測するようにしているようです。HarkonnenBotについても同様です。ただし、これらのボットの勝率はNeoBotの勝率よりもはるかに高いことに注意してください。乱数ジェネレーターが6を回転させないように積極的に操作していますか?
maxb

ああ、私の最初の読書で、これがより良いだけでなく、より良いことにも気づきませんでしたNeoBot!また、ここでランダム性(特にコントローラー)を使用するすべてが何をすべきか例を示す方法が好きです:独自のrandom.Randomインスタンスを使用します。のようにNeoBot、これはコントローラの不特定の実装詳細の変更に少し敏感に思えます。
クリスチャンジーバーズ

@maxb:HarkonnenBotRNGには触れません。乱数はまったく気にしません。他のすべてのボットを毒し、可能な限りゆっくりとフィニッシュラインまで歩きます。多くの料理の料理と同様に、復venは長く繊細な準備の後にゆっくりと味わうのが一番の料理です。
ダニオ

@ChristianSievers:(および)とは異なり 、実装の1つの詳細のみに依存します。特に、random.random()がどのように実装されているかを知る必要はなく、コントローラーがそれを使用することだけが必要です; DNeoBotHarkonnenBotKwisatzHaderach
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 

まあ、それは明らかです


そのクラスのボットでいくつかの実験を行いました(初めてゲームをプレイしたときに使用した戦術でした)。ラウンドごとに5〜6の平均スコアが高くなっていますが、私は4スローで行きました。
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ポイント未満で解決したくありません。


興味深いアプローチ。ボットが遅れ始めると、ボットが苦しむと思います。1回のラウンドで30ポイントを超える確率は低いため、ボットは現在のスコアを維持する可能性が高くなります。
maxb

1
これは私が犯したのと同じ間違いに苦しんでいると思います(NotTooFarBehindBotのコメントを参照)-最後のラウンドと同様に、勝てない場合は6を得るまで投げ続けます(スコア[self.index]は更新されません)実際-あなたはその不平等を間違った方法で持っていますか?max(scores)は常に> = score [self.index]になります
スチュアートムーア

@StuartMoore母、はい、あなたは正しいと思います。ありがとう!
Spitemaster

私はあなたが欲しい「とlast_round」あなたがやりたい第二中に疑いがある-それ以外の場合は第二しばらくはlast_roundかどうかに使用されますが本当です
スチュアート・ムーア

3
それは意図的です。ターンを終了するとき、常に先頭に立ちます。
Spitemaster

2

QuotaBot

私が実装した素朴な「クォータ」システムは、実際には全体的にかなり高いスコアを付けているように見えました。

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

@Spitemasterはスタック交換への貼り付けエラーでしたが、今は動作するはずです。
FlipTack

あったためです@Spitemaster <>に干渉シンボル<pre>私が使用したタグ
FlipTack

2

ExpectationsBot

それをまっすぐにプレイし、サイコロの投球の期待値を計算し、それが正の場合にのみそれを行います。

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:name 'bots_per_game' is not defined」がマルチスレッドで発生したため、これがどのように実行されるか全く分かりません。


1
私は、これは「16に移動し、」ボットと同等なってしまうと思いますが、私たちはまだ、これらのいずれかを持っていない
スチュアート・ムーア

1
@StuartMooreそれは...本当のポイントです、はい
カイン

コントローラをWindowsマシンで実行したときに、コントローラで問題が発生しました。どういうわけか、私のLinuxマシンではうまく動きました。コントローラーを更新しています。完了したら投稿を更新します。
maxb

@maxbありがとう。おそらく、異なるプロセスでどの変数が利用可能かについて何か。FYIもこれを更新し、降伏に関する愚かなエラーを犯しました:/
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

40代

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

ためらう

2つの控えめなステップを実行し、他の誰かがラインを越えるのを待ちます。更新されたバージョンは、もはやハイスコアを破ろうとせず、到達したいだけです-ソースコードの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)

まあ、これは非常にエレガントです:)また、メインコンペで1位と2位の両方を獲得したことをお祝いします!
ダニオ

当然のことながら、それ自体で毒を解かないHarkonnenBotように調整しましたRebel;)そして、それがもう検出されないTleilaxuBotように調整しましたRebel
ダニ・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

半分の時間で、6の前に5をロールします。そうすると、キャッシュアウトします。


代わりに1で停止すると、進行は遅くなりますが、単一の境界で40に達する可能性が高くなります。
ニーモニック

私のテストでは、TakeFiveのラウンドあたり24.262ポイントと比較して、TakeOneはラウンドあたり20.868ポイントを獲得しました(また、勝率は0.291から0.259になりました)。だから私はそれが価値があるとは思わない。
Spitemaster

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

チェイサーはポジション1に追いつきます。最終ラウンドの場合、必死に少なくとも50ポイントに到達しようとします。

[編集1:最後のラウンドでgo-for-gold戦略を追加]

[編集2:ロジックを更新しました。ボットのスコアが最高であるだけでなく、ボットのスコアが40であると誤って判断したためです]

[編集3:チェイサーを最終ゲームでもう少し防御的にした]


PPCGへようこそ!追いつくためだけでなく、最初の場所を通過するためのきちんとしたアイデア。私は今シミュレーションを実行しています、そしてあなたの幸運を祈っています!
maxb

ありがとう!当初、私は前のリーダーを一定量(6〜20の値を試してみました)で超えようとしましたが、フェアを2倍投げるだけで良いことがわかりました。
アクロエル

@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

一対のボット、彼らはサイコロの独自のセットを持ってきて、それらを転がして未来を予測します。1つが6で停止した場合、FutureBotは2つのサイコロのどちらが次のロールのためのものであったかを思い出せず、soめます。

どちらが良くなるのだろうか。

OneStepAheadは私の好みではOneInFiveに少し似すぎていますが、FutureBotやOneInFiveと比較する方法も見たいです。

編集:45を押すと停止します


PPCGへようこそ!ボットは間違いなくゲームの精神でプレイします!今晩、シミュレーションを実行します。
maxb

ありがとう!私はそれがどれだけうまくいくかについて興味がありますが、私はそれが低い側にあると推測しています。
ポーター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.