Tournoi terminé!
Le tournoi est maintenant terminé! La simulation finale s’est déroulée pendant la nuit, avec un total de parties. Le gagnant est Christian Sievers avec son bot OptFor2X . Christian Sievers a également réussi à s'assurer la deuxième place avec Rebel . Toutes nos félicitations! Ci-dessous, vous pouvez voir la liste officielle des meilleurs scores du tournoi.
Si vous souhaitez toujours jouer au jeu, vous pouvez utiliser le contrôleur affiché ci-dessous et utiliser le code qu'il contient pour créer votre propre jeu.
J'ai été invité à jouer à un jeu de dés dont je n'avais jamais entendu parler. Les règles étaient simples, mais je pense que ce serait parfait pour un défi KotH.
Les règles
Le début du jeu
Le dé fait le tour de la table et chaque fois que c'est votre tour, vous lancez le dé autant de fois que vous le souhaitez. Cependant, vous devez le jeter au moins une fois. Vous gardez une trace de la somme de tous les lancers pour votre tour. Si vous choisissez d'arrêter, le score de la ronde s'ajoute à votre score total.
Alors, pourquoi voudriez-vous jamais arrêter de jeter le dé? Parce que si vous obtenez 6, votre score pour le tour entier devient zéro et le dé est transmis. Ainsi, l'objectif initial est d'augmenter votre score le plus rapidement possible.
Qui est le gagnant?
Lorsque le premier joueur autour de la table atteint 40 points ou plus, le dernier tour commence. Une fois que le dernier tour a commencé, tout le monde sauf celui qui a initié le dernier tour a droit à un tour supplémentaire.
Les règles du dernier tour sont les mêmes que pour tout autre tour. Vous choisissez de continuer à lancer ou de vous arrêter. Cependant, vous savez que vous n’avez aucune chance de gagner si vous n’obtenez pas un score plus élevé que ceux que vous aviez au dernier tour. Mais si vous continuez à aller trop loin, alors vous pourriez obtenir un 6.
Cependant, il y a une autre règle à prendre en compte. Si votre score total actuel (votre score précédent + votre score actuel du tour) est égal ou supérieur à 40 et que vous atteignez un 6, votre score total est défini sur 0. Cela signifie que vous devez tout recommencer. Si vous frappez un 6 lorsque votre score total actuel est de 40 ou plus, le jeu continue normalement, sauf que vous êtes maintenant à la dernière place. Le dernier tour n'est pas déclenché lorsque votre score total est réinitialisé. Vous pouvez toujours gagner la manche, mais cela devient plus difficile.
Le gagnant est le joueur avec le score le plus élevé une fois le dernier tour terminé. Si deux joueurs ou plus partagent le même score, ils seront tous comptés comme vainqueurs.
Une règle supplémentaire est que le jeu continue pour un maximum de 200 tours. Ceci permet d'éviter les cas où plusieurs robots continuent à lancer jusqu'à ce qu'ils atteignent 6 pour rester à leur score actuel. Une fois que le 199ème tour est passé, last_round
est défini sur true et un autre tour est joué. Si le jeu se déroule à 200 tours, le bot (ou les bots) avec le score le plus élevé est le vainqueur, même s’ils n’ont pas 40 points ou plus.
résumer
- À chaque tour, vous continuez à lancer le dé jusqu'à ce que vous choisissiez de vous arrêter ou que vous obteniez un 6
- Vous devez lancer le dé une fois (si votre premier lancer est un 6, votre tour est immédiatement terminé)
- Si vous obtenez un 6, votre score actuel est mis à 0 (pas votre score total)
- Vous ajoutez votre score actuel à votre score total après chaque tour
- Lorsqu'un bot termine son tour, ce qui donne un score total d'au moins 40, tout le monde a son dernier tour.
- Si votre score total actuel est de et que vous obtenez un 6, votre score total est mis à 0 et votre partie est terminée
- Le dernier tour n'est pas déclenché lorsque ce qui précède se produit
- La personne avec le score total le plus élevé après le dernier tour est le gagnant
- Au cas où il y aurait plusieurs gagnants, tous seront comptés comme gagnants.
- Le jeu dure 200 tours maximum
Clarification des scores
- Score total: le score que vous avez enregistré lors des tours précédents
- Score actuel: le score du tour en cours
- Score total actuel: la somme des deux scores ci-dessus
Comment participez-vous
Pour participer à ce défi KotH, vous devez écrire une classe Python dont elle hérite Bot
. Vous devez implémenter la fonction: make_throw(self, scores, last_round)
. Cette fonction sera appelée une fois que c'est votre tour, et votre premier lancer n'était pas un 6. Pour continuer à lancer, vous devriez yield True
. Pour arrêter de lancer, vous devriez yield False
. Après chaque lancer, la fonction parent update_state
est appelée. Ainsi, vous avez accès à vos lancers pour le tour en cours en utilisant la variable self.current_throws
. Vous avez également accès à votre propre index en utilisant self.index
. Ainsi, pour voir votre propre score total, vous utiliseriez scores[self.index]
. Vous pouvez également accéder au end_score
jeu en utilisant self.end_score
, mais vous pouvez supposer en toute sécurité qu'il sera de 40 pour ce défi.
Vous êtes autorisé à créer des fonctions d’aide dans votre classe. Vous pouvez également remplacer des fonctions existantes dans la Bot
classe parente, par exemple si vous souhaitez ajouter d'autres propriétés de classe. Vous n'êtes pas autorisé à modifier l'état du jeu de quelque manière que ce True
soit, sauf céder ou False
.
Vous êtes libre de vous inspirer de ce message et de copier l'un des deux robots que j'ai inclus ici. Cependant, j'ai bien peur qu'ils ne soient pas particulièrement efficaces ...
En autorisant d'autres langues
Dans le bac à sable et sur le dix-neuvième octet, nous avons discuté de l’autorisation des soumissions dans d’autres langues. Après avoir pris connaissance de ces implémentations et entendu les arguments des deux côtés, j'ai décidé de limiter ce défi à Python uniquement. Cela est dû à deux facteurs: le temps nécessaire pour prendre en charge plusieurs langues et le caractère aléatoire de ce défi qui nécessite un grand nombre d'itérations pour atteindre la stabilité. J'espère que vous continuerez à participer et si vous voulez apprendre un peu de Python pour ce défi, je vais essayer d'être disponible dans le chat le plus souvent possible.
Pour toute question que vous pourriez avoir, vous pouvez écrire dans la salle de discussion pour ce défi . On se voit là-bas!
Règles
- Le sabotage est autorisé et encouragé. C'est-à-dire sabotage contre d'autres joueurs
- Toute tentative de modification du contrôleur, de l'exécution ou de toute autre soumission sera rejetée. Toutes les soumissions ne doivent fonctionner qu'avec les entrées et le stockage qui leur sont attribués.
- Tout bot utilisant plus de 500 Mo de mémoire pour prendre sa décision sera disqualifié (si vous avez besoin de plus de mémoire, vous devriez repenser vos choix).
- Un bot ne doit pas implémenter exactement la même stratégie qu'un existant, intentionnellement ou accidentellement.
- Vous êtes autorisé à mettre à jour votre bot pendant la durée du défi. Cependant, vous pouvez également poster un autre bot si votre approche est différente.
Exemple
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Ce bot va continuer jusqu'à ce qu'il ait un score d'au moins 10 pour le tour, ou il jette un 6. Notez que vous ne avez pas besoin de logique pour gérer lancer 6. Notez également que si votre premier jet est un 6, make_throw
est jamais appelé, puisque votre tour est immédiatement terminé.
Pour ceux qui découvrent Python (et qui ne connaissent pas bien le yield
concept), mais qui veulent essayer, le yield
mot clé ressemble à un retour, à certains égards, mais différent à d'autres. Vous pouvez lire sur le concept ici . En gros, une fois que yield
votre fonction sera arrêtée, la valeur que vous yield
aurez modifiée sera renvoyée au contrôleur. Là, le contrôleur gère sa logique jusqu’à ce que votre bot prenne une autre décision. Ensuite, le contrôleur vous envoie le lancer de dés, et votre make_throw
fonction continuera à s’exécuter exactement là où elle s’est arrêtée avant, essentiellement sur la ligne après la yield
déclaration précédente .
De cette façon, le contrôleur de jeu peut mettre à jour l'état sans nécessiter un appel de fonction bot distinct pour chaque lancer de dés.
spécification
Vous pouvez utiliser n’importe quelle bibliothèque Python disponible dans pip
. Pour que je puisse obtenir une bonne moyenne, vous avez une limite de temps de 100 millisecondes par tour. Je serais vraiment heureux si votre script était bien plus rapide que ça, afin que je puisse faire plus de rondes.
Évaluation
Pour trouver le gagnant, je prendrai tous les robots et les ferai par groupes de 8. Si moins de 8 classes ont été soumises, je les ferai par groupes de 4 au hasard pour éviter d'avoir toujours tous les bots à chaque tour. Je vais faire des simulations pendant environ 8 heures, et le gagnant sera le bot avec le pourcentage de victoire le plus élevé. Je lancerai les dernières simulations au début de 2019, en vous donnant tout Noël pour coder vos robots! La date finale préliminaire est le 4 janvier, mais si le temps est compté, je peux le changer pour une date ultérieure.
En attendant, je vais essayer de faire une simulation quotidienne en utilisant 30 à 60 minutes de temps CPU et en mettant à jour le tableau de résultats. Ce ne sera pas le score officiel, mais il servira de guide pour voir quels bots sont les plus performants. Cependant, à l'approche de Noël, j'espère que vous comprendrez que je ne serai pas disponible à tout moment. Je ferai de mon mieux pour exécuter des simulations et répondre à toutes les questions relatives au défi.
Testez-le vous-même
Si vous souhaitez exécuter vos propres simulations, voici le code complet destiné au contrôleur qui exécute la simulation, y compris deux exemples de robots.
Manette
Voici le contrôleur mis à jour pour ce défi. Il supporte les sorties ANSI, le multi-threading et collecte des statistiques supplémentaires grâce à AKroell ! Lorsque je modifie le contrôleur, je mets à jour le message une fois la documentation terminée.
Grâce à BMO , le contrôleur est maintenant en mesure de télécharger tous les robots de cet article en utilisant le -d
drapeau. Les autres fonctionnalités ne sont pas modifiées dans cette version. Cela devrait vous assurer que toutes vos dernières modifications sont simulées dès que possible!
#!/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)
Si vous souhaitez accéder au contrôleur d'origine pour ce défi, il est disponible dans l'historique des modifications. Le nouveau contrôleur a exactement la même logique pour exécuter le jeu, la seule différence est la performance, la collecte de statistiques et une impression plus jolie.
Bots
Sur ma machine, les robots sont conservés dans le fichier forty_game_bots.py
. Si vous utilisez un autre nom pour le fichier, vous devez mettre à jour l' import
instruction en haut du contrôleur.
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
Lancer la simulation
Pour exécuter une simulation, enregistrez les deux extraits de code publiés ci-dessus dans deux fichiers distincts. Je les ai sauvegardés au fur forty_game_controller.py
et à mesure forty_game_bots.py
. Ensuite, vous utilisez simplement python forty_game_controller.py
ou en python3 forty_game_controller.py
fonction de votre configuration Python. Suivez les instructions à partir de là si vous souhaitez configurer davantage votre simulation ou essayez de modifier le code si vous le souhaitez.
Statistiques du jeu
Si vous créez un bot qui vise un certain score sans prendre en compte les autres robots, voici les centiles de score gagnants:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Scores élevés
Comme plus de réponses sont postées, je vais essayer de garder cette liste à jour. Le contenu de la liste sera toujours de la dernière simulation. Les bots ThrowTwiceBot
et GoToTenBot
sont les bots du code ci-dessus, et sont utilisés comme référence. J'ai fait une simulation avec 10 ^ 8 jeux, ce qui a pris environ 1 heure. Ensuite, j'ai constaté que le jeu avait atteint la stabilité par rapport à mes manches de 10 ^ 7 parties. Cependant, comme les gens continuent à envoyer des bots, je ne ferai plus de simulations tant que la fréquence des réponses n’aura pas diminué.
J'essaie d'ajouter de nouveaux robots et d'ajouter les modifications que vous avez apportées aux robots existants. S'il semble que j'ai raté votre bot ou vos modifications, écrivez dans le chat et je m'assurerai d'avoir votre toute dernière version dans la prochaine simulation.
Nous avons maintenant plus de statistiques pour chaque bot grâce à AKroell ! Les trois nouvelles colonnes contiennent le score maximum pour tous les jeux, le score moyen par match et le score moyen lors de la victoire pour chaque bot.
Comme indiqué dans les commentaires, il y avait un problème avec la logique de jeu qui faisait que les bots ayant un indice plus élevé au sein d'un jeu obtenaient une partie supplémentaire dans certains cas. Ceci a été corrigé maintenant, et les scores ci-dessous reflètent cela.
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|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
Les bots suivants (sauf Rebel
) sont faits pour se plier aux règles, et les créateurs ont accepté de ne pas participer au tournoi officiel. Cependant, je pense toujours que leurs idées sont créatives et qu’elles méritent une mention honorable. Rebel figure également sur cette liste car il utilise une stratégie astucieuse pour éviter le sabotage et fonctionne mieux avec le bot saboteur en jeu.
Les bots NeoBot
et KwisatzHaderach
suivent les règles, mais utilisent une échappatoire en prédisant le générateur aléatoire. Comme ces robots nécessitent beaucoup de ressources pour simuler, j'ai ajouté ses statistiques à partir d'une simulation avec moins de jeux. Le bot HarkonnenBot
réalise la victoire en désactivant tous les autres robots, ce qui est strictement contraire aux règles.
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|
+----------+-----+