Турнир окончен!
Турнир окончен! Финальная симуляция была проведена ночью, всего игр. Победителем стал Кристиан Сиверс со своим ботом OptFor2X . Кристиану Сиверсу также удалось завоевать второе место с повстанцами . Поздравляем! Ниже вы можете увидеть официальный список рекордов турнира.
Если вы все еще хотите играть в игру, вы можете использовать контроллер, указанный ниже, и использовать код для создания своей собственной игры.
Меня пригласили сыграть в игру в кости, о которой я никогда не слышал. Правила были просты, но я думаю, что это было бы идеально для вызова KotH.
Правила
Начало игры
Кубик проходит по столу, и каждый раз, когда наступает ваша очередь, вы можете бросать кубик столько раз, сколько захотите. Тем не менее, вы должны бросить его хотя бы один раз. Вы отслеживаете сумму всех бросков за ваш раунд. Если вы решите остановиться, счет за раунд добавляется к вашему общему счету.
Так почему бы тебе перестать бросать кубик? Потому что, если вы получите 6, ваш счет за весь раунд станет нулевым, и кубик будет передан. Таким образом, первоначальная цель - как можно быстрее увеличить ваш счет.
Кто является победителем?
Когда первый игрок за столом набирает 40 или более очков, начинается последний раунд. Как только начался последний раунд, все, кроме того, кто инициировал последний раунд, получают еще один ход.
Правила для последнего раунда такие же, как и для любого другого раунда. Вы выбираете продолжать бросать или останавливаться. Тем не менее, вы знаете, что у вас нет шансов на победу, если вы не наберете больше очков, чем те, которые были до вас в последнем раунде. Но если вы продолжите идти слишком далеко, вы можете получить 6.
Однако есть еще одно правило, которое необходимо учитывать. Если ваш текущий общий балл (ваш предыдущий балл + ваш текущий балл за раунд) составляет 40 или более, и вы набрали 6, ваш общий балл устанавливается равным 0. Это означает, что вы должны начинать все сначала. Если вы набрали 6, когда ваш текущий общий счет 40 или больше, игра продолжится как обычно, за исключением того, что вы сейчас на последнем месте. Последний раунд не запускается, когда ваш общий счет сбрасывается. Вы все еще можете выиграть раунд, но он становится более сложным.
Победителем становится игрок с наибольшим количеством очков после окончания последнего раунда. Если два или более игроков имеют одинаковый счет, все они будут считаться победителями.
Дополнительное правило заключается в том, что игра продолжается максимум 200 раундов. Это сделано для предотвращения случаев, когда несколько ботов в основном продолжают бросать, пока не достигнут 6, чтобы остаться на своем текущем счете. После того, как 199-й раунд пройден, last_round
устанавливается значение true и воспроизводится еще один раунд. Если игра идет до 200 раундов, бот (или боты) с наибольшим количеством очков считается победителем, даже если у них нет 40 и более очков.
резюмировать
- Каждый раунд вы продолжаете бросать кубик, пока не решите остановиться или не получите 6
- Вы должны бросить кубик один раз (если ваш первый бросок 6, ваш раунд сразу заканчивается)
- Если вы получите 6, ваш текущий счет будет установлен на 0 (не ваш общий счет)
- Вы добавляете свой текущий счет к общему количеству очков после каждого раунда.
- Когда бот заканчивает свой ход, в результате чего общий счет не менее 40, все остальные получают последний ход
- Если ваш текущий общий балл и вы получаете 6, ваш общий балл будет равен 0 и ваш раунд окончен
- Последний раунд не запускается, когда происходит выше
- Человек с наибольшим общим счетом после последнего тура является победителем
- В случае, если есть несколько победителей, все будут считаться победителями
- Игра длится не более 200 раундов
Разъяснение результатов
- Общая оценка: оценка, которую вы сохранили в предыдущих раундах
- Текущий счет: счет за текущий раунд
- Текущий общий балл: сумма двух баллов выше
Как вы участвуете
Чтобы принять участие в этой задаче KotH, вы должны написать класс Python, который наследуется от Bot
. Вы должны реализовать функцию: make_throw(self, scores, last_round)
. Эта функция будет вызываться, как только наступит ваш ход, и ваш первый бросок не был 6. Чтобы продолжать бросать, вы должны это сделать yield True
. Чтобы прекратить бросать, вы должны yield False
. После каждого броска update_state
вызывается родительская функция . Таким образом, у вас есть доступ к вашим броскам за текущий раунд с использованием переменной self.current_throws
. У вас также есть доступ к своему индексу с помощью self.index
. Таким образом, чтобы увидеть свой общий счет, вы бы использовали scores[self.index]
. Вы также можете получить доступ end_score
к игре с помощью self.end_score
, но вы можете смело предположить, что для этого испытания будет 40.
Вам разрешено создавать вспомогательные функции внутри вашего класса. Вы также можете переопределить функции, существующие в Bot
родительском классе, например, если вы хотите добавить больше свойств класса. Вам не разрешается изменять состояние игры каким-либо образом, кроме сдачи True
или False
.
Вы можете черпать вдохновение из этого поста и скопировать любого из двух ботов, которые я здесь включил. Тем не менее, я боюсь, что они не особенно эффективны ...
На разрешении других языков
Как в песочнице, так и в «Девятнадцатом байте» мы обсуждали вопрос о разрешении подачи заявок на других языках. Прочитав о таких реализациях и выслушав аргументы обеих сторон, я решил ограничить этот вызов только Python. Это связано с двумя факторами: временем, необходимым для поддержки нескольких языков, и случайностью этой задачи, требующей большого количества итераций для достижения стабильности. Я надеюсь, что вы по-прежнему будете участвовать, и если вы хотите изучить Python для этой задачи, я постараюсь быть доступным в чате как можно чаще.
По любым вопросам, которые у вас могут возникнуть, вы можете написать в чате на эту проблему . Увидимся там!
правила
- Саботаж допускается и поощряется. То есть саботаж против других игроков
- Любая попытка возиться с контроллером, во время выполнения или другими представлениями будет дисквалифицирована. Все представления должны работать только с входами и хранилищами, которые им предоставляются.
- Любой бот, который использует более 500 МБ памяти для принятия решения, будет дисквалифицирован (если вам нужно столько памяти, вам следует пересмотреть свой выбор)
- Бот не должен реализовывать ту же стратегию, что и существующая, намеренно или случайно.
- Вы можете обновить своего бота во время испытания. Тем не менее, вы также можете разместить другого бота, если ваш подход отличается.
пример
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Этот бот будет продолжать работать до тех пор, пока у него не будет по крайней мере 10 очков за раунд или пока он не выбросит 6. Обратите внимание, что вам не нужна логика для обработки броска 6. Также обратите внимание, что если ваш первый бросок равен 6, make_throw
это никогда не звонил, так как ваш раунд сразу закончился.
Для тех, кто плохо знаком с Python (и новичок в yield
концепции), но хочет попробовать, yield
ключевое слово похоже на возврат в некоторых отношениях, но отличается в других. Вы можете прочитать о концепции здесь . По сути, как только вы yield
, ваша функция остановится, и значение, которое вы yield
отредактировали, будет отправлено обратно в контроллер. Там контроллер обрабатывает свою логику до тех пор, пока ваш бот не примет другое решение. Затем контроллер отправляет вам бросок игральных костей, и ваша make_throw
функция будет продолжать выполняться там, где она остановлена раньше, в основном на строке после предыдущего yield
оператора.
Таким образом, игровой контроллер может обновлять состояние, не требуя отдельного вызова функции бота для каждого броска костей.
Спецификация
Вы можете использовать любую библиотеку Python, доступную в pip
. Чтобы убедиться, что я смогу получить хорошее среднее значение, у вас есть ограничение в 100 миллисекунд на раунд. Я был бы очень рад, если бы ваш сценарий был намного быстрее, чтобы я мог запустить больше раундов.
оценка
Чтобы найти победителя, я беру всех ботов и запускаю их в случайных группах по 8. Если представлено менее 8 классов, я буду запускать их в случайных группах по 4, чтобы избежать того, чтобы в каждом раунде были все боты. Я буду проводить симуляции около 8 часов, и победителем станет бот с самым высоким процентом побед. Я начну финальные симуляции в начале 2019 года, и у вас будет все Рождество, чтобы кодировать своих ботов! Предварительная окончательная дата - 4 января, но если это слишком мало времени, я могу изменить ее на более позднюю дату.
До этого я постараюсь делать ежедневные симуляции, используя 30-60 минут процессорного времени, и обновлять табло. Это не будет официальным счетом, но он послужит руководством, чтобы увидеть, какие боты показывают лучшие результаты. Однако, с приближением Рождества, я надеюсь, вы понимаете, что я не буду доступен в любое время. Я сделаю все возможное, чтобы проводить симуляции и отвечать на любые вопросы, связанные с задачей.
Проверь это сам
Если вы хотите запустить собственное моделирование, вот полный код контроллера, выполняющего моделирование, включая два примера ботов.
контроллер
Вот обновленный контроллер для этой задачи. Он поддерживает выходы ANSI, многопоточность и собирает дополнительную статистику благодаря AKroell ! Когда я внесу изменения в контроллер, я обновлю сообщение, как только документация будет завершена.
Благодаря BMO , контроллер теперь может загружать всех ботов из этого поста, используя -d
флаг. Другие функции не изменились в этой версии. Это должно гарантировать, что все ваши последние изменения будут смоделированы как можно скорее!
#!/usr/bin/env python3
import re
import json
import math
import random
import requests
import sys
import time
from numpy import cumsum
from collections import defaultdict
from html import unescape
from lxml import html
from multiprocessing import Pool
from os import path, rename, remove
from sys import stderr
from time import strftime
# If you want to see what each bot decides, set this to true
# Should only be used with one thread and one game
DEBUG = False
# If your terminal supports ANSI, try setting this to true
ANSI = False
# File to keep base class and own bots
OWN_FILE = 'forty_game_bots.py'
# File where to store the downloaded bots
AUTO_FILE = 'auto_bots.py'
# If you want to use up all your quota & re-download all bots
DOWNLOAD = False
# If you want to ignore a specific user's bots (eg. your own bots): add to list
IGNORE = []
# The API-request to get all the bots
URL = "https://api.stackexchange.com/2.2/questions/177765/answers?page=%s&pagesize=100&order=desc&sort=creation&site=codegolf&filter=!bLf7Wx_BfZlJ7X"
def print_str(x, y, string):
print("\033["+str(y)+";"+str(x)+"H"+string, end = "", flush = True)
class bcolors:
WHITE = '\033[0m'
GREEN = '\033[92m'
BLUE = '\033[94m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
# Class for handling the game logic and relaying information to the bots
class Controller:
def __init__(self, bots_per_game, games, bots, thread_id):
"""Initiates all fields relevant to the simulation
Keyword arguments:
bots_per_game -- the number of bots that should be included in a game
games -- the number of games that should be simulated
bots -- a list of all available bot classes
"""
self.bots_per_game = bots_per_game
self.games = games
self.bots = bots
self.number_of_bots = len(self.bots)
self.wins = defaultdict(int)
self.played_games = defaultdict(int)
self.bot_timings = defaultdict(float)
# self.wins = {bot.__name__: 0 for bot in self.bots}
# self.played_games = {bot.__name__: 0 for bot in self.bots}
self.end_score = 40
self.thread_id = thread_id
self.max_rounds = 200
self.timed_out_games = 0
self.tied_games = 0
self.total_rounds = 0
self.highest_round = 0
#max, avg, avg_win, throws, success, rounds
self.highscore = defaultdict(lambda:[0, 0, 0, 0, 0, 0])
self.winning_scores = defaultdict(int)
# self.highscore = {bot.__name__: [0, 0, 0] for bot in self.bots}
# Returns a fair dice throw
def throw_die(self):
return random.randint(1,6)
# Print the current game number without newline
def print_progress(self, progress):
length = 50
filled = int(progress*length)
fill = "="*filled
space = " "*(length-filled)
perc = int(100*progress)
if ANSI:
col = [
bcolors.RED,
bcolors.YELLOW,
bcolors.WHITE,
bcolors.BLUE,
bcolors.GREEN
][int(progress*4)]
end = bcolors.ENDC
print_str(5, 8 + self.thread_id,
"\t%s[%s%s] %3d%%%s" % (col, fill, space, perc, end)
)
else:
print(
"\r\t[%s%s] %3d%%" % (fill, space, perc),
flush = True,
end = ""
)
# Handles selecting bots for each game, and counting how many times
# each bot has participated in a game
def simulate_games(self):
for game in range(self.games):
if self.games > 100:
if game % (self.games // 100) == 0 and not DEBUG:
if self.thread_id == 0 or ANSI:
progress = (game+1) / self.games
self.print_progress(progress)
game_bot_indices = random.sample(
range(self.number_of_bots),
self.bots_per_game
)
game_bots = [None for _ in range(self.bots_per_game)]
for i, bot_index in enumerate(game_bot_indices):
self.played_games[self.bots[bot_index].__name__] += 1
game_bots[i] = self.bots[bot_index](i, self.end_score)
self.play(game_bots)
if not DEBUG and (ANSI or self.thread_id == 0):
self.print_progress(1)
self.collect_results()
def play(self, game_bots):
"""Simulates a single game between the bots present in game_bots
Keyword arguments:
game_bots -- A list of instantiated bot objects for the game
"""
last_round = False
last_round_initiator = -1
round_number = 0
game_scores = [0 for _ in range(self.bots_per_game)]
# continue until one bot has reached end_score points
while not last_round:
for index, bot in enumerate(game_bots):
t0 = time.clock()
self.single_bot(index, bot, game_scores, last_round)
t1 = time.clock()
self.bot_timings[bot.__class__.__name__] += t1-t0
if game_scores[index] >= self.end_score and not last_round:
last_round = True
last_round_initiator = index
round_number += 1
# maximum of 200 rounds per game
if round_number > self.max_rounds - 1:
last_round = True
self.timed_out_games += 1
# this ensures that everyone gets their last turn
last_round_initiator = self.bots_per_game
# make sure that all bots get their last round
for index, bot in enumerate(game_bots[:last_round_initiator]):
t0 = time.clock()
self.single_bot(index, bot, game_scores, last_round)
t1 = time.clock()
self.bot_timings[bot.__class__.__name__] += t1-t0
# calculate which bots have the highest score
max_score = max(game_scores)
nr_of_winners = 0
for i in range(self.bots_per_game):
bot_name = game_bots[i].__class__.__name__
# average score per bot
self.highscore[bot_name][1] += game_scores[i]
if self.highscore[bot_name][0] < game_scores[i]:
# maximum score per bot
self.highscore[bot_name][0] = game_scores[i]
if game_scores[i] == max_score:
# average winning score per bot
self.highscore[bot_name][2] += game_scores[i]
nr_of_winners += 1
self.wins[bot_name] += 1
if nr_of_winners > 1:
self.tied_games += 1
self.total_rounds += round_number
self.highest_round = max(self.highest_round, round_number)
self.winning_scores[max_score] += 1
def single_bot(self, index, bot, game_scores, last_round):
"""Simulates a single round for one bot
Keyword arguments:
index -- The player index of the bot (e.g. 0 if the bot goes first)
bot -- The bot object about to be simulated
game_scores -- A list of ints containing the scores of all players
last_round -- Boolean describing whether it is currently the last round
"""
current_throws = [self.throw_die()]
if current_throws[-1] != 6:
bot.update_state(current_throws[:])
for throw in bot.make_throw(game_scores[:], last_round):
# send the last die cast to the bot
if not throw:
break
current_throws.append(self.throw_die())
if current_throws[-1] == 6:
break
bot.update_state(current_throws[:])
if current_throws[-1] == 6:
# reset total score if running total is above end_score
if game_scores[index] + sum(current_throws) - 6 >= self.end_score:
game_scores[index] = 0
else:
# add to total score if no 6 is cast
game_scores[index] += sum(current_throws)
if DEBUG:
desc = "%d: Bot %24s plays %40s with " + \
"scores %30s and last round == %5s"
print(desc % (index, bot.__class__.__name__,
current_throws, game_scores, last_round))
bot_name = bot.__class__.__name__
# average throws per round
self.highscore[bot_name][3] += len(current_throws)
# average success rate per round
self.highscore[bot_name][4] += int(current_throws[-1] != 6)
# total number of rounds
self.highscore[bot_name][5] += 1
# Collects all stats for the thread, so they can be summed up later
def collect_results(self):
self.bot_stats = {
bot.__name__: [
self.wins[bot.__name__],
self.played_games[bot.__name__],
self.highscore[bot.__name__]
]
for bot in self.bots}
#
def print_results(total_bot_stats, total_game_stats, elapsed_time):
"""Print the high score after the simulation
Keyword arguments:
total_bot_stats -- A list containing the winning stats for each thread
total_game_stats -- A list containing controller stats for each thread
elapsed_time -- The number of seconds that it took to run the simulation
"""
# Find the name of each bot, the number of wins, the number
# of played games, and the win percentage
wins = defaultdict(int)
played_games = defaultdict(int)
highscores = defaultdict(lambda: [0, 0, 0, 0, 0, 0])
bots = set()
timed_out_games = sum(s[0] for s in total_game_stats)
tied_games = sum(s[1] for s in total_game_stats)
total_games = sum(s[2] for s in total_game_stats)
total_rounds = sum(s[4] for s in total_game_stats)
highest_round = max(s[5] for s in total_game_stats)
average_rounds = total_rounds / total_games
winning_scores = defaultdict(int)
bot_timings = defaultdict(float)
for stats in total_game_stats:
for score, count in stats[6].items():
winning_scores[score] += count
percentiles = calculate_percentiles(winning_scores, total_games)
for thread in total_bot_stats:
for bot, stats in thread.items():
wins[bot] += stats[0]
played_games[bot] += stats[1]
highscores[bot][0] = max(highscores[bot][0], stats[2][0])
for i in range(1, 6):
highscores[bot][i] += stats[2][i]
bots.add(bot)
for bot in bots:
bot_timings[bot] += sum(s[3][bot] for s in total_game_stats)
bot_stats = [[bot, wins[bot], played_games[bot], 0] for bot in bots]
for i, bot in enumerate(bot_stats):
bot[3] = 100 * bot[1] / bot[2] if bot[2] > 0 else 0
bot_stats[i] = tuple(bot)
# Sort the bots by their winning percentage
sorted_scores = sorted(bot_stats, key=lambda x: x[3], reverse=True)
# Find the longest class name for any bot
max_len = max([len(b[0]) for b in bot_stats])
# Print the highscore list
if ANSI:
print_str(0, 9 + threads, "")
else:
print("\n")
sim_msg = "\tSimulation or %d games between %d bots " + \
"completed in %.1f seconds"
print(sim_msg % (total_games, len(bots), elapsed_time))
print("\tEach game lasted for an average of %.2f rounds" % average_rounds)
print("\t%d games were tied between two or more bots" % tied_games)
print("\t%d games ran until the round limit, highest round was %d\n"
% (timed_out_games, highest_round))
print_bot_stats(sorted_scores, max_len, highscores)
print_score_percentiles(percentiles)
print_time_stats(bot_timings, max_len)
def calculate_percentiles(winning_scores, total_games):
percentile_bins = 10000
percentiles = [0 for _ in range(percentile_bins)]
sorted_keys = list(sorted(winning_scores.keys()))
sorted_values = [winning_scores[key] for key in sorted_keys]
cumsum_values = list(cumsum(sorted_values))
i = 0
for perc in range(percentile_bins):
while cumsum_values[i] < total_games * (perc+1) / percentile_bins:
i += 1
percentiles[perc] = sorted_keys[i]
return percentiles
def print_score_percentiles(percentiles):
n = len(percentiles)
show = [.5, .75, .9, .95, .99, .999, .9999]
print("\t+----------+-----+")
print("\t|Percentile|Score|")
print("\t+----------+-----+")
for p in show:
print("\t|%10.2f|%5d|" % (100*p, percentiles[int(p*n)]))
print("\t+----------+-----+")
print()
def print_bot_stats(sorted_scores, max_len, highscores):
"""Print the stats for the bots
Keyword arguments:
sorted_scores -- A list containing the bots in sorted order
max_len -- The maximum name length for all bots
highscores -- A dict with additional stats for each bot
"""
delimiter_format = "\t+%s%s+%s+%s+%s+%s+%s+%s+%s+%s+"
delimiter_args = ("-"*(max_len), "", "-"*4, "-"*8,
"-"*8, "-"*6, "-"*6, "-"*7, "-"*6, "-"*8)
delimiter_str = delimiter_format % delimiter_args
print(delimiter_str)
print("\t|%s%s|%4s|%8s|%8s|%6s|%6s|%7s|%6s|%8s|"
% ("Bot", " "*(max_len-3), "Win%", "Wins",
"Played", "Max", "Avg", "Avg win", "Throws", "Success%"))
print(delimiter_str)
for bot, wins, played, score in sorted_scores:
highscore = highscores[bot]
bot_max_score = highscore[0]
bot_avg_score = highscore[1] / played
bot_avg_win_score = highscore[2] / max(1, wins)
bot_avg_throws = highscore[3] / highscore[5]
bot_success_rate = 100 * highscore[4] / highscore[5]
space_fill = " "*(max_len-len(bot))
format_str = "\t|%s%s|%4.1f|%8d|%8d|%6d|%6.2f|%7.2f|%6.2f|%8.2f|"
format_arguments = (bot, space_fill, score, wins,
played, bot_max_score, bot_avg_score,
bot_avg_win_score, bot_avg_throws, bot_success_rate)
print(format_str % format_arguments)
print(delimiter_str)
print()
def print_time_stats(bot_timings, max_len):
"""Print the execution time for all bots
Keyword arguments:
bot_timings -- A dict containing information about timings for each bot
max_len -- The maximum name length for all bots
"""
total_time = sum(bot_timings.values())
sorted_times = sorted(bot_timings.items(),
key=lambda x: x[1], reverse = True)
delimiter_format = "\t+%s+%s+%s+"
delimiter_args = ("-"*(max_len), "-"*7, "-"*5)
delimiter_str = delimiter_format % delimiter_args
print(delimiter_str)
print("\t|%s%s|%7s|%5s|" % ("Bot", " "*(max_len-3), "Time", "Time%"))
print(delimiter_str)
for bot, bot_time in sorted_times:
space_fill = " "*(max_len-len(bot))
perc = 100 * bot_time / total_time
print("\t|%s%s|%7.2f|%5.1f|" % (bot, space_fill, bot_time, perc))
print(delimiter_str)
print()
def run_simulation(thread_id, bots_per_game, games_per_thread, bots):
"""Used by multithreading to run the simulation in parallel
Keyword arguments:
thread_id -- A unique identifier for each thread, starting at 0
bots_per_game -- How many bots should participate in each game
games_per_thread -- The number of games to be simulated
bots -- A list of all bot classes available
"""
try:
controller = Controller(bots_per_game,
games_per_thread, bots, thread_id)
controller.simulate_games()
controller_stats = (
controller.timed_out_games,
controller.tied_games,
controller.games,
controller.bot_timings,
controller.total_rounds,
controller.highest_round,
controller.winning_scores
)
return (controller.bot_stats, controller_stats)
except KeyboardInterrupt:
return {}
# Prints the help for the script
def print_help():
print("\nThis is the controller for the PPCG KotH challenge " + \
"'A game of dice, but avoid number 6'")
print("For any question, send a message to maxb\n")
print("Usage: python %s [OPTIONS]" % sys.argv[0])
print("\n -n\t\tthe number of games to simluate")
print(" -b\t\tthe number of bots per round")
print(" -t\t\tthe number of threads")
print(" -d\t--download\tdownload all bots from codegolf.SE")
print(" -A\t--ansi\trun in ANSI mode, with prettier printing")
print(" -D\t--debug\trun in debug mode. Sets to 1 thread, 1 game")
print(" -h\t--help\tshow this help\n")
# Make a stack-API request for the n-th page
def req(n):
req = requests.get(URL % n)
req.raise_for_status()
return req.json()
# Pull all the answers via the stack-API
def get_answers():
n = 1
api_ans = req(n)
answers = api_ans['items']
while api_ans['has_more']:
n += 1
if api_ans['quota_remaining']:
api_ans = req(n)
answers += api_ans['items']
else:
break
m, r = api_ans['quota_max'], api_ans['quota_remaining']
if 0.1 * m > r:
print(" > [WARN]: only %s/%s API-requests remaining!" % (r,m), file=stderr)
return answers
def download_players():
players = {}
for ans in get_answers():
name = unescape(ans['owner']['display_name'])
bots = []
root = html.fromstring('<body>%s</body>' % ans['body'])
for el in root.findall('.//code'):
code = el.text
if re.search(r'^class \w+\(\w*Bot\):.*$', code, flags=re.MULTILINE):
bots.append(code)
if not bots:
print(" > [WARN] user '%s': couldn't locate any bots" % name, file=stderr)
elif name in players:
players[name] += bots
else:
players[name] = bots
return players
# Download all bots from codegolf.stackexchange.com
def download_bots():
print('pulling bots from the interwebs..', file=stderr)
try:
players = download_players()
except Exception as ex:
print('FAILED: (%s)' % ex, file=stderr)
exit(1)
if path.isfile(AUTO_FILE):
print(' > move: %s -> %s.old' % (AUTO_FILE,AUTO_FILE), file=stderr)
if path.exists('%s.old' % AUTO_FILE):
remove('%s.old' % AUTO_FILE)
rename(AUTO_FILE, '%s.old' % AUTO_FILE)
print(' > writing players to %s' % AUTO_FILE, file=stderr)
f = open(AUTO_FILE, 'w+', encoding='utf8')
f.write('# -*- coding: utf-8 -*- \n')
f.write('# Bots downloaded from https://codegolf.stackexchange.com/questions/177765 @ %s\n\n' % strftime('%F %H:%M:%S'))
with open(OWN_FILE, 'r') as bfile:
f.write(bfile.read()+'\n\n\n# Auto-pulled bots:\n\n')
for usr in players:
if usr not in IGNORE:
for bot in players[usr]:
f.write('# User: %s\n' % usr)
f.write(bot+'\n\n')
f.close()
print('OK: pulled %s bots' % sum(len(bs) for bs in players.values()))
if __name__ == "__main__":
games = 10000
bots_per_game = 8
threads = 4
for i, arg in enumerate(sys.argv):
if arg == "-n" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
games = int(sys.argv[i+1])
if arg == "-b" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
bots_per_game = int(sys.argv[i+1])
if arg == "-t" and len(sys.argv) > i+1 and sys.argv[i+1].isdigit():
threads = int(sys.argv[i+1])
if arg == "-d" or arg == "--download":
DOWNLOAD = True
if arg == "-A" or arg == "--ansi":
ANSI = True
if arg == "-D" or arg == "--debug":
DEBUG = True
if arg == "-h" or arg == "--help":
print_help()
quit()
if ANSI:
print(chr(27) + "[2J", flush = True)
print_str(1,3,"")
else:
print()
if DOWNLOAD:
download_bots()
exit() # Before running other's code, you might want to inspect it..
if path.isfile(AUTO_FILE):
exec('from %s import *' % AUTO_FILE[:-3])
else:
exec('from %s import *' % OWN_FILE[:-3])
bots = get_all_bots()
if bots_per_game > len(bots):
bots_per_game = len(bots)
if bots_per_game < 2:
print("\tAt least 2 bots per game is needed")
bots_per_game = 2
if games <= 0:
print("\tAt least 1 game is needed")
games = 1
if threads <= 0:
print("\tAt least 1 thread is needed")
threads = 1
if DEBUG:
print("\tRunning in debug mode, with 1 thread and 1 game")
threads = 1
games = 1
games_per_thread = math.ceil(games / threads)
print("\tStarting simulation with %d bots" % len(bots))
sim_str = "\tSimulating %d games with %d bots per game"
print(sim_str % (games, bots_per_game))
print("\tRunning simulation on %d threads" % threads)
if len(sys.argv) == 1:
print("\tFor help running the script, use the -h flag")
print()
with Pool(threads) as pool:
t0 = time.time()
results = pool.starmap(
run_simulation,
[(i, bots_per_game, games_per_thread, bots) for i in range(threads)]
)
t1 = time.time()
if not DEBUG:
total_bot_stats = [r[0] for r in results]
total_game_stats = [r[1] for r in results]
print_results(total_bot_stats, total_game_stats, t1-t0)
Если вы хотите получить доступ к исходному контроллеру для этой задачи, он доступен в истории редактирования. Новый контроллер имеет ту же логику для запуска игры, единственное отличие - производительность, сбор статистики и более приятная печать.
Боты
На моей машине боты хранятся в файле forty_game_bots.py
. Если вы используете любое другое имя для файла, вы должны обновить import
оператор в верхней части контроллера.
import sys, inspect
import random
import numpy as np
# Returns a list of all bot classes which inherit from the Bot class
def get_all_bots():
return Bot.__subclasses__()
# The parent class for all bots
class Bot:
def __init__(self, index, end_score):
self.index = index
self.end_score = end_score
def update_state(self, current_throws):
self.current_throws = current_throws
def make_throw(self, scores, last_round):
yield False
class ThrowTwiceBot(Bot):
def make_throw(self, scores, last_round):
yield True
yield False
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Запуск симуляции
Чтобы запустить симуляцию, сохраните оба приведенных выше фрагмента кода в двух отдельных файлах. Я сохранил их как forty_game_controller.py
и forty_game_bots.py
. Затем вы просто используете python forty_game_controller.py
или в python3 forty_game_controller.py
зависимости от конфигурации Python. Следуйте инструкциям оттуда, если вы хотите настроить симуляцию дальше, или попробуйте поработать с кодом, если хотите.
Статистика игры
Если вы создаете бота, который стремится к определенному количеству очков, не принимая во внимание других ботов, это процентили победных очков:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Лучшие результаты
По мере появления новых ответов я постараюсь обновлять этот список. Содержимое списка всегда будет из последней симуляции. Боты ThrowTwiceBot
и GoToTenBot
боты из кода выше, и используются в качестве ссылки. Я провел симуляцию с 10 ^ 8 играми, что заняло около 1 часа. Затем я увидел, что игра достигла стабильности по сравнению с моими пробежками с 10 ^ 7 играми. Однако, поскольку люди все еще публикуют ботов, я больше не буду делать симуляции, пока частота ответов не уменьшится.
Я пытаюсь добавить всех новых ботов и добавить любые изменения, которые вы внесли в существующие боты. Если мне кажется, что я пропустил вашего бота или какие-либо изменения, которые у вас есть, напишите в чате, и я позабочусь о том, чтобы в следующей симуляции была ваша самая последняя версия.
Теперь у нас есть больше статистики для каждого бота, благодаря AKroell ! Три новых столбца содержат максимальный балл по всем играм, средний балл за игру и средний балл при победе каждого бота.
Как отмечено в комментариях, была проблема с игровой логикой, из-за которой боты с более высоким индексом в игре получали дополнительный раунд в некоторых случаях. Это было исправлено сейчас, и оценки ниже отражают это.
Simulation or 300000000 games between 49 bots completed in 35628.7 seconds
Each game lasted for an average of 3.73 rounds
29127662 games were tied between two or more bots
0 games ran until the round limit, highest round was 22
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|Bot |Win%| Wins| Played| Max| Avg|Avg win|Throws|Success%|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|OptFor2X |21.6|10583693|48967616| 99| 20.49| 44.37| 4.02| 33.09|
|Rebel |20.7|10151261|48977862| 104| 21.36| 44.25| 3.90| 35.05|
|Hesitate |20.3| 9940220|48970815| 105| 21.42| 44.23| 3.89| 35.11|
|EnsureLead |20.3| 9929074|48992362| 101| 20.43| 44.16| 4.50| 25.05|
|StepBot |20.2| 9901186|48978938| 96| 20.42| 43.47| 4.56| 24.06|
|BinaryBot |20.1| 9840684|48981088| 115| 21.01| 44.48| 3.85| 35.92|
|Roll6Timesv2 |20.1| 9831713|48982301| 101| 20.83| 43.53| 4.37| 27.15|
|AggressiveStalker |19.9| 9767637|48979790| 110| 20.46| 44.86| 3.90| 35.04|
|FooBot |19.9| 9740900|48980477| 100| 22.03| 43.79| 3.91| 34.79|
|QuotaBot |19.9| 9726944|48980023| 101| 19.96| 44.95| 4.50| 25.03|
|BePrepared |19.8| 9715461|48978569| 112| 18.68| 47.58| 4.30| 28.31|
|AdaptiveRoller |19.7| 9659023|48982819| 107| 20.70| 43.27| 4.51| 24.81|
|GoTo20Bot |19.6| 9597515|48973425| 108| 21.15| 43.24| 4.44| 25.98|
|Gladiolen |19.5| 9550368|48970506| 107| 20.16| 45.31| 3.91| 34.81|
|LastRound |19.4| 9509645|48988860| 100| 20.45| 43.50| 4.20| 29.98|
|BrainBot |19.4| 9500957|48985984| 105| 19.26| 45.56| 4.46| 25.71|
|GoTo20orBestBot |19.4| 9487725|48975944| 104| 20.98| 44.09| 4.46| 25.73|
|Stalker |19.4| 9485631|48969437| 103| 20.20| 45.34| 3.80| 36.62|
|ClunkyChicken |19.1| 9354294|48972986| 112| 21.14| 45.44| 3.57| 40.48|
|FortyTeen |18.8| 9185135|48980498| 107| 20.90| 46.77| 3.88| 35.32|
|Crush |18.6| 9115418|48985778| 96| 14.82| 43.08| 5.15| 14.15|
|Chaser |18.6| 9109636|48986188| 107| 19.52| 45.62| 4.06| 32.39|
|MatchLeaderBot |16.6| 8122985|48979024| 104| 18.61| 45.00| 3.20| 46.70|
|Ro |16.5| 8063156|48972140| 108| 13.74| 48.24| 5.07| 15.44|
|TakeFive |16.1| 7906552|48994992| 100| 19.38| 44.68| 3.36| 43.96|
|RollForLuckBot |16.1| 7901601|48983545| 109| 17.30| 50.54| 4.72| 21.30|
|Alpha |15.5| 7584770|48985795| 104| 17.45| 46.64| 4.04| 32.67|
|GoHomeBot |15.1| 7418649|48974928| 44| 13.23| 41.41| 5.49| 8.52|
|LeadBy5Bot |15.0| 7354458|48987017| 110| 17.15| 46.95| 4.13| 31.16|
|NotTooFarBehindBot |15.0| 7338828|48965720| 115| 17.75| 45.03| 2.99| 50.23|
|GoToSeventeenRollTenBot|14.1| 6900832|48976440| 104| 10.26| 49.25| 5.68| 5.42|
|LizduadacBot |14.0| 6833125|48978161| 96| 9.67| 51.35| 5.72| 4.68|
|TleilaxuBot |13.5| 6603853|48985292| 137| 15.25| 45.05| 4.27| 28.80|
|BringMyOwn_dice |12.0| 5870328|48974969| 44| 21.27| 41.47| 4.24| 29.30|
|SafetyNet |11.4| 5600688|48987015| 98| 15.81| 45.03| 2.41| 59.84|
|WhereFourArtThouChicken|10.5| 5157324|48976428| 64| 22.38| 47.39| 3.59| 40.19|
|ExpectationsBot | 9.0| 4416154|48976485| 44| 24.40| 41.55| 3.58| 40.41|
|OneStepAheadBot | 8.4| 4132031|48975605| 50| 18.24| 46.02| 3.20| 46.59|
|GoBigEarly | 6.6| 3218181|48991348| 49| 20.77| 42.95| 3.90| 35.05|
|OneInFiveBot | 5.8| 2826326|48974364| 155| 17.26| 49.72| 3.00| 50.00|
|ThrowThriceBot | 4.1| 1994569|48984367| 54| 21.70| 44.55| 2.53| 57.88|
|FutureBot | 4.0| 1978660|48985814| 50| 17.93| 45.17| 2.36| 60.70|
|GamblersFallacy | 1.3| 621945|48986528| 44| 22.52| 41.46| 2.82| 53.07|
|FlipCoinRollDice | 0.7| 345385|48972339| 87| 15.29| 44.55| 1.61| 73.17|
|BlessRNG | 0.2| 73506|48974185| 49| 14.54| 42.72| 1.42| 76.39|
|StopBot | 0.0| 1353|48984828| 44| 10.92| 41.57| 1.00| 83.33|
|CooperativeSwarmBot | 0.0| 991|48970284| 44| 10.13| 41.51| 1.36| 77.30|
|PointsAreForNerdsBot | 0.0| 0|48986508| 0| 0.00| 0.00| 6.00| 0.00|
|SlowStart | 0.0| 0|48973613| 35| 5.22| 0.00| 3.16| 47.39|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
Следующие боты (кроме Rebel
) сделаны, чтобы изменить правила, и создатели согласились не принимать участие в официальном турнире. Тем не менее, я все еще думаю, что их идеи являются творческими, и они заслуживают достойного упоминания. Мятежник также в этом списке, потому что он использует умную стратегию, чтобы избежать саботажа, и на самом деле лучше работает с саботирующим ботом в игре.
Боты NeoBot
и KwisatzHaderach
правда следуют правилам, но используют лазейки, предсказывая случайный генератор. Поскольку эти боты требуют много ресурсов для симуляции, я добавил их статистику из симуляции с меньшим количеством игр. Бот HarkonnenBot
добивается победы, отключая всех других ботов, что строго противоречит правилам.
Simulation or 300000 games between 52 bots completed in 66.2 seconds
Each game lasted for an average of 4.82 rounds
20709 games were tied between two or more bots
0 games ran until the round limit, highest round was 31
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|Bot |Win%| Wins| Played| Max| Avg|Avg win|Throws|Success%|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
|KwisatzHaderach |80.4| 36986| 46015| 214| 58.19| 64.89| 11.90| 42.09|
|HarkonnenBot |76.0| 35152| 46264| 44| 34.04| 41.34| 1.00| 83.20|
|NeoBot |39.0| 17980| 46143| 214| 37.82| 59.55| 5.44| 50.21|
|Rebel |26.8| 12410| 46306| 92| 20.82| 43.39| 3.80| 35.84|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 45|
| 75.00| 50|
| 90.00| 59|
| 95.00| 70|
| 99.00| 97|
| 99.90| 138|
| 99.99| 214|
+----------+-----+