Turniej się skończył!
Turniej się zakończył! Ostateczna symulacja została przeprowadzona w nocy, łącznie gier. Zwycięzcą został Christian Sievers ze swoim botem OptFor2X . Christian Sievers zdołał także zabezpieczyć drugie miejsce Rebelią . Gratulacje! Poniżej znajduje się oficjalna lista najlepszych wyników turnieju.
Jeśli nadal chcesz zagrać w tę grę, z przyjemnością skorzystaj z kontrolera zamieszczonego poniżej i użyj kodu w nim, aby stworzyć własną grę.
Zostałem zaproszony do gry w kości, o której nigdy nie słyszałem. Zasady były proste, ale myślę, że byłoby idealne na wyzwanie KotH.
Zasady
Początek gry
Kostka porusza się wokół stołu i za każdym razem, gdy jest twoja kolej, możesz rzucić kostką tyle razy, ile chcesz. Musisz jednak rzucić to przynajmniej raz. Śledzisz sumę wszystkich rzutów w swojej rundzie. Jeśli zdecydujesz się zatrzymać, wynik za rundę zostanie dodany do całkowitego wyniku.
Dlaczego więc miałbyś przestać rzucać kostką? Ponieważ jeśli zdobędziesz 6, twój wynik za całą rundę wyniesie zero, a kość zostanie przekazana. Zatem początkowym celem jest jak najszybsze zwiększenie wyniku.
Kto jest zwycięzcą?
Gdy pierwszy gracz przy stole osiągnie 40 lub więcej punktów, rozpoczyna się ostatnia runda. Po rozpoczęciu ostatniej rundy wszyscy oprócz osoby, która zainicjowała ostatnią rundę, dostaje jeszcze jedną turę.
Zasady dla ostatniej rundy są takie same jak dla każdej innej rundy. Wybierasz rzucanie lub przestaniesz. Wiesz jednak, że nie masz szans na wygraną, jeśli nie uzyskasz wyższego wyniku niż przed ostatnią rundą. Ale jeśli posuniesz się za daleko, możesz otrzymać 6.
Należy jednak wziąć pod uwagę jeszcze jedną zasadę. Jeśli bieżący całkowity wynik (poprzedni wynik + aktualny wynik w rundzie) wynosi 40 lub więcej i trafisz 6, łączny wynik jest ustawiony na 0. Oznacza to, że musisz zacząć wszystko od nowa. Jeśli trafisz 6, gdy obecny całkowity wynik wynosi 40 lub więcej, gra będzie kontynuowana normalnie, z tym wyjątkiem, że jesteś teraz na ostatnim miejscu. Ostatnia runda nie jest uruchamiana po zresetowaniu całkowitego wyniku. Nadal możesz wygrać rundę, ale staje się ona trudniejsza.
Zwycięzcą zostaje gracz z najwyższym wynikiem po zakończeniu ostatniej rundy. Jeśli dwóch lub więcej graczy podzieli ten sam wynik, wszyscy będą liczeni jako zwycięzcy.
Dodatkową zasadą jest to, że gra trwa maksymalnie 200 rund. Ma to na celu zapobieganie przypadkom, w których wiele botów w zasadzie rzuca, dopóki nie osiągnie 6, aby utrzymać swój obecny wynik. Po przejściu 199. rundy zostaje last_round
ustawiona na wartość true i rozpoczyna się jeszcze jedna runda. Jeśli gra przejdzie do 200 rund, zwycięzcą jest bot (lub boty) z najwyższym wynikiem, nawet jeśli nie mają 40 punktów lub więcej.
Podsumować
- W każdej rundzie rzucasz kością, dopóki nie zatrzymasz się lub nie otrzymasz 6
- Musisz rzucić kostką raz (jeśli twój pierwszy rzut to 6, twoja runda natychmiast się kończy)
- Jeśli otrzymasz 6, twój aktualny wynik jest ustawiony na 0 (nie twój całkowity wynik)
- Po każdej rundzie dodajesz swój aktualny wynik do całkowitego wyniku
- Kiedy bot kończy swoją turę, co daje łączny wynik co najmniej 40, wszyscy inni otrzymują ostatnią turę
- Jeśli twój obecny całkowity wynik to a otrzymasz 6, twój całkowity wynik jest ustawiony na 0 i Twoja runda się kończy
- Ostatnia runda nie jest uruchamiana, gdy wystąpi powyższe
- Zwycięzcą zostaje osoba z najwyższym łącznym wynikiem po ostatniej rundzie
- W przypadku wielu zwycięzców, wszyscy będą liczeni jako zwycięzcy
- Gra trwa maksymalnie 200 rund
Wyjaśnienie wyników
- Łączny wynik: wynik zapisany z poprzednich rund
- Aktualny wynik: wynik dla bieżącej rundy
- Aktualny łączny wynik: suma dwóch powyższych wyników
Jak uczestniczysz
Aby wziąć udział w tym wyzwaniu KotH, powinieneś napisać klasę Python, która dziedziczy po Bot
. Należy wdrożyć funkcję: make_throw(self, scores, last_round)
. Ta funkcja zostanie wywołana, gdy nadejdzie Twoja kolej, a twój pierwszy rzut nie był 6. Aby kontynuować rzucanie, powinieneś yield True
. Aby przestać rzucać, powinieneś yield False
. Po każdym rzucie update_state
wywoływana jest funkcja rodzica . Dzięki temu masz dostęp do swoich rzutów w bieżącej rundzie za pomocą zmiennej self.current_throws
. Masz również dostęp do własnego indeksu za pomocą self.index
. Zatem, aby zobaczyć swój własny wynik, którego byś użył scores[self.index]
. Możesz również uzyskać dostęp end_score
do gry za pomocą self.end_score
, ale możesz bezpiecznie założyć, że będzie to 40 za to wyzwanie.
Możesz tworzyć funkcje pomocnicze w swojej klasie. Możesz również zastąpić funkcje istniejące w Bot
klasie nadrzędnej, np. Jeśli chcesz dodać więcej właściwości klasy. Nie możesz modyfikować stanu gry w jakikolwiek sposób, z wyjątkiem ustępowania True
lub False
.
Możesz szukać inspiracji w tym poście i skopiować dowolny z dwóch botów, które tu zawarłem. Obawiam się jednak, że nie są szczególnie skuteczne ...
Zezwalanie na inne języki
Zarówno w piaskownicy, jak i w The Nineteenth Byte, rozmawialiśmy o dopuszczaniu zgłoszeń w innych językach. Po przeczytaniu o takich implementacjach i wysłuchaniu argumentów z obu stron postanowiłem ograniczyć to wyzwanie tylko do Pythona. Wynika to z dwóch czynników: czasu wymaganego do obsługi wielu języków oraz losowości tego wyzwania wymagającego dużej liczby iteracji w celu osiągnięcia stabilności. Mam nadzieję, że nadal będziesz brać udział, a jeśli chcesz nauczyć się Pythona do tego wyzwania, postaram się być dostępny na czacie tak często, jak to możliwe.
W przypadku jakichkolwiek pytań możesz napisać w pokoju czatu dotyczącym tego wyzwania . Do zobaczenia tam!
Zasady
- Sabotaż jest dozwolony i zachęcany. To znaczy sabotaż przeciwko innym graczom
- Wszelkie próby majsterkowania przy kontrolerze, czasie wykonywania lub innych zgłoszeniach zostaną zdyskwalifikowane. Wszystkie zgłoszenia powinny działać tylko z danymi wejściowymi i pamięcią.
- Każdy bot, który wykorzystuje więcej niż 500 MB pamięci do podjęcia decyzji, zostanie zdyskwalifikowany (jeśli potrzebujesz takiej ilości pamięci, powinieneś przemyśleć swoje wybory)
- Bot nie może wdrożyć dokładnie tej samej strategii, co istniejąca, celowo lub przypadkowo.
- W trakcie wyzwania możesz aktualizować swojego bota. Możesz jednak również opublikować innego bota, jeśli Twoje podejście jest inne.
Przykład
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Ten bot będzie działał, dopóki nie uzyska wyniku co najmniej 10 w rundzie lub nie wyrzuci 6. Zauważ, że nie potrzebujesz żadnej logiki, aby poradzić sobie z rzucaniem 6. Pamiętaj również, że jeśli twój pierwszy rzut to 6, make_throw
to nigdy nie sprawdzany, ponieważ Twoja runda natychmiast się kończy.
Dla tych, którzy są nowi w Pythonie (i nowi w yield
koncepcji), ale chcą spróbować, yield
słowo kluczowe jest pod pewnymi względami podobne do zwrotu, ale różni się pod innymi względami. O tej koncepcji możesz przeczytać tutaj . Zasadniczo, gdy ty yield
, twoja funkcja zatrzyma się, a edytowana wartość yield
zostanie odesłana z powrotem do kontrolera. Tam kontroler obsługuje logikę, aż nadejdzie czas, aby bot podjął kolejną decyzję. Następnie kontroler wyśle ci rzut kostką, a twoja make_throw
funkcja będzie kontynuowała wykonywanie dokładnie tam, gdzie została zatrzymana wcześniej, zasadniczo na linii po poprzedniej yield
instrukcji.
W ten sposób kontroler gier może aktualizować stan bez konieczności oddzielnego wywołania funkcji bota dla każdego rzutu kostką.
Specyfikacja
Możesz użyć dowolnej biblioteki Python dostępnej w pip
. Aby upewnić się, że będę w stanie uzyskać dobrą średnią, masz limit 100 milisekund na rundę. Byłbym bardzo szczęśliwy, gdyby twój skrypt był o wiele szybszy, abym mógł uruchomić więcej rund.
Ocena
Aby znaleźć zwycięzcę, wezmę wszystkie boty i uruchomię je w losowych grupach po 8 osób. Jeśli zgłoszono mniej niż 8 klas, poprowadzę je w losowych grupach po 4, aby uniknąć zawsze posiadania wszystkich botów w każdej rundzie. Będę przeprowadzał symulacje przez około 8 godzin, a zwycięzcą zostanie bot z najwyższym procentem wygranych. Rozpocznę końcowe symulacje na początku 2019 roku, dając wam wszystkie Święta Bożego Narodzenia do kodowania botów! Wstępna data końcowa to 4 stycznia, ale jeśli to za mało czasu, mogę zmienić ją na późniejszą.
Do tego czasu spróbuję wykonać codzienną symulację z wykorzystaniem 30–60 minut czasu procesora i zaktualizować tablicę wyników. To nie będzie oficjalny wynik, ale posłuży jako przewodnik do sprawdzenia, które boty działają najlepiej. Jednak zbliża się Boże Narodzenie, mam nadzieję, że zrozumiesz, że nie będę przez cały czas dostępny. Zrobię co w mojej mocy, aby przeprowadzić symulacje i odpowiedzieć na wszelkie pytania związane z wyzwaniem.
Sprawdź to sam
Jeśli chcesz uruchomić własne symulacje, oto pełny kod kontrolera uruchamiającego symulację, w tym dwa przykładowe boty.
Kontroler
Oto zaktualizowany kontroler tego wyzwania. Obsługuje wyjścia ANSI, wielowątkowość i zbiera dodatkowe statystyki dzięki AKroell ! Kiedy wprowadzę zmiany w kontrolerze, zaktualizuję post po zakończeniu dokumentacji.
Dzięki BMO kontroler może teraz pobierać wszystkie boty z tego postu za pomocą -d
flagi. Inne funkcje pozostają niezmienione w tej wersji. To powinno zapewnić, że wszystkie twoje najnowsze zmiany zostaną jak najszybciej zasymulowane!
#!/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)
Jeśli chcesz uzyskać dostęp do oryginalnego kontrolera dla tego wyzwania, jest on dostępny w historii edycji. Nowy kontroler ma tę samą logikę do uruchamiania gry, jedyną różnicą jest wydajność, zbieranie statystyk i ładniejsze drukowanie.
Boty
Na moim komputerze boty są przechowywane w pliku forty_game_bots.py
. Jeśli używasz innej nazwy pliku, musisz zaktualizować import
instrukcję na górze kontrolera.
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
Uruchamianie symulacji
Aby uruchomić symulację, zapisz oba fragmenty kodu opublikowane powyżej w dwóch osobnych plikach. Uratowałem je jako forty_game_controller.py
i forty_game_bots.py
. Następnie wystarczy użyć python forty_game_controller.py
lub w python3 forty_game_controller.py
zależności od konfiguracji Pythona. Postępuj zgodnie z instrukcjami, jeśli chcesz dalej konfigurować symulację, lub jeśli chcesz, majstruj przy kodzie.
Statystyki gry
Jeśli tworzysz bota, który dąży do określonego wyniku bez uwzględnienia innych botów, są to zwycięskie percentyle:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Wysokie wyniki
W miarę publikowania kolejnych odpowiedzi postaram się aktualizować tę listę. Zawartość listy zawsze będzie pochodzić z najnowszej symulacji. Boty ThrowTwiceBot
i GoToTenBot
są botami z powyższego kodu i są używane jako odniesienie. Przeprowadziłem symulację z 10 ^ 8 grami, co zajęło około 1 godziny. Potem zobaczyłem, że gra osiągnęła stabilność w porównaniu do moich biegów z 10 ^ 7 grami. Ponieważ jednak ludzie nadal publikują boty, nie będę wykonywać żadnych symulacji, dopóki częstotliwość odpowiedzi nie spadnie.
Próbuję dodać wszystkie nowe boty i wszelkie zmiany, które wprowadziłeś w istniejących botach. Jeśli wygląda na to, że przegapiłem twojego bota lub jakieś nowe zmiany, które masz, napisz na czacie, a upewnię się, że w następnej symulacji będę mieć twoją najnowszą wersję.
Mamy teraz więcej statystyk dla każdego bota dzięki AKroell ! Trzy nowe kolumny zawierają maksymalny wynik we wszystkich grach, średni wynik na grę i średni wynik przy wygrywaniu dla każdego bota.
Jak wskazano w komentarzach, wystąpił problem z logiką gry, w wyniku którego boty o wyższym indeksie w grze otrzymywały dodatkową rundę w niektórych przypadkach. Zostało to już naprawione i odzwierciedlają to poniższe wyniki.
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|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
Następujące boty (oprócz Rebel
) są tworzone w celu naginania zasad, a twórcy zgodzili się nie brać udziału w oficjalnym turnieju. Jednak nadal uważam, że ich pomysły są kreatywne i zasługują na wyróżnienie. Rebel jest również na tej liście, ponieważ używa sprytnej strategii, aby uniknąć sabotażu, i faktycznie działa lepiej z botem sabotażowym w grze.
Boty NeoBot
i KwisatzHaderach
postępują zgodnie z zasadami, ale wykorzystują lukę, przewidując losowy generator. Ponieważ boty te wymagają wielu zasobów do symulacji, dodałem statystyki z symulacji z mniejszą liczbą gier. Bot HarkonnenBot
osiąga zwycięstwo poprzez wyłączenie wszystkich innych botów, co jest niezgodne z zasadami.
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|
+----------+-----+
Odpowiedzi:
OptFor2X
Bot ten jest zgodny z optymalną strategią dla dwóch graczy w tej grze, wykorzystując jedynie swój wynik i wynik najlepszego przeciwnika. W ostatniej rundzie zaktualizowana wersja uwzględnia wszystkie wyniki.
źródło
NeoBot
Zamiast tego spróbuj tylko zrozumieć prawdę - nie ma łyżki
NeoBot zagląda do matrycy (inaczej losowo) i przewiduje, czy następny rzut będzie 6, czy nie - nie może nic poradzić na to, że otrzyma 6, ale z radością unika zakończenia serii.
NeoBot tak naprawdę nie modyfikuje kontrolera ani środowiska uruchomieniowego, po prostu uprzejmie prosi bibliotekę o więcej informacji.
źródło
Spółdzielnia rój
Strategia
Nie sądzę, aby ktokolwiek jeszcze zauważył znaczenie tej reguły:
Gdyby każdy bot zawsze toczył się, dopóki nie odpadł, wszyscy mieliby wynik zero na końcu 200 rundy i wszyscy by wygrywali! Tak więc strategia Spółdzielczego Roju polega na współpracy, dopóki wszyscy gracze mają wynik zero, ale normalnej grze, jeśli ktokolwiek zdobędzie jakiekolwiek punkty.
W tym poście przesyłam dwa boty: pierwszy to CooperativeSwarmBot, a drugi to CooperativeThrowTwice. CooperativeSwarmBot służy jako klasa bazowa dla wszystkich botów, które formalnie są częścią roju spółdzielczego, i ma zachowanie zastępcze polegające na zaakceptowaniu pierwszego udanego rzutu, gdy współpraca się nie powiedzie. CooperativeSwarmBot ma CooperativeSwarmBot jako swojego rodzica i jest z nim identyczny pod każdym względem, z wyjątkiem tego, że jego brak współpracy polega na wykonaniu dwóch rzutów zamiast jednego. W ciągu kilku najbliższych dni będę rewidować ten post, aby dodać nowe boty, które wykorzystują znacznie bardziej inteligentne zachowanie, grając przeciwko botom niewspółpracującym.
Kod
Analiza
Zdolność do życia
W tej grze bardzo trudno jest współpracować, ponieważ do jej działania potrzebujemy wsparcia wszystkich ośmiu graczy. Ponieważ każda klasa botów jest ograniczona do jednej instancji na grę, jest to trudny cel do osiągnięcia. Na przykład szanse na wybór ośmiu botów kooperacyjnych z puli 100 botów kooperacyjnych i 30 botów kooperacyjnych to:
Studium przypadku
Z wielu powodów (patrz przypisy 1 i 2) właściwy rój kooperacyjny nigdy nie będzie konkurował w oficjalnych grach. W związku z tym w tej sekcji podsumuję wyniki jednej z moich własnych symulacji.
W tej symulacji uruchomiono 10000 gier przy użyciu 38 innych botów, które zostały tu zamieszczone podczas ostatniego sprawdzania, oraz 2900 botów, które miały CooperativeSwarmBot jako klasę nadrzędną. Kontroler poinformował, że 9051 z 10000 gier (90,51%) zakończyło się w 200 rundach, co jest dość bliskie prognozie, że 90% gier będzie współpracować. Wdrożenie tych botów było banalne; inne niż CooperativeSwarmBot wszyscy przyjęli tę formę:
Mniej niż 3% botów miało procent wygranych poniżej 80%, a nieco ponad 11% botów wygrywało każdą grę, w którą grały. Mediana procent wygranych 2900 botów w roju wynosi około 86%, co jest oburzająco dobre. Dla porównania, najlepsze wyniki w bieżącej oficjalnej tabeli liderów wygrywają mniej niż 22% swoich gier. Nie mogę zmieścić pełnej listy roju spółdzielczego w maksymalnej dozwolonej długości dla odpowiedzi, więc jeśli chcesz zobaczyć, że musisz iść tutaj: https://pastebin.com/3Zc8m1Ex
Ponieważ każdy bot grał średnio w około 27 grach, szczęście odgrywa stosunkowo dużą rolę, gdy patrzysz na wyniki dla poszczególnych botów. Ponieważ nie wdrożyłem jeszcze zaawansowanej strategii dla gier niewspółpracujących, większość innych botów skorzystała drastycznie z gry przeciwko rojowi kooperatywnemu, osiągając nawet średni wskaźnik wygranych roju kooperacyjnego wynoszący 86%.
Pełne wyniki dla botów, których nie ma w roju, wymieniono poniżej; są dwa boty, których wyniki, moim zdaniem, zasługują na szczególną uwagę. Po pierwsze, StopBot nie wygrał żadnych gier. Jest to szczególnie tragiczne, ponieważ rój spółdzielczy faktycznie używał dokładnie tej samej strategii, co StopBot; spodziewałbyś się, że StopBot wygra osiem gier przez przypadek, a trochę więcej, ponieważ rój kooperatywny jest zmuszony dać swoim przeciwnikom pierwszy ruch. Drugim interesującym rezultatem jest jednak to, że ciężka praca PointsAreForNerdsBot ostatecznie się opłaciła: współpracowała z rojem i wygrała każdą grę!
Wady
Podejście oparte na współpracy ma kilka wad. Po pierwsze, grając przeciwko botom niewspółpracującym, boty kooperacyjne nigdy nie uzyskują przewagi w pierwszej turze, ponieważ kiedy grają jako pierwsze, nie wiedzą jeszcze, czy ich przeciwnicy są skłonni do współpracy, a zatem nie mają wyboru, jak tylko zdobyć wynik zero. Podobnie ta strategia współpracy jest wyjątkowo podatna na wykorzystywanie przez złośliwe boty; na przykład podczas gry kooperacyjnej bot, który gra ostatni w ostatniej rundzie, może natychmiast zatrzymać rzucanie, aby wszyscy inni przegrali (zakładając oczywiście, że ich pierwszy rzut nie był szóstką).
Współpracując, wszystkie boty mogą osiągnąć optymalne rozwiązanie o 100% wygranej. Jako taki, jeśli wskaźnik wygranych byłby jedyną rzeczą, która się liczyła, wówczas współpraca byłaby stabilną równowagą i nie miałaby się czym martwić. Jednak niektóre boty mogą nadawać priorytet innym celom, takim jak osiągnięcie szczytu tabeli liderów. Oznacza to, że istnieje ryzyko, że inny bot może uszkodzić się po twojej ostatniej turze, co stwarza dla ciebie zachętę do tego, by najpierw go wykonać. Ponieważ konfiguracja tych zawodów nie pozwala nam zobaczyć, co zrobili nasi przeciwnicy w swoich poprzednich grach, nie możemy karać osób, które uciekły. Współpraca zatem jest ostatecznie niestabilną równowagą skazaną na niepowodzenie.
Przypisy
[1]: Głównymi powodami, dla których nie chcę przesyłać tysięcy botów zamiast tylko dwóch, jest to, że spowolniłoby to symulację o współczynnik rzędu 1000 [2], a to spowodowałoby znaczny bałagan z wygrać procent, ponieważ inne boty prawie wyłącznie grają przeciwko rojowi, a nie sobie nawzajem. Ważniejsze jest jednak to, że nawet gdybym chciał, nie byłbym w stanie zrobić tylu botów w rozsądnych ramach czasowych bez złamania zasady, że „bot nie może wdrożyć dokładnie takiej samej strategii jak istniejący, umyślnie lub przypadkowo ”.
[2]: Myślę, że istnieją dwa główne powody spowolnienia symulacji podczas prowadzenia roju kooperacyjnego. Po pierwsze, więcej botów oznacza więcej gier, jeśli chcesz, aby każdy bot grał w tej samej liczbie gier (w studium przypadku liczba gier różni się około 77 razy). Po drugie, gry kooperacyjne trwają dłużej, ponieważ trwają przez pełne 200 rund, aw ciągu jednej rundy gracze muszą grać w nieskończoność. W mojej konfiguracji symulowanie gier trwało około 40 razy: studium przypadku zajęło nieco ponad trzy minuty, aby uruchomić 10000 gier, ale po usunięciu roju kooperacyjnego skończy 10000 gier w zaledwie 4,5 sekundy. Szacuję, że pomiędzy tymi dwoma przyczynami dokładne mierzenie wydajności botów zajęłoby około 3100 razy dłużej, gdy rój konkuruje w porównaniu do tego, kiedy nie ma.
źródło
GoTo20Bot
Po prostu spróbuj wszystkich
GoToNBot
, a 20, 22, 24 gra najlepiej. Nie wiem dlaczego.Aktualizacja: zawsze zatrzymaj rzut, jeśli uzyskasz wynik 40 lub więcej.
źródło
end_score
na 4000 (i zmieniłem twojego bota, aby używał tego wtarget
obliczeniach), 15-16 botów było znacznie lepszych. Ale jeśli w grze chodziło tylko o zwiększenie twojego wyniku, byłoby to banalne.end_score
jest 4000, prawie niemożliwe jest zdobycie 4000 przed 200 turami . A gra jest po prostu tym, który uzyskał najwyższy wynik w 200 turach. I przystanek na 15 powinien działać, ponieważ tym razem strategia najwyższego wyniku w jednej turze jest taka sama jak najwyższy wynik w 200 turach.Wałek adaptacyjny
Zaczyna się bardziej agresywnie i uspokaja pod koniec rundy.
Jeśli wierzy, że wygrywa, rzuć dodatkowy czas na bezpieczeństwo.
źródło
lim = max(min(self.end_score - scores[self.index], 24), 6)
podniesienie maksimum do 24 i dodanie minimum 6 zarówno zwiększają procent zwycięstwa samodzielnie, a nawet bardziej łącznie.Alfa
Alfa nigdy nie ustępuje nikomu. Tak długo, jak istnieje bot z wyższym wynikiem, będzie się toczył.
źródło
yield
działa, jeśli zacznie się toczyć, nigdy się nie zatrzyma. Będziesz chciał zaktualizowaćmy_score
w pętli.NotTooFarBehindBot
Chodzi o to, że inne boty mogą tracić punkty, więc bycie na drugim miejscu nie jest złe - ale jeśli jesteś bardzo w tyle, równie dobrze możesz popsuć się.
źródło
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
. Nawet jeśli twój bot jest na prowadzeniu po 7 rzutach, trwa do momentu, aż osiągnie 6. Kiedy piszę to, odkryłem problem!scores
Zawierają jedynie łączne wyniki, a nie przypadki umrzeć bieżącej rundzie. Powinieneś go zmodyfikowaćcurrent_score = scores[self.index] + sum(self.current_throws)
.GoHomeBot
Chcemy iść na całość lub do domu, prawda? GoHomeBot przeważnie po prostu wraca do domu. (Ale robi zaskakująco dobrze!)
źródło
scores
liście. Był już taki bot (GoToEnd), ale David usunął odpowiedź. Zastąpię tego bota twoim.Zapewnić
Upewnij się, że pożycza pomysły od GoTo20Bot. Dodaje koncepcję, którą zawsze bierze pod uwagę (gdy jest w Last_round lub osiąga 40), że istnieją inne, które będą miały co najmniej jeden rzut. Tak więc bot próbuje wyprzedzić ich trochę, tak aby musieli nadrobić zaległości.
źródło
Roll6TimesV2
Nie bije obecnie najlepszych, ale myślę, że będzie lepiej z większą liczbą botów w grze.
Nawiasem mówiąc, naprawdę świetna gra.
źródło
StopBot
Dosłownie tylko jeden rzut.
Jest to równoważne z
Bot
klasą podstawową .źródło
BringMyOwn_dice (BMO_d)
Ten bot uwielbia kości, przynosi 2 (wydaje się, że wykonuje najlepsze) własne kości. Przed rzuceniem kostkami w rundzie rzuca swoimi 2 kostkami i oblicza ich sumę, jest to liczba rzutów, które zamierza wykonać, rzuca tylko, jeśli nie ma jeszcze 40 punktów.
źródło
FooBot
źródło
# Must throw at least once
jest niepotrzebny - rzuca raz przed wywołaniem bota. Twój bot zawsze rzuci minimum dwa razy.make_throw
metodę wcześnie, kiedy chciałem, aby gracze mogli pominąć swoją turę. Wydaje mi się, że bardziej odpowiednie byłobykeep_throwing
. Dzięki za opinie w piaskownicy, naprawdę pomogło uczynić z tego właściwe wyzwanie!Idź na początku
Pomysł: staraj się wygrywać duże na wczesnym rzucie (do 25), a następnie skradaj się stamtąd 2 rzuty na raz.
źródło
BinaryBot
Stara się zbliżyć do wyniku końcowego, aby jak tylko ktoś uruchomił ostatnią rundę, mógł pokonać swój wynik za zwycięstwo. Cel zawsze znajduje się w połowie drogi między aktualnym wynikiem a wynikiem końcowym.
źródło
Hesitate
również odmawia przekroczenia linii jako pierwszej. Musisz otoczyć swoją funkcjęclass
materiałami.PointsAreForNerdsBot
To nie wymaga wyjaśnienia.
OneInFiveBot
Toczy się dalej, dopóki nie rzuci piątki na swojej 5-stronnej kości. Pięć to mniej niż sześć, więc MUSI WYGRAĆ!
źródło
OneInFiveBot
fajny pomysł, ale myślę, że cierpi w końcowej fazie gry w porównaniu do niektórych bardziej zaawansowanych botów. Nadal świetne zgłoszenie!OneInFiveBot
jest dość ciekawy sposób, który konsekwentnie ma najwyższy ogólny wynik osiągnięty.StopBot
worka treningowego: P. OneInFiveBot jest naprawdę fajną, fajną robotą!OneInFiveBot
i robi to znacznie lepiej niż się spodziewałemLizduadacBot
Próbuje wygrać w 1 kroku. Warunek końcowy jest nieco arogancki.
To także mój pierwszy post (i jestem nowy w Pythonie), więc jeśli pobiję „PointsAreForNerdsBot”, będę szczęśliwy!
źródło
PointsAreForNerdsBot
, ale twój bot radzi sobie całkiem dobrze. Zaktualizuję wynik albo dziś wieczorem, albo jutro, ale twoja wygrana wynosi około 15%, czyli więcej niż średnio 12,5%.SlowStart
Ten bot implementuje algorytm TCP Slow Start. Dostosowuje liczbę rzutów ( ani ) zgodnie ze swoją poprzednią turą: jeśli nie rzucił 6 w poprzedniej turze, zwiększa wartość nor dla tej tury; podczas gdy zmniejsza, a nawet jeśli tak.
źródło
def updateValues():
powinny byćdef updateValues(self):
(lubdef update_values(self):
jeśli chcesz śledzić PEP8). Po drugie, wywołanieupdateValues()
powinno zamiast tego byćself.updateValues()
(lubself.update_vales()
).i
zmienną w pętli while. W tej chwili twój bot całkowicie przechodzi przez pętlę while lub utknął w pętli while, dopóki nie osiągnie 6.self.nor
i zobaczyć, jak wpływa ona na wydajność twojego bota.KwisatzHaderach
We wczesnych dniach tego wyzwania (tj. Przed
NeoBot
opublikowaniem) napisałem prawie trywialnegoOracle
bota:ale nie opublikowałem tego, ponieważ nie sądziłem, że to wystarczająco interesujące;) Ale kiedy
NeoBot
przeszedłem na prowadzenie, zacząłem myśleć o tym, jak pokonać swoją doskonałą zdolność przewidywania przyszłości. Oto cytat z Dune; właśnie wtedy Paul Atreides, Kwisatz Haderach, stoi w miejscu, z którego może rozwinąć się nieskończona liczba różnych przyszłości:Oto odpowiedź: przewidzieć przyszłość to ją zmienić; a jeśli jesteś bardzo ostrożny, to poprzez selektywne działanie lub bezczynność możesz to zmienić w korzystny sposób - przynajmniej przez większość czasu. Nawet ci, którzy
KwisatzHaderach
nie mogą uzyskać 100% wygranej!źródło
NeoBot
ale i lepsze! Podoba mi się również, jak podajesz przykład tego, co powinno zrobić wszystko, używając losowości (szczególnie kontrolera): użyj własnejrandom.Random
instancji. JakNeoBot
wydaje się to nieco wrażliwy na zmiany nieokreślonych szczegółów implementacji regulatora.HarkonnenBot
nie dotyka RNG; nie przejmuje się w ogóle liczbami losowymi. Po prostu zatruwa wszystkie inne boty, a następnie podchodzi do linii mety tak wolno, jak to możliwe. Podobnie jak wiele kulinarnych przysmaków, zemsta jest potrawą najlepiej smakującą powoli, po długim i delikatnym przygotowaniu.NeoBot
(iHarkonnenBot
),KwisatzHaderach
opiera się tylko na jednym szczególe wdrożenia; w szczególności nie musi wiedzieć, w jaki sposób jest implementowany random.random (), tylko to, że kontroler go używa; DKwisatzHaderach
iHarkonnenBot
tak samo jakNeoBot
. Otrzymają swoje wyniki z symulacji z mniejszą liczbą gier i nie będą w oficjalnej symulacji. Jednak podobnie jak oni znajdą się na liście najlepszych wynikówNeoBot
. Głównym powodem, dla którego nie biorą udziału w oficjalnej symulacji, jest to, że zepsują inne strategie botów. Jednak.WisdomOfCrowds
powinien być odpowiedni do uczestnictwa, a ja jestem ciekawy nowych zmian, które wprowadziłeś!Cóż, to jest oczywiste
źródło
LastRound działa tak, jakby zawsze była to ostatnia runda i ostatni bot: toczy się dalej, aż znajdzie się na czele. Nie chce też zadowolić się mniej niż 15 punktami, chyba że faktycznie jest to ostatnia runda lub nie osiągnie 40 punktów.
źródło
QuotaBot
Naiwny system „kwot”, który wdrożyłem, który w rzeczywistości wydawał się ogólnie dość wysoki.
źródło
if own_score mean + 5:
daje mi błąd. Takżewhile sum(self.current_throws)
<
i>
symbole, które kolidowały z<pre>
tagami, którychOczekiwania Bot
Po prostu gra prosto, oblicza oczekiwaną wartość rzutu kostką i robi to tylko wtedy, gdy jest dodatnia.
Miałem problem z uruchomieniem kontrolera, dostałem „NameError: nazwa„ bots_per_game ”nie jest zdefiniowana” na wielowątkowym, więc naprawdę nie mam pojęcia, jak to działa.
źródło
BlessRNG
BlessRNG FrankerZ GabeN BlessRNG
źródło
Czterdzieści jeden
Spróbuj zdobyć 14 punktów do ostatniej rundy, a następnie załóż, że wszyscy będą próbować zdobyć 14 punktów i spróbować związać ten wynik.
źródło
TypeError: unsupported operand type(s) for -: 'list' and 'int'
twojego bota.max_projected_score
powinieneś być maksimum listy, a nie całej listy, czy mam rację? W przeciwnym razie dostaję ten sam problem co tsh.Wahać się
Wykonuje dwa skromne kroki, a następnie czeka, aż ktoś przekroczy linię. Zaktualizowana wersja nie próbuje już pobić rekordu, chce go tylko osiągnąć - poprawiając wydajność, usuwając dwa bajty kodu źródłowego!
źródło
Buntownik
Ten bot łączy prostą strategię
Hesitate
z zaawansowaną strategią ostatniej rundyBotFor2X
, próbuje zapamiętać, kim jest i szaleje, gdy stwierdzi, że żyje w iluzji.źródło
HarkonnenBot
abyRebel
nie można było już samemu uwolnić się od trucizny;) i poprawiłem,TleilaxuBot
abyRebel
nie wykrywał już więcej!Weź pięć
W połowie czasu wyrzucimy 5 przed 6. Kiedy to zrobimy, wypłacimy pieniądze.
źródło
Pościgowy
Chaser próbuje dogonić pozycję pierwszą. Jeśli to ostatnia runda, desperacko stara się zdobyć co najmniej 50 punktów. Na wszelki wypadek rzuca co najmniej cztery razy, bez względu na wszystko
[edytuj 1: dodano strategię zdobywania złota w ostatniej rundzie]
[edycja 2: zaktualizowana logika, ponieważ błędnie myślałem, że bot uzyska wynik 40, a nie tylko najwyższy wynik]
[edytuj 3: Sprawił, że ścigający był trochę bardziej defensywny w końcowej grze]
źródło
FutureBot
OneStepAheadBot
Para botów przynosi własne zestawy kości i rzuca nimi, aby przewidzieć przyszłość. Jeśli jeden ma 6, zatrzymują się, FutureBot nie pamięta, która z dwóch kości była na następny rzut, więc się poddaje.
Zastanawiam się, co da radę.
Jak na mój gust OneStepAhead jest trochę zbyt podobny do OneInFive, ale chcę też zobaczyć, jak się ma do FutureBot i OneInFive.
Edycja: Teraz przestają działać po trafieniu 45
źródło