Turnier vorbei!
Das Turnier ist jetzt vorbei! Die letzte Simulation wurde während der Nacht durchgeführt, insgesamt Spiele. Der Gewinner ist Christian Sievers mit seinem Bot OptFor2X . Auch Christian Sievers konnte sich mit Rebel den zweiten Platz sichern . Herzliche Glückwünsche! Unten sehen Sie die offizielle Highscore-Liste für das Turnier.
Wenn Sie das Spiel dennoch spielen möchten, können Sie gerne den unten angegebenen Controller verwenden und den Code darin verwenden, um Ihr eigenes Spiel zu erstellen.
Ich wurde eingeladen, ein Würfelspiel zu spielen, von dem ich noch nie gehört hatte. Die Regeln waren einfach, aber ich denke, es wäre perfekt für eine KotH-Herausforderung.
Die Regeln
Der Beginn des Spiels
Der Würfel geht um den Tisch und jedes Mal, wenn Sie an der Reihe sind, können Sie den Würfel so oft werfen, wie Sie möchten. Sie müssen es jedoch mindestens einmal werfen. Sie behalten die Summe aller Würfe für Ihre Runde im Auge. Wenn Sie aufhören möchten, wird die Punktzahl für die Runde zu Ihrer Gesamtpunktzahl hinzugefügt.
Warum würdest du jemals aufhören, den Würfel zu werfen? Denn wenn Sie 6 bekommen, wird Ihre Punktzahl für die gesamte Runde Null und der Würfel wird weitergereicht. Daher ist das ursprüngliche Ziel, Ihre Punktzahl so schnell wie möglich zu erhöhen.
Wer ist der Gewinner?
Wenn der erste Spieler am Tisch 40 Punkte oder mehr erreicht, beginnt die letzte Runde. Sobald die letzte Runde begonnen hat, erhält jeder außer der Person, die die letzte Runde eingeleitet hat, eine weitere Runde.
Die Regeln für die letzte Runde sind die gleichen wie für jede andere Runde. Sie entscheiden sich dafür, weiter zu werfen oder aufzuhören. Sie wissen jedoch, dass Sie keine Gewinnchance haben, wenn Sie in der letzten Runde keine höhere Punktzahl erzielen als vor Ihnen. Aber wenn Sie zu weit gehen, erhalten Sie möglicherweise eine 6.
Es gibt jedoch noch eine weitere Regel, die berücksichtigt werden muss. Wenn Ihre aktuelle Gesamtpunktzahl (Ihre vorherige Punktzahl + Ihre aktuelle Punktzahl für die Runde) 40 oder mehr beträgt und Sie eine 6 treffen, wird Ihre Gesamtpunktzahl auf 0 gesetzt. Das bedeutet, dass Sie von vorne beginnen müssen. Wenn Sie eine 6 erreichen, wenn Ihre aktuelle Gesamtpunktzahl 40 oder mehr beträgt, wird das Spiel normal fortgesetzt, mit der Ausnahme, dass Sie jetzt auf dem letzten Platz sind. Die letzte Runde wird nicht ausgelöst, wenn Ihre Gesamtpunktzahl zurückgesetzt wird. Sie könnten die Runde noch gewinnen, aber es wird schwieriger.
Der Gewinner ist der Spieler mit der höchsten Punktzahl, wenn die letzte Runde vorbei ist. Wenn zwei oder mehr Spieler die gleiche Punktzahl haben, werden sie alle als Sieger gewertet.
Eine zusätzliche Regel ist, dass das Spiel maximal 200 Runden lang fortgesetzt wird. Dies soll verhindern, dass mehrere Bots grundsätzlich so lange werfen, bis sie 6 treffen, um auf ihrem aktuellen Stand zu bleiben. Sobald die 199. Runde vorbei ist, last_round
wird auf true gesetzt und eine weitere Runde gespielt. Wenn das Spiel bis zu 200 Runden dauert, gewinnt der Bot (oder die Bots) mit der höchsten Punktzahl, auch wenn sie nicht über 40 Punkte oder mehr verfügen.
Rekapitulieren
- In jeder Runde wirfst du den Würfel, bis du aufhörst oder eine 6 erhältst
- Du musst den Würfel einmal werfen (wenn dein erster Wurf eine 6 ist, ist deine Runde sofort vorbei)
- Wenn Sie eine 6 erhalten, wird Ihre aktuelle Punktzahl auf 0 gesetzt (nicht Ihre Gesamtpunktzahl).
- Sie addieren Ihre aktuelle Punktzahl nach jeder Runde zu Ihrer Gesamtpunktzahl
- Wenn ein Bot seinen Zug beendet und eine Gesamtpunktzahl von mindestens 40 erreicht, erhält jeder andere eine letzte Runde
- Wenn Ihre aktuelle Gesamtpunktzahl und Sie eine 6 erhalten, wird Ihre Gesamtpunktzahl auf 0 gesetzt und Ihre Runde ist beendet
- Die letzte Runde wird nicht ausgelöst, wenn die oben genannten Ereignisse eintreten
- Die Person mit der höchsten Gesamtpunktzahl nach der letzten Runde ist der Gewinner
- Falls es mehrere Gewinner gibt, werden alle als Gewinner gewertet
- Das Spiel dauert maximal 200 Runden
Klärung der Partituren
- Gesamtpunktzahl: Die Punktzahl, die Sie in früheren Runden gespeichert haben
- Aktuelle Punktzahl: Die Punktzahl für die aktuelle Runde
- Aktuelle Gesamtpunktzahl: Die Summe der beiden obigen Punkte
Wie nimmst du teil?
Um an dieser KotH-Herausforderung teilzunehmen, sollten Sie eine Python-Klasse schreiben, von der erbt Bot
. Sie sollten die Funktion implementieren: make_throw(self, scores, last_round)
. Diese Funktion wird aufgerufen, sobald Sie an der Reihe sind und Ihr erster Wurf keine 6 war. Um weiter zu werfen, sollten Sie yield True
. Um mit dem Werfen aufzuhören, solltest du yield False
. Nach jedem Wurf wird die übergeordnete Funktion update_state
aufgerufen. Somit haben Sie über die Variable Zugriff auf Ihre Würfe für die aktuelle Runde self.current_throws
. Sie haben auch Zugriff auf Ihren eigenen Index mit self.index
. So, um Ihre eigene Gesamtpunktzahl zu sehen, würden Sie verwenden scores[self.index]
. Sie können auch end_score
mithilfe von auf das für das Spiel zugreifen self.end_score
, aber Sie können davon ausgehen, dass es für diese Herausforderung 40 sein wird.
Sie dürfen Hilfsfunktionen in Ihrer Klasse erstellen. Sie können auch in der Bot
übergeordneten Klasse vorhandene Funktionen überschreiben , z. B. wenn Sie weitere Klasseneigenschaften hinzufügen möchten. Sie dürfen den Status des Spiels in keiner Weise ändern, außer in Bezug auf "Nachgeben" True
oder " Nachgeben" False
.
Es steht Ihnen frei, sich von diesem Beitrag inspirieren zu lassen und einen der beiden Bots zu kopieren, die ich hier aufgenommen habe. Ich fürchte jedoch, dass sie nicht besonders effektiv sind ...
Zum Zulassen anderer Sprachen
Sowohl in der Sandbox als auch in The Nineteenth Byte haben wir Diskussionen darüber geführt, ob Beiträge in anderen Sprachen eingereicht werden dürfen. Nachdem ich über solche Implementierungen gelesen und Argumente von beiden Seiten gehört habe, habe ich beschlossen, diese Herausforderung nur auf Python zu beschränken. Dies ist auf zwei Faktoren zurückzuführen: die für die Unterstützung mehrerer Sprachen erforderliche Zeit und die Zufälligkeit dieser Herausforderung, die eine hohe Anzahl von Iterationen erfordert, um Stabilität zu erreichen. Ich hoffe, dass Sie weiterhin teilnehmen und wenn Sie Python für diese Herausforderung lernen möchten, werde ich versuchen, so oft wie möglich im Chat verfügbar zu sein.
Bei Fragen, die Sie möglicherweise haben, können Sie im Chat-Raum für diese Herausforderung schreiben . Wir sehen uns dort!
Regeln
- Sabotage ist erlaubt und wird empfohlen. Das heißt, Sabotage gegen andere Spieler
- Jeder Versuch, mit dem Controller, der Laufzeit oder anderen Einsendungen zu basteln, wird disqualifiziert. Alle Einsendungen sollten nur mit den angegebenen Eingaben und Speichern funktionieren.
- Jeder Bot, der mehr als 500 MB Speicher benötigt, um seine Entscheidung zu treffen, wird disqualifiziert (wenn Sie so viel Speicher benötigen, sollten Sie Ihre Auswahl überdenken).
- Ein Bot darf nicht absichtlich oder versehentlich die exakt gleiche Strategie wie ein vorhandener implementieren.
- Sie dürfen Ihren Bot während der Dauer der Challenge aktualisieren. Sie können jedoch auch einen anderen Bot posten, wenn Ihre Herangehensweise anders ist.
Beispiel
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Dieser Bot wird so lange weitermachen, bis er mindestens 10 Punkte für die Runde erzielt hat oder eine 6 auslöst. Beachten Sie, dass Sie keine Logik mehr benötigen, um 6 zu werfen. Beachten Sie auch, dass, wenn Ihr erster Wurf eine 6 make_throw
ist, nie angerufen, da deine Runde sofort vorbei ist.
Für diejenigen, die neu in Python (und neu im yield
Konzept) sind, dies aber ausprobieren möchten, yield
ähnelt das Schlüsselwort in gewisser Weise einer Rendite, in anderer Hinsicht unterscheidet es sich jedoch. Über das Konzept können Sie hier lesen . Grundsätzlich yield
stoppt Ihre Funktion , sobald Sie dies tun, und der von Ihnen yield
eingegebene Wert wird an die Steuerung zurückgesendet. Dort verarbeitet der Controller seine Logik, bis es Zeit für Ihren Bot ist, eine andere Entscheidung zu treffen. Dann sendet der Controller Ihnen den Würfelwurf und Ihre make_throw
Funktion wird genau dort ausgeführt, wo sie zuvor gestoppt wurde, im Grunde in der Zeile nach der vorherigen yield
Anweisung.
Auf diese Weise kann der Gamecontroller den Status aktualisieren, ohne dass für jeden Würfelwurf ein separater Bot-Funktionsaufruf erforderlich ist.
Spezifikation
Sie können jede Python-Bibliothek verwenden, die in verfügbar ist pip
. Um sicherzustellen, dass ich einen guten Durchschnitt erhalten kann, haben Sie ein Zeitlimit von 100 Millisekunden pro Runde. Ich würde mich sehr freuen, wenn Ihr Skript viel schneller wäre, damit ich mehr Runden drehen kann.
Auswertung
Um den Gewinner zu finden, nehme ich alle Bots und starte sie in zufälligen 8er-Gruppen. Wenn weniger als 8 Klassen eingereicht werden, starte ich sie in zufälligen 4er-Gruppen, um zu vermeiden, dass immer alle Bots in jeder Runde sind. Ich werde ungefähr 8 Stunden lang Simulationen durchführen, und der Gewinner wird der Bot mit dem höchsten Gewinnanteil sein. Ich starte die letzten Simulationen Anfang 2019 und gebe dir Weihnachten, um deine Bots zu programmieren! Das vorläufige Enddatum ist der 4. Januar, aber wenn das zu wenig Zeit ist, kann ich es auf ein späteres Datum umstellen.
Bis dahin werde ich versuchen, eine tägliche Simulation mit 30-60 Minuten CPU-Zeit durchzuführen und die Anzeigetafel zu aktualisieren. Dies wird nicht die offizielle Punktzahl sein, aber es wird als Leitfaden dienen, um zu sehen, welche Bots die beste Leistung erbringen. Ich hoffe jedoch, dass Sie mit Weihnachten verstehen können, dass ich nicht immer verfügbar sein werde. Ich werde mein Bestes geben, um Simulationen durchzuführen und alle Fragen im Zusammenhang mit der Herausforderung zu beantworten.
Testen Sie es selbst
Wenn Sie Ihre eigenen Simulationen ausführen möchten, finden Sie hier den vollständigen Code des Controllers, der die Simulation ausführt, einschließlich zweier Beispiel-Bots.
Regler
Hier ist der aktualisierte Controller für diese Herausforderung. Es unterstützt ANSI-Ausgänge, Multithreading und sammelt dank AKroell zusätzliche Statistiken ! Wenn ich Änderungen am Controller vornehme, aktualisiere ich den Post, sobald die Dokumentation vollständig ist.
Dank BMO kann der Controller nun alle Bots von diesem Beitrag mit der -d
Flagge herunterladen . Andere Funktionen bleiben in dieser Version unverändert. Dies sollte sicherstellen, dass alle Ihre letzten Änderungen so schnell wie möglich simuliert werden!
#!/usr/bin/env python3
import re
import json
import math
import random
import requests
import sys
import time
from numpy import cumsum
from collections import defaultdict
from html import unescape
from lxml import html
from multiprocessing import Pool
from os import path, rename, remove
from sys import stderr
from time import strftime
# If you want to see what each bot decides, set this to true
# Should only be used with one thread and one game
DEBUG = False
# If your terminal supports ANSI, try setting this to true
ANSI = False
# File to keep base class and own bots
OWN_FILE = 'forty_game_bots.py'
# File where to store the downloaded bots
AUTO_FILE = 'auto_bots.py'
# If you want to use up all your quota & re-download all bots
DOWNLOAD = False
# If you want to ignore a specific user's bots (eg. your own bots): add to list
IGNORE = []
# The API-request to get all the bots
URL = "https://api.stackexchange.com/2.2/questions/177765/answers?page=%s&pagesize=100&order=desc&sort=creation&site=codegolf&filter=!bLf7Wx_BfZlJ7X"
def print_str(x, y, string):
print("\033["+str(y)+";"+str(x)+"H"+string, end = "", flush = True)
class bcolors:
WHITE = '\033[0m'
GREEN = '\033[92m'
BLUE = '\033[94m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
# Class for handling the game logic and relaying information to the bots
class Controller:
def __init__(self, bots_per_game, games, bots, thread_id):
"""Initiates all fields relevant to the simulation
Keyword arguments:
bots_per_game -- the number of bots that should be included in a game
games -- the number of games that should be simulated
bots -- a list of all available bot classes
"""
self.bots_per_game = bots_per_game
self.games = games
self.bots = bots
self.number_of_bots = len(self.bots)
self.wins = defaultdict(int)
self.played_games = defaultdict(int)
self.bot_timings = defaultdict(float)
# self.wins = {bot.__name__: 0 for bot in self.bots}
# self.played_games = {bot.__name__: 0 for bot in self.bots}
self.end_score = 40
self.thread_id = thread_id
self.max_rounds = 200
self.timed_out_games = 0
self.tied_games = 0
self.total_rounds = 0
self.highest_round = 0
#max, avg, avg_win, throws, success, rounds
self.highscore = defaultdict(lambda:[0, 0, 0, 0, 0, 0])
self.winning_scores = defaultdict(int)
# self.highscore = {bot.__name__: [0, 0, 0] for bot in self.bots}
# Returns a fair dice throw
def throw_die(self):
return random.randint(1,6)
# Print the current game number without newline
def print_progress(self, progress):
length = 50
filled = int(progress*length)
fill = "="*filled
space = " "*(length-filled)
perc = int(100*progress)
if ANSI:
col = [
bcolors.RED,
bcolors.YELLOW,
bcolors.WHITE,
bcolors.BLUE,
bcolors.GREEN
][int(progress*4)]
end = bcolors.ENDC
print_str(5, 8 + self.thread_id,
"\t%s[%s%s] %3d%%%s" % (col, fill, space, perc, end)
)
else:
print(
"\r\t[%s%s] %3d%%" % (fill, space, perc),
flush = True,
end = ""
)
# Handles selecting bots for each game, and counting how many times
# each bot has participated in a game
def simulate_games(self):
for game in range(self.games):
if self.games > 100:
if game % (self.games // 100) == 0 and not DEBUG:
if self.thread_id == 0 or ANSI:
progress = (game+1) / self.games
self.print_progress(progress)
game_bot_indices = random.sample(
range(self.number_of_bots),
self.bots_per_game
)
game_bots = [None for _ in range(self.bots_per_game)]
for i, bot_index in enumerate(game_bot_indices):
self.played_games[self.bots[bot_index].__name__] += 1
game_bots[i] = self.bots[bot_index](i, self.end_score)
self.play(game_bots)
if not DEBUG and (ANSI or self.thread_id == 0):
self.print_progress(1)
self.collect_results()
def play(self, game_bots):
"""Simulates a single game between the bots present in game_bots
Keyword arguments:
game_bots -- A list of instantiated bot objects for the game
"""
last_round = False
last_round_initiator = -1
round_number = 0
game_scores = [0 for _ in range(self.bots_per_game)]
# continue until one bot has reached end_score points
while not last_round:
for index, bot in enumerate(game_bots):
t0 = time.clock()
self.single_bot(index, bot, game_scores, last_round)
t1 = time.clock()
self.bot_timings[bot.__class__.__name__] += t1-t0
if game_scores[index] >= self.end_score and not last_round:
last_round = True
last_round_initiator = index
round_number += 1
# maximum of 200 rounds per game
if round_number > self.max_rounds - 1:
last_round = True
self.timed_out_games += 1
# this ensures that everyone gets their last turn
last_round_initiator = self.bots_per_game
# make sure that all bots get their last round
for index, bot in enumerate(game_bots[:last_round_initiator]):
t0 = time.clock()
self.single_bot(index, bot, game_scores, last_round)
t1 = time.clock()
self.bot_timings[bot.__class__.__name__] += t1-t0
# calculate which bots have the highest score
max_score = max(game_scores)
nr_of_winners = 0
for i in range(self.bots_per_game):
bot_name = game_bots[i].__class__.__name__
# average score per bot
self.highscore[bot_name][1] += game_scores[i]
if self.highscore[bot_name][0] < game_scores[i]:
# maximum score per bot
self.highscore[bot_name][0] = game_scores[i]
if game_scores[i] == max_score:
# average winning score per bot
self.highscore[bot_name][2] += game_scores[i]
nr_of_winners += 1
self.wins[bot_name] += 1
if nr_of_winners > 1:
self.tied_games += 1
self.total_rounds += round_number
self.highest_round = max(self.highest_round, round_number)
self.winning_scores[max_score] += 1
def single_bot(self, index, bot, game_scores, last_round):
"""Simulates a single round for one bot
Keyword arguments:
index -- The player index of the bot (e.g. 0 if the bot goes first)
bot -- The bot object about to be simulated
game_scores -- A list of ints containing the scores of all players
last_round -- Boolean describing whether it is currently the last round
"""
current_throws = [self.throw_die()]
if current_throws[-1] != 6:
bot.update_state(current_throws[:])
for throw in bot.make_throw(game_scores[:], last_round):
# send the last die cast to the bot
if not throw:
break
current_throws.append(self.throw_die())
if current_throws[-1] == 6:
break
bot.update_state(current_throws[:])
if current_throws[-1] == 6:
# reset total score if running total is above end_score
if game_scores[index] + sum(current_throws) - 6 >= self.end_score:
game_scores[index] = 0
else:
# add to total score if no 6 is cast
game_scores[index] += sum(current_throws)
if DEBUG:
desc = "%d: Bot %24s plays %40s with " + \
"scores %30s and last round == %5s"
print(desc % (index, bot.__class__.__name__,
current_throws, game_scores, last_round))
bot_name = bot.__class__.__name__
# average throws per round
self.highscore[bot_name][3] += len(current_throws)
# average success rate per round
self.highscore[bot_name][4] += int(current_throws[-1] != 6)
# total number of rounds
self.highscore[bot_name][5] += 1
# Collects all stats for the thread, so they can be summed up later
def collect_results(self):
self.bot_stats = {
bot.__name__: [
self.wins[bot.__name__],
self.played_games[bot.__name__],
self.highscore[bot.__name__]
]
for bot in self.bots}
#
def print_results(total_bot_stats, total_game_stats, elapsed_time):
"""Print the high score after the simulation
Keyword arguments:
total_bot_stats -- A list containing the winning stats for each thread
total_game_stats -- A list containing controller stats for each thread
elapsed_time -- The number of seconds that it took to run the simulation
"""
# Find the name of each bot, the number of wins, the number
# of played games, and the win percentage
wins = defaultdict(int)
played_games = defaultdict(int)
highscores = defaultdict(lambda: [0, 0, 0, 0, 0, 0])
bots = set()
timed_out_games = sum(s[0] for s in total_game_stats)
tied_games = sum(s[1] for s in total_game_stats)
total_games = sum(s[2] for s in total_game_stats)
total_rounds = sum(s[4] for s in total_game_stats)
highest_round = max(s[5] for s in total_game_stats)
average_rounds = total_rounds / total_games
winning_scores = defaultdict(int)
bot_timings = defaultdict(float)
for stats in total_game_stats:
for score, count in stats[6].items():
winning_scores[score] += count
percentiles = calculate_percentiles(winning_scores, total_games)
for thread in total_bot_stats:
for bot, stats in thread.items():
wins[bot] += stats[0]
played_games[bot] += stats[1]
highscores[bot][0] = max(highscores[bot][0], stats[2][0])
for i in range(1, 6):
highscores[bot][i] += stats[2][i]
bots.add(bot)
for bot in bots:
bot_timings[bot] += sum(s[3][bot] for s in total_game_stats)
bot_stats = [[bot, wins[bot], played_games[bot], 0] for bot in bots]
for i, bot in enumerate(bot_stats):
bot[3] = 100 * bot[1] / bot[2] if bot[2] > 0 else 0
bot_stats[i] = tuple(bot)
# Sort the bots by their winning percentage
sorted_scores = sorted(bot_stats, key=lambda x: x[3], reverse=True)
# Find the longest class name for any bot
max_len = max([len(b[0]) for b in bot_stats])
# Print the highscore list
if ANSI:
print_str(0, 9 + threads, "")
else:
print("\n")
sim_msg = "\tSimulation or %d games between %d bots " + \
"completed in %.1f seconds"
print(sim_msg % (total_games, len(bots), elapsed_time))
print("\tEach game lasted for an average of %.2f rounds" % average_rounds)
print("\t%d games were tied between two or more bots" % tied_games)
print("\t%d games ran until the round limit, highest round was %d\n"
% (timed_out_games, highest_round))
print_bot_stats(sorted_scores, max_len, highscores)
print_score_percentiles(percentiles)
print_time_stats(bot_timings, max_len)
def calculate_percentiles(winning_scores, total_games):
percentile_bins = 10000
percentiles = [0 for _ in range(percentile_bins)]
sorted_keys = list(sorted(winning_scores.keys()))
sorted_values = [winning_scores[key] for key in sorted_keys]
cumsum_values = list(cumsum(sorted_values))
i = 0
for perc in range(percentile_bins):
while cumsum_values[i] < total_games * (perc+1) / percentile_bins:
i += 1
percentiles[perc] = sorted_keys[i]
return percentiles
def print_score_percentiles(percentiles):
n = len(percentiles)
show = [.5, .75, .9, .95, .99, .999, .9999]
print("\t+----------+-----+")
print("\t|Percentile|Score|")
print("\t+----------+-----+")
for p in show:
print("\t|%10.2f|%5d|" % (100*p, percentiles[int(p*n)]))
print("\t+----------+-----+")
print()
def print_bot_stats(sorted_scores, max_len, highscores):
"""Print the stats for the bots
Keyword arguments:
sorted_scores -- A list containing the bots in sorted order
max_len -- The maximum name length for all bots
highscores -- A dict with additional stats for each bot
"""
delimiter_format = "\t+%s%s+%s+%s+%s+%s+%s+%s+%s+%s+"
delimiter_args = ("-"*(max_len), "", "-"*4, "-"*8,
"-"*8, "-"*6, "-"*6, "-"*7, "-"*6, "-"*8)
delimiter_str = delimiter_format % delimiter_args
print(delimiter_str)
print("\t|%s%s|%4s|%8s|%8s|%6s|%6s|%7s|%6s|%8s|"
% ("Bot", " "*(max_len-3), "Win%", "Wins",
"Played", "Max", "Avg", "Avg win", "Throws", "Success%"))
print(delimiter_str)
for bot, wins, played, score in sorted_scores:
highscore = highscores[bot]
bot_max_score = highscore[0]
bot_avg_score = highscore[1] / played
bot_avg_win_score = highscore[2] / max(1, wins)
bot_avg_throws = highscore[3] / highscore[5]
bot_success_rate = 100 * highscore[4] / highscore[5]
space_fill = " "*(max_len-len(bot))
format_str = "\t|%s%s|%4.1f|%8d|%8d|%6d|%6.2f|%7.2f|%6.2f|%8.2f|"
format_arguments = (bot, space_fill, score, wins,
played, bot_max_score, bot_avg_score,
bot_avg_win_score, bot_avg_throws, bot_success_rate)
print(format_str % format_arguments)
print(delimiter_str)
print()
def print_time_stats(bot_timings, max_len):
"""Print the execution time for all bots
Keyword arguments:
bot_timings -- A dict containing information about timings for each bot
max_len -- The maximum name length for all bots
"""
total_time = sum(bot_timings.values())
sorted_times = sorted(bot_timings.items(),
key=lambda x: x[1], reverse = True)
delimiter_format = "\t+%s+%s+%s+"
delimiter_args = ("-"*(max_len), "-"*7, "-"*5)
delimiter_str = delimiter_format % delimiter_args
print(delimiter_str)
print("\t|%s%s|%7s|%5s|" % ("Bot", " "*(max_len-3), "Time", "Time%"))
print(delimiter_str)
for bot, bot_time in sorted_times:
space_fill = " "*(max_len-len(bot))
perc = 100 * bot_time / total_time
print("\t|%s%s|%7.2f|%5.1f|" % (bot, space_fill, bot_time, perc))
print(delimiter_str)
print()
def run_simulation(thread_id, bots_per_game, games_per_thread, bots):
"""Used by multithreading to run the simulation in parallel
Keyword arguments:
thread_id -- A unique identifier for each thread, starting at 0
bots_per_game -- How many bots should participate in each game
games_per_thread -- The number of games to be simulated
bots -- A list of all bot classes available
"""
try:
controller = Controller(bots_per_game,
games_per_thread, bots, thread_id)
controller.simulate_games()
controller_stats = (
controller.timed_out_games,
controller.tied_games,
controller.games,
controller.bot_timings,
controller.total_rounds,
controller.highest_round,
controller.winning_scores
)
return (controller.bot_stats, controller_stats)
except KeyboardInterrupt:
return {}
# Prints the help for the script
def print_help():
print("\nThis is the controller for the PPCG KotH challenge " + \
"'A game of dice, but avoid number 6'")
print("For any question, send a message to maxb\n")
print("Usage: python %s [OPTIONS]" % sys.argv[0])
print("\n -n\t\tthe number of games to simluate")
print(" -b\t\tthe number of bots per round")
print(" -t\t\tthe number of threads")
print(" -d\t--download\tdownload all bots from codegolf.SE")
print(" -A\t--ansi\trun in ANSI mode, with prettier printing")
print(" -D\t--debug\trun in debug mode. Sets to 1 thread, 1 game")
print(" -h\t--help\tshow this help\n")
# Make a stack-API request for the n-th page
def req(n):
req = requests.get(URL % n)
req.raise_for_status()
return req.json()
# Pull all the answers via the stack-API
def get_answers():
n = 1
api_ans = req(n)
answers = api_ans['items']
while api_ans['has_more']:
n += 1
if api_ans['quota_remaining']:
api_ans = req(n)
answers += api_ans['items']
else:
break
m, r = api_ans['quota_max'], api_ans['quota_remaining']
if 0.1 * m > r:
print(" > [WARN]: only %s/%s API-requests remaining!" % (r,m), file=stderr)
return answers
def download_players():
players = {}
for ans in get_answers():
name = unescape(ans['owner']['display_name'])
bots = []
root = html.fromstring('<body>%s</body>' % ans['body'])
for el in root.findall('.//code'):
code = el.text
if re.search(r'^class \w+\(\w*Bot\):.*$', code, flags=re.MULTILINE):
bots.append(code)
if not bots:
print(" > [WARN] user '%s': couldn't locate any bots" % name, file=stderr)
elif name in players:
players[name] += bots
else:
players[name] = bots
return players
# Download all bots from codegolf.stackexchange.com
def download_bots():
print('pulling bots from the interwebs..', file=stderr)
try:
players = download_players()
except Exception as ex:
print('FAILED: (%s)' % ex, file=stderr)
exit(1)
if path.isfile(AUTO_FILE):
print(' > move: %s -> %s.old' % (AUTO_FILE,AUTO_FILE), file=stderr)
if path.exists('%s.old' % AUTO_FILE):
remove('%s.old' % AUTO_FILE)
rename(AUTO_FILE, '%s.old' % AUTO_FILE)
print(' > writing players to %s' % AUTO_FILE, file=stderr)
f = open(AUTO_FILE, 'w+', encoding='utf8')
f.write('# -*- coding: utf-8 -*- \n')
f.write('# Bots downloaded from https://codegolf.stackexchange.com/questions/177765 @ %s\n\n' % strftime('%F %H:%M:%S'))
with open(OWN_FILE, 'r') as bfile:
f.write(bfile.read()+'\n\n\n# Auto-pulled bots:\n\n')
for usr in players:
if usr not in IGNORE:
for bot in players[usr]:
f.write('# User: %s\n' % usr)
f.write(bot+'\n\n')
f.close()
print('OK: pulled %s bots' % sum(len(bs) for bs in players.values()))
if __name__ == "__main__":
games = 10000
bots_per_game = 8
threads = 4
for i, arg in enumerate(sys.argv):
if arg == "-n" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
games = int(sys.argv[i+1])
if arg == "-b" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
bots_per_game = int(sys.argv[i+1])
if arg == "-t" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
threads = int(sys.argv[i+1])
if arg == "-d" or arg == "--download":
DOWNLOAD = True
if arg == "-A" or arg == "--ansi":
ANSI = True
if arg == "-D" or arg == "--debug":
DEBUG = True
if arg == "-h" or arg == "--help":
print_help()
quit()
if ANSI:
print(chr(27) + "[2J", flush = True)
print_str(1,3,"")
else:
print()
if DOWNLOAD:
download_bots()
exit() # Before running other's code, you might want to inspect it..
if path.isfile(AUTO_FILE):
exec('from %s import *' % AUTO_FILE[:-3])
else:
exec('from %s import *' % OWN_FILE[:-3])
bots = get_all_bots()
if bots_per_game > len(bots):
bots_per_game = len(bots)
if bots_per_game < 2:
print("\tAt least 2 bots per game is needed")
bots_per_game = 2
if games <= 0:
print("\tAt least 1 game is needed")
games = 1
if threads <= 0:
print("\tAt least 1 thread is needed")
threads = 1
if DEBUG:
print("\tRunning in debug mode, with 1 thread and 1 game")
threads = 1
games = 1
games_per_thread = math.ceil(games / threads)
print("\tStarting simulation with %d bots" % len(bots))
sim_str = "\tSimulating %d games with %d bots per game"
print(sim_str % (games, bots_per_game))
print("\tRunning simulation on %d threads" % threads)
if len(sys.argv) == 1:
print("\tFor help running the script, use the -h flag")
print()
with Pool(threads) as pool:
t0 = time.time()
results = pool.starmap(
run_simulation,
[(i, bots_per_game, games_per_thread, bots) for i in range(threads)]
)
t1 = time.time()
if not DEBUG:
total_bot_stats = [r[0] for r in results]
total_game_stats = [r[1] for r in results]
print_results(total_bot_stats, total_game_stats, t1-t0)
Wenn Sie für diese Herausforderung auf den ursprünglichen Controller zugreifen möchten, ist dieser im Bearbeitungsverlauf verfügbar. Der neue Controller verfügt über genau die gleiche Logik für die Ausführung des Spiels. Der einzige Unterschied besteht in der Leistung, der Erfassung von Statistiken und dem hübscheren Drucken.
Bots
Auf meinem Computer werden die Bots in der Datei gespeichert forty_game_bots.py
. Wenn Sie einen anderen Namen für die Datei verwenden, müssen Sie die import
Anweisung oben auf dem Controller aktualisieren .
import sys, inspect
import random
import numpy as np
# Returns a list of all bot classes which inherit from the Bot class
def get_all_bots():
return Bot.__subclasses__()
# The parent class for all bots
class Bot:
def __init__(self, index, end_score):
self.index = index
self.end_score = end_score
def update_state(self, current_throws):
self.current_throws = current_throws
def make_throw(self, scores, last_round):
yield False
class ThrowTwiceBot(Bot):
def make_throw(self, scores, last_round):
yield True
yield False
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Simulation ausführen
Speichern Sie zum Ausführen einer Simulation beide oben veröffentlichten Codefragmente in zwei separaten Dateien. Ich habe sie als forty_game_controller.py
und gespeichert forty_game_bots.py
. Dann benutzt du einfach python forty_game_controller.py
oder python3 forty_game_controller.py
abhängig von deiner Python-Konfiguration. Befolgen Sie die Anweisungen von dort, wenn Sie Ihre Simulation weiter konfigurieren möchten, oder versuchen Sie, den Code zu basteln, wenn Sie möchten.
Spielstatistik
Wenn Sie einen Bot erstellen, der auf eine bestimmte Punktzahl abzielt, ohne andere Bots zu berücksichtigen, sind dies die Perzentile der Siegpunktzahl:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Highscores
Wenn weitere Antworten veröffentlicht werden, werde ich versuchen, diese Liste auf dem neuesten Stand zu halten. Der Inhalt der Liste ist immer von der neuesten Simulation. Die Bots ThrowTwiceBot
und GoToTenBot
sind die Bots aus dem obigen Code und werden als Referenz verwendet. Ich habe eine Simulation mit 10 ^ 8 Spielen gemacht, die ungefähr 1 Stunde gedauert hat. Dann sah ich, dass das Spiel im Vergleich zu meinen Läufen mit 10 ^ 7 Spielen Stabilität erreichte. Da die Leute immer noch Bots posten, werde ich keine Simulationen mehr durchführen, bis die Häufigkeit der Antworten gesunken ist.
Ich versuche, alle neuen Bots hinzuzufügen und alle Änderungen, die Sie an vorhandenen Bots vorgenommen haben, hinzuzufügen. Wenn es den Anschein hat, dass ich Ihren Bot verpasst habe oder Sie neue Änderungen vorgenommen haben, schreiben Sie in den Chat und ich werde sicherstellen, dass Ihre neueste Version in der nächsten Simulation verfügbar ist.
Dank AKroell haben wir jetzt mehr Statistiken für jeden Bot ! Die drei neuen Spalten enthalten die maximale Punktzahl für alle Spiele, die durchschnittliche Punktzahl pro Spiel und die durchschnittliche Punktzahl beim Gewinnen für jeden Bot.
Wie in den Kommentaren erwähnt, gab es ein Problem mit der Spiellogik, bei dem Bots mit einem höheren Index innerhalb eines Spiels in einigen Fällen eine Extrarunde erhielten. Dies wurde jetzt behoben, und die unten stehenden Scores spiegeln dies wider.
Simulation or 300000000 games between 49 bots completed in 35628.7 seconds
Each game lasted for an average of 3.73 rounds
29127662 games were tied between two or more bots
0 games ran until the round limit, highest round was 22
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|Bot |Win%| Wins| Played| Max| Avg|Avg win|Throws|Success%|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|OptFor2X |21.6|10583693|48967616| 99| 20.49| 44.37| 4.02| 33.09|
|Rebel |20.7|10151261|48977862| 104| 21.36| 44.25| 3.90| 35.05|
|Hesitate |20.3| 9940220|48970815| 105| 21.42| 44.23| 3.89| 35.11|
|EnsureLead |20.3| 9929074|48992362| 101| 20.43| 44.16| 4.50| 25.05|
|StepBot |20.2| 9901186|48978938| 96| 20.42| 43.47| 4.56| 24.06|
|BinaryBot |20.1| 9840684|48981088| 115| 21.01| 44.48| 3.85| 35.92|
|Roll6Timesv2 |20.1| 9831713|48982301| 101| 20.83| 43.53| 4.37| 27.15|
|AggressiveStalker |19.9| 9767637|48979790| 110| 20.46| 44.86| 3.90| 35.04|
|FooBot |19.9| 9740900|48980477| 100| 22.03| 43.79| 3.91| 34.79|
|QuotaBot |19.9| 9726944|48980023| 101| 19.96| 44.95| 4.50| 25.03|
|BePrepared |19.8| 9715461|48978569| 112| 18.68| 47.58| 4.30| 28.31|
|AdaptiveRoller |19.7| 9659023|48982819| 107| 20.70| 43.27| 4.51| 24.81|
|GoTo20Bot |19.6| 9597515|48973425| 108| 21.15| 43.24| 4.44| 25.98|
|Gladiolen |19.5| 9550368|48970506| 107| 20.16| 45.31| 3.91| 34.81|
|LastRound |19.4| 9509645|48988860| 100| 20.45| 43.50| 4.20| 29.98|
|BrainBot |19.4| 9500957|48985984| 105| 19.26| 45.56| 4.46| 25.71|
|GoTo20orBestBot |19.4| 9487725|48975944| 104| 20.98| 44.09| 4.46| 25.73|
|Stalker |19.4| 9485631|48969437| 103| 20.20| 45.34| 3.80| 36.62|
|ClunkyChicken |19.1| 9354294|48972986| 112| 21.14| 45.44| 3.57| 40.48|
|FortyTeen |18.8| 9185135|48980498| 107| 20.90| 46.77| 3.88| 35.32|
|Crush |18.6| 9115418|48985778| 96| 14.82| 43.08| 5.15| 14.15|
|Chaser |18.6| 9109636|48986188| 107| 19.52| 45.62| 4.06| 32.39|
|MatchLeaderBot |16.6| 8122985|48979024| 104| 18.61| 45.00| 3.20| 46.70|
|Ro |16.5| 8063156|48972140| 108| 13.74| 48.24| 5.07| 15.44|
|TakeFive |16.1| 7906552|48994992| 100| 19.38| 44.68| 3.36| 43.96|
|RollForLuckBot |16.1| 7901601|48983545| 109| 17.30| 50.54| 4.72| 21.30|
|Alpha |15.5| 7584770|48985795| 104| 17.45| 46.64| 4.04| 32.67|
|GoHomeBot |15.1| 7418649|48974928| 44| 13.23| 41.41| 5.49| 8.52|
|LeadBy5Bot |15.0| 7354458|48987017| 110| 17.15| 46.95| 4.13| 31.16|
|NotTooFarBehindBot |15.0| 7338828|48965720| 115| 17.75| 45.03| 2.99| 50.23|
|GoToSeventeenRollTenBot|14.1| 6900832|48976440| 104| 10.26| 49.25| 5.68| 5.42|
|LizduadacBot |14.0| 6833125|48978161| 96| 9.67| 51.35| 5.72| 4.68|
|TleilaxuBot |13.5| 6603853|48985292| 137| 15.25| 45.05| 4.27| 28.80|
|BringMyOwn_dice |12.0| 5870328|48974969| 44| 21.27| 41.47| 4.24| 29.30|
|SafetyNet |11.4| 5600688|48987015| 98| 15.81| 45.03| 2.41| 59.84|
|WhereFourArtThouChicken|10.5| 5157324|48976428| 64| 22.38| 47.39| 3.59| 40.19|
|ExpectationsBot | 9.0| 4416154|48976485| 44| 24.40| 41.55| 3.58| 40.41|
|OneStepAheadBot | 8.4| 4132031|48975605| 50| 18.24| 46.02| 3.20| 46.59|
|GoBigEarly | 6.6| 3218181|48991348| 49| 20.77| 42.95| 3.90| 35.05|
|OneInFiveBot | 5.8| 2826326|48974364| 155| 17.26| 49.72| 3.00| 50.00|
|ThrowThriceBot | 4.1| 1994569|48984367| 54| 21.70| 44.55| 2.53| 57.88|
|FutureBot | 4.0| 1978660|48985814| 50| 17.93| 45.17| 2.36| 60.70|
|GamblersFallacy | 1.3| 621945|48986528| 44| 22.52| 41.46| 2.82| 53.07|
|FlipCoinRollDice | 0.7| 345385|48972339| 87| 15.29| 44.55| 1.61| 73.17|
|BlessRNG | 0.2| 73506|48974185| 49| 14.54| 42.72| 1.42| 76.39|
|StopBot | 0.0| 1353|48984828| 44| 10.92| 41.57| 1.00| 83.33|
|CooperativeSwarmBot | 0.0| 991|48970284| 44| 10.13| 41.51| 1.36| 77.30|
|PointsAreForNerdsBot | 0.0| 0|48986508| 0| 0.00| 0.00| 6.00| 0.00|
|SlowStart | 0.0| 0|48973613| 35| 5.22| 0.00| 3.16| 47.39|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
Die folgenden Bots (außer Rebel
) dienen dazu, die Regeln zu biegen, und die Macher haben zugestimmt, nicht am offiziellen Turnier teilzunehmen. Ich denke jedoch immer noch, dass ihre Ideen kreativ sind und eine lobende Erwähnung verdienen. Rebel ist auch auf dieser Liste, weil es eine clevere Strategie einsetzt, um Sabotage zu vermeiden, und mit dem Sabotage-Bot, der gerade im Spiel ist, tatsächlich eine bessere Leistung erzielt.
Die Bots NeoBot
und KwisatzHaderach
folgen den Regeln, nutzen aber eine Lücke, indem sie den Zufallsgenerator vorhersagen. Da diese Bots eine Menge Ressourcen zum Simulieren benötigen, habe ich ihre Statistiken aus einer Simulation mit weniger Spielen hinzugefügt. Der Bot HarkonnenBot
gewinnt, indem er alle anderen Bots deaktiviert, was streng gegen die Regeln verstößt.
Simulation or 300000 games between 52 bots completed in 66.2 seconds
Each game lasted for an average of 4.82 rounds
20709 games were tied between two or more bots
0 games ran until the round limit, highest round was 31
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|Bot |Win%| Wins| Played| Max| Avg|Avg win|Throws|Success%|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|KwisatzHaderach |80.4| 36986| 46015| 214| 58.19| 64.89| 11.90| 42.09|
|HarkonnenBot |76.0| 35152| 46264| 44| 34.04| 41.34| 1.00| 83.20|
|NeoBot |39.0| 17980| 46143| 214| 37.82| 59.55| 5.44| 50.21|
|Rebel |26.8| 12410| 46306| 92| 20.82| 43.39| 3.80| 35.84|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 45|
| 75.00| 50|
| 90.00| 59|
| 95.00| 70|
| 99.00| 97|
| 99.90| 138|
| 99.99| 214|
+----------+-----+
Antworten:
OptFor2X
Dieser Bot folgt einer Annäherung an die optimale Strategie für die Zwei-Spieler-Version dieses Spiels, wobei nur der Score und der Score des besten Gegners verwendet werden. In der letzten Runde werden in der aktualisierten Version alle Ergebnisse berücksichtigt.
quelle
NeoBot
Versuchen Sie stattdessen nur, die Wahrheit zu erkennen - es gibt keinen Löffel
NeoBot wirft einen Blick in die Matrix (auch bekannt als zufällig) und sagt voraus, ob der nächste Wurf eine 6 ist oder nicht - er kann nichts dagegen tun, wenn er eine 6 erhält, ist aber mehr als glücklich, einem Streak-Ender auszuweichen.
NeoBot ändert weder den Controller noch die Laufzeit, sondern bittet die Bibliothek höflich um weitere Informationen.
quelle
Kooperativer Schwarm
Strategie
Ich glaube noch niemandem ist die Bedeutung dieser Regel aufgefallen:
Wenn jeder Bot immer rollte, bis er pleite war, hätte jeder am Ende der 200. Runde eine Punktzahl von Null und jeder würde gewinnen! Daher besteht die Strategie des Genossenschaftsschwarms darin, zusammenzuarbeiten, solange alle Spieler eine Punktzahl von Null haben, aber normal zu spielen, wenn jemand Punkte erzielt.
In diesem Beitrag reiche ich zwei Bots ein: Der erste ist CooperativeSwarmBot und der zweite ist CooperativeThrowTwice. CooperativeSwarmBot dient als Basisklasse für alle Bots, die formell Teil des kooperativen Schwarms sind, und hat das Platzhalter-Verhalten, einfach den ersten erfolgreichen Wurf zu akzeptieren, wenn die Kooperation fehlschlägt. CooperativeSwarmBot hat CooperativeSwarmBot als übergeordnetes Element und ist in jeder Hinsicht identisch mit CooperativeSwarmBot, mit der Ausnahme, dass es nicht kooperativ ist, zwei Würfe anstatt eines zu machen. In den nächsten Tagen werde ich diesen Beitrag überarbeiten, um neue Bots hinzuzufügen, die viel intelligenteres Verhalten beim Spielen gegen nicht kooperative Bots verwenden.
Code
Analyse
Lebensfähigkeit
Es ist sehr schwierig, in diesem Spiel zusammenzuarbeiten, da wir die Unterstützung aller acht Spieler benötigen, damit es funktioniert. Da jede Bot-Klasse auf eine Instanz pro Spiel beschränkt ist, ist dies ein schwieriges Ziel. Die Wahrscheinlichkeit, acht kooperative Bots aus einem Pool von 100 kooperativen Bots und 30 nicht kooperativen Bots auszuwählen, beträgt beispielsweise:
Fallstudie
Aus einer Reihe von Gründen (siehe Fußnoten 1 und 2) wird ein ordnungsgemäßer kooperativer Schwarm niemals an den offiziellen Spielen teilnehmen. In diesem Abschnitt fasse ich die Ergebnisse einer meiner eigenen Simulationen zusammen.
Diese Simulation lief 10000 Spiele mit den 38 anderen Bots, die hier das letzte Mal gepostet wurden, und 2900 Bots, die CooperativeSwarmBot als Elternklasse hatten. Der Prüfer berichtete, dass 9051 der 10000 Spiele (90,51%) nach 200 Runden endeten, was ziemlich nahe an der Vorhersage liegt, dass 90% der Spiele kooperativ sein würden. Die Implementierung dieser Bots war trivial. außer CooperativeSwarmBot hatten alle diese Form:
Weniger als 3% der Bots hatten einen Gewinnanteil, der unter 80% lag, und etwas mehr als 11% der Bots gewannen jedes einzelne Spiel, das sie spielten. Der mittlere Gewinnprozentsatz der 2900 Bots im Schwarm liegt bei 86%, was unverschämt gut ist. Zum Vergleich: Die Top-Performer der aktuellen offiziellen Bestenliste gewinnen weniger als 22% ihrer Spiele. Ich kann die vollständige Auflistung des Genossenschaftsschwarms nicht innerhalb der maximal zulässigen Länge für eine Antwort anpassen. Wenn Sie also anzeigen möchten, müssen Sie stattdessen hier klicken: https://pastebin.com/3Zc8m1Ex
Da jeder Bot in durchschnittlich 27 Spielen gespielt hat, spielt das Glück eine relativ große Rolle, wenn man die Ergebnisse für einzelne Bots betrachtet. Da ich noch keine fortgeschrittene Strategie für nicht kooperative Spiele implementiert habe, haben die meisten anderen Bots drastisch vom Spiel gegen den kooperativen Schwarm profitiert und sogar die mittlere Gewinnrate des kooperativen Schwarms von 86% erreicht.
Die vollständigen Ergebnisse für Bots, die nicht im Schwarm sind, sind unten aufgeführt. Es gibt zwei Bots, deren Ergebnisse meiner Meinung nach besondere Aufmerksamkeit verdienen. Erstens konnte StopBot überhaupt keine Spiele gewinnen. Dies ist besonders tragisch, da der Genossenschaftsschwarm genau dieselbe Strategie wie StopBot verwendete. Sie hätten erwartet, dass StopBot zufällig acht seiner Spiele gewinnt, und ein bisschen mehr, weil der kooperative Schwarm gezwungen ist, seinen Gegnern den ersten Zug zu geben. Das zweite interessante Ergebnis ist jedoch, dass sich die harte Arbeit von PointsAreForNerdsBot endlich gelohnt hat: Es hat mit dem Schwarm zusammengearbeitet und es geschafft, jedes einzelne gespielte Spiel zu gewinnen!
Mängel
Dieser kooperative Ansatz weist einige Nachteile auf. Erstens, wenn sie gegen nicht kooperative Bots spielen, erhalten kooperative Bots niemals den Vorteil der ersten Runde, weil sie, wenn sie zuerst spielen, noch nicht wissen, ob ihre Gegner zur Zusammenarbeit bereit sind oder nicht, und daher keine andere Wahl haben, als eine zu bekommen Punktzahl von Null. In ähnlicher Weise ist diese kooperative Strategie äußerst anfällig für die Ausbeutung durch böswillige Bots. Zum Beispiel kann der Bot, der zuletzt in der letzten Runde spielt, während des kooperativen Spiels sofort aufhören zu würfeln, um alle anderen zu verlieren (vorausgesetzt natürlich, sein erster Wurf war keine Sechs).
Durch die Zusammenarbeit können alle Bots die optimale Lösung mit einer Gewinnrate von 100% erzielen. Wäre also nur die Gewinnrate von Bedeutung, wäre die Zusammenarbeit ein stabiles Gleichgewicht und es gäbe nichts, worüber man sich Sorgen machen müsste. Einige Bots setzen jedoch Prioritäten für andere Ziele, z. B. das Erreichen des oberen Bereichs der Bestenliste. Dies bedeutet, dass das Risiko besteht, dass ein anderer Bot nach Ihrem letzten Zug ausfällt, was einen Anreiz für Sie darstellt, zuerst auszufallen. Da wir durch die Einrichtung dieses Wettbewerbs nicht sehen können, was unsere Gegner in ihren vorherigen Spielen getan haben, können wir Personen, die übergelaufen sind, nicht bestrafen. Kooperation ist also letztendlich ein instabiles Gleichgewicht, das zum Scheitern verurteilt ist.
Fußnoten
[1]: Die Hauptgründe, warum ich nicht Tausende von Bots anstatt nur zwei einsenden möchte, sind, dass dies die Simulation um einen Faktor in der Größenordnung von 1000 verlangsamen würde [2] und dass dies einen erheblichen Schaden anrichten würde Gewinnanteile, da andere Bots fast ausschließlich gegen den Schwarm und nicht gegeneinander spielen. Wichtiger ist jedoch die Tatsache, dass ich selbst dann, wenn ich wollte, nicht in der Lage wäre, so viele Bots in einem angemessenen Zeitrahmen zu erstellen, ohne den Geist der Regel zu brechen, dass "ein Bot nicht genau die gleiche Strategie wie ein Bots implementieren darf absichtlich oder versehentlich ".
[2]: Ich denke, es gibt zwei Hauptgründe, warum die Simulation beim Laufen eines kooperativen Schwarms langsamer wird. Erstens bedeutet mehr Bots mehr Spiele, wenn Sie möchten, dass jeder Bot in der gleichen Anzahl von Spielen spielt (in der Fallstudie würde sich die Anzahl der Spiele um den Faktor 77 unterscheiden). Zweitens dauern kooperative Spiele nur länger, weil sie volle 200 Runden dauern und die Spieler innerhalb einer Runde auf unbestimmte Zeit weitermachen müssen. Bei meinem Setup dauerte die Simulation von Spielen etwa 40-mal länger: Die Fallstudie dauerte etwas mehr als drei Minuten, um 10000 Spiele auszuführen, aber nach dem Entfernen des kooperativen Schwarms waren 10000 Spiele in nur 4,5 Sekunden beendet. Ich schätze, zwischen diesen beiden Gründen würde es ungefähr 3100 Mal länger dauern, die Leistung von Bots genau zu messen, wenn ein Schwarm im Wettbewerb ist, verglichen mit dem, wenn es keinen gibt.
quelle
GoTo20Bot
Probieren Sie es einfach mit allen aus
GoToNBot
, und 20, 22, 24 spielen am besten. Ich weiß nicht warum.Update: Stoppen Sie immer zu werfen, wenn Sie 40 oder mehr Punkte bekommen.
quelle
end_score
4000 eingestellt habe (und Ihren Bot geändert habe, um dies in dertarget
Berechnung zu verwenden), waren die 15-16 Bots viel besser. Aber wenn es bei dem Spiel nur darum ging, die Punktzahl zu erhöhen, wäre es trivial.end_score
4000 ist, ist es fast unmöglich, 4000 vor 200 Umdrehungen zu bekommen. Und das Spiel ist einfach, wer in 200 Runden die höchste Punktzahl erzielt hat. Und aufhören bei 15 sollte funktionieren, da diesmal die Strategie für die höchste Punktzahl in einer Runde dieselbe ist wie die höchste Punktzahl in 200 Runden.Adaptive Walze
Fängt aggressiver an und beruhigt sich gegen Ende der Runde.
Wenn es glaubt, dass es gewinnt, würfeln Sie eine zusätzliche Zeit für die Sicherheit.
quelle
lim = max(min(self.end_score - scores[self.index], 24), 6)
Durch Erhöhen des Maximums auf 24 und Hinzufügen eines Minimums von 6 erhöht sich der Gewinnprozentsatz für sich und noch mehr für sich.Alpha
Alpha weigert sich, hinter irgendjemandem zurückzubleiben. Solange es einen Bot mit einer höheren Punktzahl gibt, rollt er weiter.
quelle
yield
funktioniert, wenn es beginnt , rollt es wird nie aufhören. Sie möchtenmy_score
in der Schleife aktualisieren .NotTooFarBehindBot
Die Idee ist, dass andere Bots Punkte verlieren können, also ist es nicht schlecht, Zweiter zu sein - aber wenn Sie sehr weit hinten liegen, können Sie genauso gut pleite gehen.
quelle
6: Bot NotTooFarBehindBot plays [4, 2, 4, 2, 3, 3, 5, 5, 1, 4, 1, 4, 2, 4, 3, 6] with scores [0, 9, 0, 20, 0, 0, 0] and last round == False
. Obwohl Ihr Bot nach 7 Würfen die Führung innehat, geht er weiter, bis er eine 6 erreicht. Während ich dies schreibe, habe ich das Problem herausgefunden! Siescores
enthalten nur die Gesamtpunktzahlen, nicht die Fälle für die aktuelle Runde. Sie sollten es ändern, um zu seincurrent_score = scores[self.index] + sum(self.current_throws)
.GoHomeBot
Wir wollen groß oder nach Hause gehen, oder? GoHomeBot geht meistens einfach nach Hause. (Macht sich aber überraschend gut!)
quelle
scores
Liste. Es gab vorher einen solchen Bot (den GoToEnd-Bot), aber David hat seine Antwort gelöscht. Ich werde diesen Bot durch deinen ersetzen.Stellen Sie sicher, führen
EnsureLead leiht Ideen von GoTo20Bot aus. Es fügt das Konzept hinzu, dass immer berücksichtigt wird (wenn in last_round oder 40 erreicht wird), dass es andere gibt, die mindestens einen weiteren Wurf haben. So versucht der Bot, ihnen ein Stück voraus zu sein, so dass sie aufholen müssen.
quelle
Roll6TimesV2
Schlägt nicht die aktuell besten, aber ich denke, es wird besser mit mehr Bots im Spiel.
Wirklich tolles Spiel übrigens.
quelle
StopBot
Wörtlich nur einen Wurf.
Dies entspricht der Basisklasse
Bot
.quelle
BringMyOwn_dice (BMO_d)
Dieser Bot liebt Würfel, er bringt 2 (scheinbar die besten) Würfel für sich. Bevor er in einer Runde Würfel wirft, wirft er seine eigenen 2 Würfel und berechnet ihre Summe. Dies ist die Anzahl der Würfe, die er ausführen wird. Er wirft nur, wenn er noch keine 40 Punkte hat.
quelle
FooBot
quelle
# Must throw at least once
ist nicht nötig - es wird einmal geworfen, bevor Sie Ihren Bot anrufen. Ihr Bot wirft immer mindestens zweimal.make_throw
Methode früh benannt, als ich wollte, dass die Spieler ihren Zug überspringen können. Ich denke ein passenderer Name wärekeep_throwing
. Vielen Dank für das Feedback in der Sandbox, es hat wirklich geholfen, dies zu einer richtigen Herausforderung zu machen!Gehen Sie früh groß raus
Konzept: Versuchen Sie, bei einem frühen Wurf (bis 25) groß zu gewinnen, und steigen Sie dann von dort aus zu 2 Würfen auf einmal auf.
quelle
BinaryBot
Versucht, sich dem Endergebnis zu nähern, damit jemand, der die letzte Runde auslöst, sein Endergebnis übertrifft. Das Ziel liegt immer in der Mitte zwischen dem aktuellen Punktestand und dem Endpunktestand.
quelle
Hesitate
weigert sich auch, die Linie zuerst zu überqueren. Sie müssen Ihre Funktion mit demclass
Zeug umgeben.PointsAreForNerdsBot
Dieser braucht keine Erklärung.
OneInFiveBot
Würfelt so lange, bis er auf seinem eigenen 5-seitigen Würfel eine Fünf würfelt. Fünf ist weniger als sechs, also MUSS es GEWINNEN!
quelle
OneInFiveBot
ist eine nette Idee, aber ich denke, es leidet im Endspiel im Vergleich zu einigen der fortgeschritteneren Bots. Noch eine tolle Einreichung!OneInFiveBot
ist insofern recht interessant, als er durchweg die höchste erreichte Gesamtpunktzahl hat.StopBot
einen Boxsack gegeben hast: P. Der OneInFiveBot ist eigentlich ziemlich ordentlich, nette Arbeit!OneInFiveBot
und esLizduadacBot
Versucht in einem Schritt zu gewinnen. Endbedingung ist etwas willkürlich.
Dies ist auch mein erster Beitrag (und ich bin neu in Python). Wenn ich also "PointsAreForNerdsBot" besiege, würde ich mich freuen!
quelle
PointsAreForNerdsBot
, aber Ihr Bot schlägt sich tatsächlich recht gut. Ich werde die Punktzahl entweder später heute Abend oder morgen aktualisieren, aber Ihre Winrate liegt bei 15%, was über dem Durchschnitt von 12,5% liegt.Langsamer Start
Dieser Bot implementiert den TCP Slow Start Algorithmus. Er passt die Anzahl seiner Würfe ( noch ) entsprechend seiner vorherigen Runde an: Wenn er in der vorherigen Runde keine 6 gewürfelt hat, erhöht er das Nor für diese Runde; während es reduziert noch wenn es tat.
quelle
def updateValues():
sollte seindef updateValues(self):
(oderdef update_values(self):
wenn Sie PEP8 folgen wollen). Zweitens sollte der AufrufupdateValues()
stattdessenself.updateValues()
(oderself.update_vales()
) sein.i
Variable in der while-Schleife aktualisieren müssen . Im Moment übergibt Ihr Bot entweder die while-Schleife vollständig oder bleibt in der while-Schleife stecken, bis er 6 erreicht.self.nor
und sehen, wie sich dies auf die Leistung Ihres Bots auswirkt.KwisatzHaderach
In den Anfängen dieser Herausforderung (dh bevor sie
NeoBot
veröffentlicht wurde) schrieb ich einen fast trivialenOracle
Bot:habe es aber nicht gepostet, da ich es nicht interessant genug fand;) Aber einmal
NeoBot
die Führung übernahm, begann ich darüber nachzudenken, wie ich seine perfekte Fähigkeit, die Zukunft vorherzusagen, übertreffen könnte. Also hier ist ein Dünenzitat; es ist, wenn Paul Atreides, der Kwisatz Haderach, an einem Zusammenhang steht, von dem aus sich unendlich viele verschiedene Zukünfte abwickeln können:Hier war also die Antwort: Die Zukunft vorauszusehen bedeutet, sie zu ändern. und wenn Sie sehr vorsichtig sind, dann können Sie es durch selektives Handeln oder Untätigkeit auf vorteilhafte Weise ändern - zumindest die meiste Zeit. Sogar die
KwisatzHaderach
können keine 100% Gewinnrate erzielen!quelle
NeoBot
sondern auch besser ist! Mir gefällt auch, wie Sie ein Beispiel geben, was alles mit Zufälligkeit (insbesondere der Steuerung) tun sollte: Verwenden Sie Ihre eigenerandom.Random
Instanz. WieNeoBot
, scheint dies ein wenig empfindlich auf Veränderungen von nicht näheren Implementierungsdetails des Controllers.HarkonnenBot
berührt das RNG nicht; es interessiert sich überhaupt nicht für Zufallszahlen. Es vergiftet nur alle anderen Bots und läuft dann so langsam wie möglich bis zur Ziellinie. Wie viele kulinarische Köstlichkeiten ist Rache ein Gericht, das nach langer und delikater Zubereitung am besten langsam genossen wird.NeoBot
(undHarkonnenBot
) basiert esKwisatzHaderach
nur auf einem Detail der Implementierung. Insbesondere muss nicht bekannt sein, wie random.random () implementiert ist, sondern nur, dass der Controller es verwendet. DKwisatzHaderach
undHarkonnenBot
genauso zu behandeln wieNeoBot
. Sie erhalten ihre Punkte aus einer Simulation mit weniger Spielen und sind nicht in der offiziellen Simulation enthalten. Sie landen jedoch in der HighscorelisteNeoBot
. Der Hauptgrund dafür, dass sie nicht in der offiziellen Simulation dabei sind, ist, dass sie andere Bot-Strategien durcheinander bringen. Jedoch.WisdomOfCrowds
sollte für die Teilnahme gut geeignet sein, und ich bin gespannt auf die neuen Änderungen, die Sie daran vorgenommen haben!Nun, das ist offensichtlich
quelle
LastRound ist immer die letzte Runde und der letzte Bot: Es rollt weiter, bis es an der Spitze liegt. Es möchte sich auch nicht mit weniger als 15 Punkten zufrieden geben, es sei denn, es ist tatsächlich die letzte Runde oder es werden 40 Punkte erreicht.
quelle
QuotaBot
Ein naives "Quoten" -System, das ich implementiert habe und das insgesamt ziemlich gut abschneidet.
quelle
if own_score mean + 5:
gibt einen fehler für mich. Alsowhile sum(self.current_throws)
<
und>
Symbole, die mit den<pre>
Tags störten, die ich verwendeteErwartungenBot
Spielt es einfach geradeaus, berechnet den erwarteten Wert für den Würfelwurf und macht es nur, wenn es positiv ist.
Ich hatte Probleme beim Laufenlassen des Controllers, bekam einen "NameError: Name 'bots_per_game' ist nicht definiert" auf dem Multithread-Controller, also keine Ahnung, wie sich das verhält.
quelle
Segen
Segen FrankerZ GabeN Segen
quelle
Vierzigzehn
Versuchen Sie, bis zur letzten Runde 14 Punkte zu erzielen, und gehen Sie dann davon aus, dass alle anderen 14 Punkte erzielen und versuchen, diese Punktzahl zu erzielen.
quelle
TypeError: unsupported operand type(s) for -: 'list' and 'int'
mit deinem Bot.max_projected_score
das Maximum der Liste sein sollten und nicht die gesamte Liste. Stimmt das? Ansonsten bekomme ich das gleiche Problem wie TSH.Zögern
Führt zwei bescheidene Schritte aus und wartet dann darauf, dass jemand anderes die Grenze überschreitet. Die aktualisierte Version versucht nicht mehr, den Highscore zu übertreffen, sondern will ihn nur noch erreichen - Verbesserung der Leistung durch Löschen von zwei Bytes des Quellcodes!
quelle
Rebell
Dieser Bot kombiniert die einfache Strategie von
Hesitate
mit der fortgeschrittenen Strategie der letzten Runde vonBotFor2X
, versucht sich zu erinnern, wer er ist und wird wild, wenn er feststellt, dass er in einer Illusion lebt.quelle
HarkonnenBot
damit esRebel
sich nicht mehr selbst vergiften kann;) und ich habe auch gezwickt,TleilaxuBot
damitRebel
es nicht mehr auffällt !Nimm fünf
In der Hälfte der Zeit würfeln wir eine 5 vor einer 6. Wenn wir dies tun, erhalten Sie eine Auszahlung.
quelle
Chaser
Chaser versucht, auf Position eins aufzuholen. Wenn es die letzte Runde ist, versucht er verzweifelt, mindestens 50 Punkte zu erreichen. Nur zum guten Zweck wirft er mindestens vier Mal, egal was passiert
[Edit 1: Go-for-Gold-Strategie in der letzten Runde hinzugefügt]
[edit 2: Logik aktualisiert, weil ich fälschlicherweise dachte, ein Bot würde eher 40 Punkte erzielen als nur die höchste Punktzahl]
[Edit 3: Chaser im Endspiel etwas defensiver gemacht]
quelle
FutureBot
OneStepAheadBot
Als Paar Bots bringen sie ihre eigenen Würfel und werfen sie, um die Zukunft vorherzusagen. Wenn einer eine 6 ist, hört FutureBot auf, sich zu erinnern, welcher der 2 Würfel für den nächsten Wurf war, und gibt auf.
Ich frage mich, was es besser machen wird.
OneStepAhead ist OneInFive für meinen Geschmack ein wenig zu ähnlich, aber ich möchte auch sehen, wie es mit FutureBot und OneInFive verglichen wird.
Edit: Jetzt hören sie auf, nachdem sie 45 getroffen haben
quelle