Turnuva bitti!
Turnuva şimdi bitti! Son simülasyon gece boyunca toplam oyun çalıştırıldı. Kazanan Christian Sievers , botu OptFor2X ile birlikte . Christian Sievers, ikinci sırayı da Rebel ile korumayı başardı . Tebrikler! Aşağıda turnuva için resmi yüksek skor listesini görebilirsiniz.
Hala oyunu oynamak istiyorsan, aşağıda belirtilen kontrol cihazını kullanıp kendi oyununu oluşturmak için içindeki kodu kullanmandan memnuniyet duyarsın.
Hiç duymadığım bir zar oyununa davet edildim. Kurallar basitti, ancak KotH mücadelesi için mükemmel olacağını düşünüyorum.
Kurallar
Oyunun başlangıcı
Kalıp masanın etrafına gider ve her zaman sizin sıranız olduğunda, kalıbı istediğiniz kadar atarsınız. Ancak, en az bir kere atmanız gerekir. Turunuz için tüm atışların toplamını takip edersiniz. Durdurmayı seçerseniz, raundun puanı toplam puanınıza eklenir.
Öyleyse neden ölmeyi atmayı bıraktın? Çünkü 6 alırsanız, tüm raund için puanınız sıfır olur ve kalıp geçer. Böylece, ilk hedef, puanınızı olabildiğince çabuk artırmaktır.
Kazanan kim?
Masanın etrafındaki ilk oyuncu 40 veya daha fazla puana ulaştığında, son tur başlar. Son tur başladıktan sonra, son turu başlatan kişi dışındaki herkes bir tur daha alır.
Son raundun kuralları, diğer raundlarla aynıdır. Atmaya devam etmeyi veya durmayı seçtin. Ancak, son rauntta önünüzden daha yüksek bir puan alamazsanız, kazanma şansınızın olmadığını biliyorsunuzdur. Ama fazla ileri gidersen 6 alabilirsin.
Ancak, dikkate alınması gereken bir kural daha var. Eğer mevcut toplam puanınız (önceki puanınız + turdaki mevcut puanınız) 40 veya daha fazla ise ve 6'ya bastığınızda toplam puanınız 0 olarak ayarlanır. Bu, her şeye baştan başlamanız gerektiği anlamına gelir. Mevcut toplam puanınız 40 veya daha fazla olduğunda 6'ya vursanız, oyun şu anda en son sırada olmanız dışında normal şekilde devam eder. Toplam puanınız sıfırlandığında son tur tetiklenmez. Hala raundu kazanabilirsin, ama daha zor hale geldi.
Son tur bittiğinde kazanan, en yüksek puana sahip oyuncu. İki veya daha fazla oyuncu aynı puanı paylaşırsa, hepsi galip sayılır.
Ek bir kural, oyunun maksimum 200 tur boyunca devam etmesidir. Bu, çoklu botların temelde mevcut puanlarında kalmak için 6'ya vurarak atmaya devam ettiği durumları önlemektir. 199. tur geçtikten last_round
sonra true olarak ayarlanır ve bir tur daha oynanır. Oyun 200 raund'a giderse, en yüksek puanı alan botlar (veya botlar) 40 puan veya daha fazla olmasa bile kazanır.
tekrarlamak
- Her turda durmayı seçinceye kadar ya da 6
- Bir kez kalıbı atmanız gerekir (ilk atışınız 6 ise, turunuz hemen biter)
- 6 alırsanız, mevcut puanınız 0'a ayarlanır (toplam puanınız değil)
- Her bir turdan sonra geçerli puanınızı toplam puanınıza eklersiniz.
- Bir bot sırasını tamamladığında en az 40 puan kazanırsa, diğerleri son sırayı alır
- Mevcut toplam puanınız ve 6 alırsanız, toplam puanınız 0 olarak ayarlanır ve .
- Yukarıdakiler gerçekleştiğinde son tur tetiklenmez
- Son turdan sonra en yüksek toplam puan alan kişi kazanır.
- Birden fazla kazanan olması durumunda, hepsi kazanan olarak sayılacaktır.
- Oyun maksimum 200 tur sürer
Skorların netleştirilmesi
- Toplam puan: önceki turlardan kaydettiğiniz puan
- Şu anki skor: güncel raund için puan
- Mevcut toplam puan: Yukarıdaki iki puanın toplamı
Nasıl katılırsın
Bu KotH yarışmasına katılmak için miras alan bir Python sınıfı yazmalısınız Bot
. : İşlevini uygulamanız gerekir make_throw(self, scores, last_round)
. Bu fonksiyon sizin sıranız geldiğinde çağrılacak ve ilk atışınız 6 değildi. Atmaya devam etmek için yapmalısınız yield True
. Atmayı durdurmak için yapmalısın yield False
. Her atıştan sonra ana işlev update_state
çağrılır. Böylece, geçerli raunttaki atışlarınızı değişkeni kullanarak erişebilirsiniz self.current_throws
. Ayrıca, kullanarak kendi dizininize de erişebilirsiniz self.index
. Böylece kendi toplam puanınızı görmek için kullanırsınız scores[self.index]
. Ayrıca end_score
oyuna kullanarak oyuna da erişebilirsiniz self.end_score
, ancak bu meydan okuma için 40 olacağını güvenli bir şekilde kabul edebilirsiniz.
Sınıfınızda yardımcı fonksiyonlar yaratmanıza izin verilir. Ayrıca Bot
, daha fazla sınıf özelliği eklemek istiyorsanız , ana sınıfta varolan işlevleri geçersiz kılabilirsiniz . Oyunun durumunu, verim True
veya oyun dışında hiçbir şekilde değiştiremezsin False
.
Bu gönderiden ilham almakta özgürsünüz ve buraya dahil ettiğim iki bottan herhangi birini kopyalıyorsunuz. Ancak, özellikle etkili olmadıklarından korkuyorum ...
Diğer dillere izin vermek üzerine
Hem sanal alanda hem de On Dokuzuncu Bayt'ta, diğer dillerde gönderimlere izin vermeyle ilgili tartışmalar yaptık. Bu tür uygulamaları okuduktan ve her iki taraftan argümanları dinledikten sonra, bu zorluğu sadece Python ile sınırlamaya karar verdim. Bunun nedeni iki faktörden kaynaklanıyor: Birden fazla dili desteklemek için gereken zaman ve bu zorluğun rastlantısallığı, kararlılığa ulaşmak için çok sayıda yinelemeyi gerektiriyor. İnşallah yine de katılacaksınız ve bu zorluk için bir Python öğrenmek istiyorsanız, sohbette mümkün olduğunca sık olmaya çalışacağım.
Aklınıza gelebilecek tüm sorularınız için, sohbet odasında bu zorluk için yazabilirsiniz . Orada görüşürüz!
kurallar
- Sabotaj izin verilir ve teşvik edilir. Yani, diğer oyunculara karşı sabotaj
- Denetleyiciyle, çalışma zamanıyla veya diğer gönderilerle bağlantı kurma girişimleri diskalifiye edilecektir. Tüm başvurular sadece verdikleri girdi ve depolama ile çalışmalıdır.
- Kararını vermek için 500 MB’dan fazla hafıza kullanan botlar diskalifiye edilir (bu kadar hafızaya ihtiyacınız varsa tercihlerinizi yeniden düşünmelisiniz)
- Bir bot, bilerek veya kazayla, mevcut olanla aynı stratejiyi uygulamamalıdır.
- Meydan okuma sırasında botunuzu güncellemenize izin verilir. Bununla birlikte, yaklaşımınız farklıysa başka bir bot da gönderebilirsiniz.
Örnek
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Bu bot tur için en az 10 olana kadar devam eder, ya da 6 atar. Atma için herhangi bir mantığa ihtiyacınız olmadığına dikkat edin. 6 Ayrıca, ilk atışı 6 ise make_throw
, Asla aranmadı, çünkü turunuz hemen sona eriyor.
Python'da yeni olan (ve yield
konsepti yeni olan ) ancak buna bir adım atmak isteyenler için, yield
anahtar kelime bazı şekillerde bir dönüşe benzer, ancak başka şekillerde farklıdır. Sen kavramı hakkında okuyabilirsiniz burada . Temelde, bir kez yield
, işleviniz durur ve yield
girdiğiniz değer denetleyiciye geri gönderilir. Burada, denetleyici, botunuzun başka bir karar vermesi için gereken zamana kadar mantığını ele alır. Ardından kontrolör size zar atışı gönderir ve make_throw
fonksiyonunuz daha önce durduğunda, temel olarak bir önceki yield
ifadeden sonraki hatta çalışmaya devam eder .
Bu sayede, oyun kumandası her zar atışı için ayrı bir bot işlev çağrısı gerektirmeden durumu güncelleyebilir.
Şartname
İçinde bulunan herhangi bir Python kütüphanesini kullanabilirsiniz pip
. İyi bir ortalama alabileceğimi garantilemek için, tur başına 100 milisaniye zaman sınırına sahipsiniz. Senaryonuz bundan daha hızlı olsaydı daha mutlu olurdum, böylece daha fazla raund koştum.
Değerlendirme
Kazanan bulmak için, tüm botları alıp 8 kişilik rasgele gruplar halinde koşacağım. Gönderilen 8'den az ders varsa, her turda her zaman tüm botların bulunmamasını önlemek için 4 kişilik rasgele gruplar halinde koşacağım. Simülasyonları yaklaşık 8 saat çalıştıracağım ve kazanan en yüksek kazanma oranına sahip bot olacak. 2019 başında final simülasyonlarına başlayacağım ve botlarınızı kodlamanız için tüm Noel'leri size vereceğim! İlk final tarihi 4 Ocak, ancak çok küçük bir zaman ise daha sonraki bir tarihe değiştirebilirim.
O zamana kadar 30-60 dakika CPU zamanı kullanarak ve puan tablosunu güncelleyerek günlük bir simülasyon yapmaya çalışacağım. Bu resmi puan olmayacak, ancak hangi botların en iyi performansı gösterdiğini görmek için bir rehber olacak. Ancak, Noel yaklaşırken, umarım her zaman müsait olmayacağımı anlarsınız. Simülasyon yapmak ve zorluklarla ilgili soruları yanıtlamak için elimden geleni yapacağım.
Kendin test et
Kendi simülasyonlarınızı çalıştırmak istiyorsanız, iki örnek bot dahil olmak üzere simülasyonu çalıştıran kontrol cihazının tam kodu.
kontrolör
İşte bu zorluk için güncellenmiş kontrolör. ANSI çıktılarını destekler, çoklu iş parçacığı ve AKroell sayesinde ek istatistikler toplar ! Denetleyicide değişiklikler yaptığımda, belgeler tamamlandıktan sonra gönderiyi güncelleyeceğim.
Sayesinde BMO , kontrolör şimdi kullanarak bu görevinden tüm botlara indirmek mümkün olan -d
bayrak. Bu sürümde diğer işlevler değişmemiştir. Bu, tüm son değişikliklerin mümkün olan en kısa sürede simüle edilmesini sağlamalıdır!
#!/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)
Bu meydan okuma için orijinal denetleyiciye erişmek istiyorsanız, düzenleme geçmişinde kullanılabilir. Yeni kumanda oyunu çalıştırmak için aynı mantığa sahip, tek fark performans, stat toplama ve daha güzel baskı.
botlar
Makinemde, botlar dosyada tutuluyor forty_game_bots.py
. Dosya için başka bir ad kullanıyorsanız import
, denetleyicinin üstündeki ifadeyi güncellemelisiniz .
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
Simülasyonu çalıştırma
Simülasyon çalıştırmak için, yukarıda belirtilen her iki kod parçasını da iki ayrı dosyaya kaydedin. Onları forty_game_controller.py
ve olarak kurtardım forty_game_bots.py
. Daha sonra Python yapılandırmanıza bağlı olarak python forty_game_controller.py
veya kullanırsınız python3 forty_game_controller.py
. Simülasyonunuzu daha fazla yapılandırmak istiyorsanız oradaki talimatları izleyin ya da isterseniz kodu eklemeyi deneyin.
Oyun istatistikleri
Başka botlar göz önünde bulundurulmaksızın belirli bir skoru hedefleyen bir bot yapıyorsanız, bunlar kazanan skor yüzdeleridir:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Yüksek puanlar
Daha fazla cevap gönderildiği için bu listeyi güncel tutmaya çalışacağım. Listenin içeriği her zaman en son simülasyondan olacaktır. Botlar ThrowTwiceBot
ve GoToTenBot
yukarıdaki koddaki botlardır ve referans olarak kullanılırlar. Yaklaşık 1 saat süren 10 ^ 8 oyunla bir simülasyon yaptım. Daha sonra 10 ^ 7 oyun ile oyunumun koşularla karşılaştırıldığında dengeye ulaştığını gördüm. Bununla birlikte, insanlar hala bot gönderirken, cevap sıklığı azalıncaya kadar daha fazla simülasyon yapmayacağım.
Tüm yeni botları eklemeye ve mevcut botlara yaptığınız değişiklikleri eklemeye çalışıyorum. Botunuzu veya yaptığınız herhangi bir değişikliği kaçırmışım gibi görünüyorsa, sohbete yazın ve bir sonraki simülasyonda en son sürümünüzü aldığınızdan emin olacağım.
AKroell sayesinde artık her bot için daha fazla istatistik var ! Üç yeni sütun tüm oyunlar için maksimum puanı, oyun başına ortalama puanı ve her bot için kazanma sırasındaki ortalama puanı içerir.
Yorumlarda da belirtildiği gibi, oyun mantığında, oyun içinde daha yüksek indeksli botların bazı durumlarda fazladan bir tur atmasına neden olan bir sorun vardı. Bu şimdi düzeltildi ve aşağıdaki puanlar bunu yansıtıyor.
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
Kuralları değiştirmek için aşağıdaki botlar (hariç ) yapılır ve yaratıcılar resmi turnuvaya katılmamayı kabul etmişlerdir. Bununla birlikte, fikirlerinin yaratıcı olduğunu düşünüyorum ve onurlu bir sözü hakediyorlar. İsyancı da bu listede çünkü sabotajı önlemek için akıllı bir strateji kullanıyor ve oyunda sabote edici botla daha iyi performans gösteriyor.
Botlar NeoBot
ve KwisatzHaderach
kurallara uyuyor, ancak rasgele üreteci tahmin ederek bir boşluk kullanıyor. Bu botlar taklit etmek için çok kaynak kullandığından, daha az oyun içeren bir simülasyondan istatistiklerini ekledim. Bot HarkonnenBot
, kurallara kesinlikle aykırı olan diğer tüm botları devre dışı bırakarak zafer kazanıyor.
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|
+----------+-----+