Turnamen berakhir!
Turnamen sudah berakhir! Simulasi terakhir dijalankan pada malam hari, total game. Pemenangnya adalah Christian Sievers dengan botnya OptFor2X . Christian Sievers juga berhasil mengamankan tempat kedua dengan Rebel . Selamat! Di bawah ini Anda dapat melihat daftar skor tinggi resmi untuk turnamen.
Jika Anda masih ingin bermain gim, Anda dapat menggunakan pengontrol yang diposting di bawah ini, dan menggunakan kode di dalamnya untuk membuat gim Anda sendiri.
Saya diundang untuk memainkan permainan dadu yang belum pernah saya dengar. Aturannya sederhana, namun saya pikir itu akan sempurna untuk tantangan KotH.
Aturan
Awal permainan
Mati berputar di sekitar meja, dan setiap kali giliran Anda, Anda bisa melempar mati sebanyak yang Anda inginkan. Namun, Anda harus membuangnya setidaknya satu kali. Anda melacak jumlah semua lemparan untuk putaran Anda. Jika Anda memilih untuk berhenti, skor untuk ronde tersebut ditambahkan ke total skor Anda.
Jadi mengapa Anda berhenti membuang dadu? Karena jika Anda mendapatkan 6, skor Anda untuk seluruh putaran menjadi nol, dan mati diteruskan. Dengan demikian, tujuan awal adalah meningkatkan skor Anda secepat mungkin.
Siapa pemenangnya?
Ketika pemain pertama di sekitar meja mencapai 40 poin atau lebih, putaran terakhir dimulai. Setelah putaran terakhir dimulai, semua orang kecuali orang yang memulai putaran terakhir mendapat satu putaran lagi.
Aturan untuk babak terakhir sama dengan untuk putaran lainnya. Anda memilih untuk terus melempar atau berhenti. Namun, Anda tahu bahwa Anda tidak memiliki peluang untuk menang jika Anda tidak mendapatkan skor lebih tinggi daripada skor sebelum Anda di babak terakhir. Tetapi jika Anda terus melangkah terlalu jauh, maka Anda mungkin mendapat nilai 6.
Namun, ada satu aturan lagi yang perlu dipertimbangkan. Jika skor total Anda saat ini (skor Anda sebelumnya + skor Anda saat ini untuk putaran) adalah 40 atau lebih, dan Anda menekan 6, skor total Anda ditetapkan ke 0. Itu berarti Anda harus memulai dari awal. Jika Anda menekan angka 6 saat skor total Anda saat ini adalah 40 atau lebih, permainan berlanjut seperti biasa, kecuali bahwa Anda sekarang berada di tempat terakhir. Babak terakhir tidak terpicu ketika skor total Anda diatur ulang. Anda masih bisa memenangkan ronde, tetapi itu menjadi lebih menantang.
Pemenangnya adalah pemain dengan skor tertinggi setelah babak terakhir berakhir. Jika dua atau lebih pemain berbagi skor yang sama, mereka semua akan dihitung sebagai pemenang.
Aturan tambahan adalah bahwa permainan berlanjut hingga maksimum 200 putaran. Ini untuk mencegah kasus di mana banyak bot pada dasarnya terus melempar sampai mereka mencapai 6 untuk tetap pada skor mereka saat ini. Setelah putaran ke-199 berlalu, last_round
disetel ke true, dan satu putaran lagi dimainkan. Jika permainan berlangsung hingga 200 putaran, bot (atau bot) dengan skor tertinggi adalah pemenangnya, bahkan jika mereka tidak memiliki 40 poin atau lebih.
Rekap
- Setiap putaran Anda terus melempar dadu sampai Anda memilih untuk berhenti atau Anda mendapatkan 6
- Anda harus melempar dadu sekali (jika lemparan pertama Anda adalah 6, putaran Anda segera berakhir)
- Jika Anda mendapatkan 6, skor Anda saat ini ditetapkan ke 0 (bukan skor total Anda)
- Anda menambahkan skor Anda saat ini ke skor total Anda setelah setiap putaran
- Ketika bot mengakhiri giliran mereka yang menghasilkan skor total setidaknya 40, semua orang mendapat giliran terakhir
- Jika skor total Anda saat ini adalah dan Anda mendapatkan 6, skor total Anda ditetapkan ke 0 dan putaran Anda berakhir
- Babak terakhir tidak terpicu ketika hal di atas terjadi
- Orang dengan skor total tertinggi setelah babak terakhir adalah pemenang
- Jika ada beberapa pemenang, semua akan dihitung sebagai pemenang
- Permainan ini berlangsung selama maksimal 200 putaran
Klarifikasi skor
- Skor total: skor yang telah Anda simpan dari babak sebelumnya
- Skor saat ini: skor untuk putaran saat ini
- Skor total saat ini: jumlah dari dua skor di atas
Bagaimana Anda berpartisipasi?
Untuk berpartisipasi dalam tantangan KotH ini, Anda harus menulis kelas Python yang diturunkan dari Bot
. Anda harus menerapkan fungsi: make_throw(self, scores, last_round)
. Fungsi itu akan dipanggil begitu giliran Anda, dan lemparan pertama Anda bukan 6. Untuk terus melempar, Anda harus yield True
. Untuk berhenti melempar, Anda harus yield False
. Setelah setiap lemparan, fungsi induk update_state
disebut. Dengan demikian, Anda memiliki akses ke lemparan Anda untuk putaran saat ini menggunakan variabel self.current_throws
. Anda juga memiliki akses ke indeks Anda sendiri menggunakan self.index
. Dengan demikian, untuk melihat skor total Anda sendiri, Anda akan menggunakan scores[self.index]
. Anda juga dapat mengakses end_score
permainan untuk menggunakan self.end_score
, tetapi Anda dapat dengan aman menganggap bahwa itu akan menjadi 40 untuk tantangan ini.
Anda diizinkan membuat fungsi pembantu di dalam kelas Anda. Anda juga dapat mengesampingkan fungsi yang ada di Bot
kelas induk, misalnya jika Anda ingin menambahkan lebih banyak properti kelas. Anda tidak diizinkan untuk mengubah keadaan permainan dengan cara apa pun selain menghasilkan True
atau False
.
Anda bebas mencari inspirasi dari pos ini, dan menyalin salah satu dari dua bot yang saya sertakan di sini. Namun, saya khawatir mereka tidak terlalu efektif ...
Pada memungkinkan bahasa lain
Baik di kotak pasir dan pada The Nineteenth Byte, kami telah berdiskusi tentang mengizinkan pengiriman dalam bahasa lain. Setelah membaca tentang implementasi seperti itu, dan mendengarkan argumen dari kedua belah pihak, saya telah memutuskan untuk membatasi tantangan ini hanya untuk Python. Ini disebabkan oleh dua faktor: waktu yang diperlukan untuk mendukung berbagai bahasa, dan keacakan tantangan ini membutuhkan jumlah iterasi yang tinggi untuk mencapai stabilitas. Saya harap Anda akan tetap berpartisipasi, dan jika Anda ingin mempelajari beberapa Python untuk tantangan ini, saya akan mencoba untuk selalu tersedia dalam obrolan sesering mungkin.
Untuk setiap pertanyaan yang mungkin Anda miliki, Anda dapat menulis di ruang obrolan untuk tantangan ini . Sampai jumpa!
Aturan
- Sabotase diperbolehkan, dan dianjurkan. Artinya, sabotase terhadap pemain lain
- Setiap upaya untuk mengutak-atik controller, run-time atau pengiriman lainnya akan didiskualifikasi. Semua pengiriman hanya dapat bekerja dengan input dan penyimpanan yang diberikan.
- Bot apa pun yang menggunakan memori lebih dari 500MB untuk mengambil keputusan akan didiskualifikasi (jika Anda membutuhkan banyak memori, Anda harus memikirkan kembali pilihan Anda)
- Bot tidak boleh menerapkan strategi yang sama persis dengan yang sudah ada, sengaja atau tidak sengaja.
- Anda diizinkan untuk memperbarui bot Anda saat tantangan berlangsung. Namun, Anda juga bisa memposting bot lain jika pendekatan Anda berbeda.
Contoh
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Bot ini akan terus berjalan sampai memiliki skor setidaknya 10 untuk ronde, atau melempar 6. Perhatikan bahwa Anda tidak perlu logika untuk menangani melempar 6. Juga perhatikan bahwa jika lemparan pertama Anda adalah 6, make_throw
adalah tidak pernah menelepon, karena putaran Anda segera berakhir.
Bagi mereka yang baru mengenal Python (dan baru dalam yield
konsep), tetapi ingin mencoba, yield
kata kunci ini mirip dengan pengembalian dalam beberapa hal, tetapi berbeda dalam hal lain. Anda dapat membaca tentang konsepnya di sini . Pada dasarnya, begitu Anda yield
, fungsi Anda akan berhenti, dan nilai yang Anda yield
edit akan dikirim kembali ke controller. Di sana, pengontrol menangani logikanya sampai tiba saatnya bot Anda membuat keputusan lain. Kemudian controller mengirimi Anda lemparan dadu, dan make_throw
fungsi Anda akan terus mengeksekusi tepat di mana jika berhenti sebelumnya, pada dasarnya di telepon setelah yield
pernyataan sebelumnya .
Dengan cara ini, game controller dapat memperbarui status tanpa memerlukan panggilan fungsi bot terpisah untuk setiap lemparan dadu.
Spesifikasi
Anda dapat menggunakan pustaka Python yang tersedia di pip
. Untuk memastikan bahwa saya bisa mendapatkan rata-rata yang baik, Anda memiliki batas waktu 100 milidetik per putaran. Saya akan sangat senang jika skrip Anda jauh lebih cepat dari itu, sehingga saya dapat menjalankan lebih banyak putaran.
Evaluasi
Untuk menemukan pemenang, saya akan mengambil semua bot dan menjalankannya dalam kelompok acak 8. Jika ada kurang dari 8 kelas yang dikirimkan, saya akan menjalankannya dalam kelompok acak 4 untuk menghindari selalu memiliki semua bot di setiap babak. Saya akan menjalankan simulasi selama sekitar 8 jam, dan pemenangnya adalah bot dengan persentase kemenangan tertinggi. Saya akan menjalankan mulai simulasi terakhir pada awal 2019, memberi Anda semua Natal untuk kode bot Anda! Tanggal akhir pendahuluan adalah 4 Januari, tetapi jika itu terlalu sedikit waktu saya dapat mengubahnya ke tanggal kemudian.
Sampai saat itu, saya akan mencoba membuat simulasi harian menggunakan waktu CPU 30-60 menit, dan memperbarui papan skor. Ini tidak akan menjadi skor resmi, tetapi akan berfungsi sebagai panduan untuk melihat bot mana yang melakukan yang terbaik. Namun, dengan Natal yang akan datang, saya harap Anda bisa mengerti bahwa saya tidak akan tersedia setiap saat. Saya akan melakukan yang terbaik untuk menjalankan simulasi dan menjawab pertanyaan yang terkait dengan tantangan.
Uji sendiri
Jika Anda ingin menjalankan simulasi Anda sendiri, inilah kode lengkap untuk pengontrol yang menjalankan simulasi, termasuk dua bot contoh.
Pengendali
Inilah pengendali yang diperbarui untuk tantangan ini. Ini mendukung keluaran ANSI, multi-threading, dan mengumpulkan statistik tambahan berkat AKroell ! Ketika saya membuat perubahan pada controller, saya akan memperbarui posting setelah dokumentasi selesai.
Berkat BMO , controller sekarang dapat mengunduh semua bot dari pos ini menggunakan -d
flag. Fungsionalitas lain tidak berubah dalam versi ini. Ini harus memastikan bahwa semua perubahan terbaru Anda disimulasikan sesegera mungkin!
#!/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)
Jika Anda ingin akses ke pengontrol asli untuk tantangan ini, ini tersedia di riwayat edit. Kontroler baru memiliki logika yang sama persis untuk menjalankan game, satu-satunya perbedaan adalah kinerja, pengumpulan stat dan pencetakan lebih cantik.
Bot
Di mesin saya, bot disimpan dalam file forty_game_bots.py
. Jika Anda menggunakan nama lain untuk file, Anda harus memperbarui import
pernyataan di bagian atas controller.
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
Menjalankan simulasi
Untuk menjalankan simulasi, simpan kedua cuplikan kode yang diposting di atas ke dua file terpisah. Saya telah menyimpannya sebagai forty_game_controller.py
dan forty_game_bots.py
. Kemudian Anda cukup menggunakan python forty_game_controller.py
atau python3 forty_game_controller.py
bergantung pada konfigurasi Python Anda. Ikuti instruksi dari sana jika Anda ingin mengkonfigurasi simulasi Anda lebih lanjut, atau coba bermain-main dengan kode jika Anda mau.
Statistik game
Jika Anda membuat bot yang bertujuan untuk skor tertentu tanpa mempertimbangkan bot lain, ini adalah persentil skor kemenangan:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Nilai yang tinggi
Ketika lebih banyak jawaban diposting, saya akan mencoba untuk menjaga daftar ini diperbarui. Isi daftar akan selalu dari simulasi terbaru. Bot ThrowTwiceBot
dan GoToTenBot
bot dari kode di atas, dan digunakan sebagai referensi. Saya melakukan simulasi dengan 10 ^ 8 game, yang memakan waktu sekitar 1 jam. Kemudian saya melihat bahwa permainan mencapai stabilitas dibandingkan dengan permainan saya dengan 10 ^ 7 pertandingan. Namun, dengan orang-orang masih memposting bot, saya tidak akan melakukan simulasi lagi sampai frekuensi tanggapan turun.
Saya mencoba menambahkan semua bot baru dan menambahkan perubahan yang telah Anda buat ke bot yang ada. Jika sepertinya saya telah melewatkan bot Anda atau perubahan baru yang Anda miliki, tulis di obrolan dan saya akan pastikan untuk memiliki versi terbaru Anda dalam simulasi berikutnya.
Kami sekarang memiliki lebih banyak statistik untuk setiap bot berkat AKroell ! Tiga kolom baru berisi skor maksimum di semua game, skor rata-rata per game, dan skor rata-rata saat menang untuk setiap bot.
Seperti yang ditunjukkan dalam komentar, ada masalah dengan logika permainan yang membuat bot yang memiliki indeks lebih tinggi dalam permainan mendapatkan putaran tambahan dalam beberapa kasus. Ini telah diperbaiki sekarang, dan skor di bawah mencerminkan ini.
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|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
Bot berikut (kecuali Rebel
) dibuat untuk membengkokkan aturan, dan pencipta telah setuju untuk tidak mengambil bagian dalam turnamen resmi. Namun, saya masih berpikir ide-ide mereka kreatif, dan mereka layak disebut terhormat. Pemberontak juga ada di daftar ini karena menggunakan strategi yang cerdas untuk menghindari sabotase, dan benar-benar berkinerja lebih baik dengan bot sabotase yang sedang bermain.
Bot NeoBot
dan KwisatzHaderach
memang mengikuti aturan, tetapi menggunakan celah dengan memprediksi generator acak. Karena bot ini membutuhkan banyak sumber daya untuk disimulasikan, saya telah menambahkan statistiknya dari simulasi dengan lebih sedikit game. Bot HarkonnenBot
mencapai kemenangan dengan melumpuhkan semua bot lainnya, yang sangat bertentangan dengan aturan.
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|
+----------+-----+