Giải đấu kết thúc!
Giải đấu đã kết thúc! Mô phỏng cuối cùng được thực hiện trong đêm, tổng cộng trò chơi. Người chiến thắng là Christian Sievers với bot OptFor2X của mình . Christian Sievers cũng cố gắng đảm bảo vị trí thứ hai với Rebel . Xin chúc mừng! Dưới đây bạn có thể xem danh sách điểm cao chính thức cho giải đấu.
Nếu bạn vẫn muốn chơi trò chơi, bạn nên sử dụng bộ điều khiển được đăng bên dưới và sử dụng mã trong đó để tạo trò chơi của riêng bạn.
Tôi được mời chơi một trò chơi súc sắc mà tôi chưa bao giờ nghe nói đến. Các quy tắc rất đơn giản, nhưng tôi nghĩ nó sẽ hoàn hảo cho một thử thách KotH.
Những quy định
Bắt đầu trò chơi
Con súc sắc đi vòng quanh bàn, và mỗi khi đến lượt của bạn, bạn có thể ném con súc vật nhiều lần như bạn muốn. Tuy nhiên, bạn phải ném nó ít nhất một lần. Bạn theo dõi tổng của tất cả các cú ném cho vòng của bạn. Nếu bạn chọn dừng lại, điểm cho vòng được thêm vào tổng số điểm của bạn.
Vậy tại sao bạn lại ngừng ném chết? Bởi vì nếu bạn nhận được 6, điểm số của bạn cho toàn bộ vòng sẽ bằng 0 và điểm chết được truyền lại. Do đó, mục tiêu ban đầu là tăng điểm của bạn càng nhanh càng tốt.
Ai là người chiến thắng?
Khi người chơi đầu tiên quanh bàn đạt 40 điểm trở lên, vòng cuối cùng bắt đầu. Khi vòng cuối cùng bắt đầu, tất cả mọi người ngoại trừ người bắt đầu vòng cuối cùng được thêm một lượt.
Các quy tắc cho vòng cuối cùng giống như bất kỳ vòng nào khác. Bạn chọn tiếp tục ném hoặc dừng lại. Tuy nhiên, bạn biết rằng bạn không có cơ hội chiến thắng nếu bạn không đạt được điểm cao hơn so với những người trước bạn vào vòng cuối cùng. Nhưng nếu bạn tiếp tục đi quá xa, thì bạn có thể nhận được 6.
Tuy nhiên, có một quy tắc nữa để xem xét. Nếu tổng số điểm hiện tại của bạn (điểm trước đó + điểm hiện tại của bạn cho vòng đấu) là 40 trở lên và bạn đạt 6, tổng điểm của bạn được đặt thành 0. Điều đó có nghĩa là bạn phải bắt đầu lại tất cả. Nếu bạn đạt 6 điểm khi tổng điểm hiện tại của bạn là 40 trở lên, trò chơi sẽ tiếp tục như bình thường, ngoại trừ việc bạn đang ở vị trí cuối cùng. Vòng cuối cùng không được kích hoạt khi tổng số điểm của bạn được đặt lại. Bạn vẫn có thể giành chiến thắng trong vòng này, nhưng nó trở nên khó khăn hơn.
Người chiến thắng là người chơi có số điểm cao nhất khi vòng cuối cùng kết thúc. Nếu hai hoặc nhiều người chơi chia sẻ cùng một số điểm, tất cả họ sẽ được tính là người chiến thắng.
Một quy tắc được thêm vào là trò chơi tiếp tục tối đa 200 vòng. Điều này là để ngăn chặn các trường hợp nhiều bot về cơ bản tiếp tục ném cho đến khi chúng đạt 6 điểm để giữ nguyên số điểm hiện tại. Khi vòng thứ 199 được thông qua, last_round
được đặt thành đúng và một vòng nữa được phát. Nếu trò chơi đi được 200 vòng, bot (hoặc bot) có số điểm cao nhất sẽ là người chiến thắng, ngay cả khi họ không có 40 điểm trở lên.
Tóm tắt
- Mỗi vòng bạn tiếp tục ném chết cho đến khi bạn chọn dừng lại hoặc bạn nhận được 6
- Bạn phải ném chết một lần (nếu lần ném đầu tiên của bạn là 6, vòng của bạn ngay lập tức kết thúc)
- Nếu bạn được điểm 6, điểm số hiện tại của bạn được đặt thành 0 (không phải tổng điểm của bạn)
- Bạn thêm số điểm hiện tại của bạn vào tổng số điểm của bạn sau mỗi vòng
- Khi một bot kết thúc lượt của họ dẫn đến tổng số điểm ít nhất là 40, mọi người khác sẽ có lượt cuối cùng
- Nếu tổng số điểm hiện tại của bạn là và bạn nhận được 6, tổng số điểm của bạn được đặt thành 0 và vòng của bạn kết thúc
- Vòng cuối cùng không được kích hoạt khi xảy ra ở trên
- Người có tổng điểm cao nhất sau vòng cuối cùng là người chiến thắng
- Trong trường hợp có nhiều người chiến thắng, tất cả sẽ được tính là người chiến thắng
- Trò chơi kéo dài tối đa 200 vòng
Làm rõ điểm số
- Tổng số điểm: số điểm mà bạn đã lưu từ các vòng trước
- Điểm hiện tại: điểm cho vòng hiện tại
- Tổng điểm hiện tại: tổng của hai điểm trên
Bạn tham gia như thế nào
Để tham gia vào thử thách KotH này, bạn nên viết một lớp Python kế thừa từ đó Bot
. Bạn nên thực hiện chức năng : make_throw(self, scores, last_round)
. Hàm đó sẽ được gọi khi đến lượt của bạn và lần ném đầu tiên của bạn không phải là 6. Để tiếp tục ném, bạn nên thực hiện yield True
. Để ngừng ném, bạn nên yield False
. Sau mỗi lần ném, hàm cha update_state
được gọi. Vì vậy, bạn có quyền truy cập vào các cú ném của mình cho vòng hiện tại bằng cách sử dụng biến self.current_throws
. Bạn cũng có quyền truy cập vào chỉ mục của riêng bạn bằng cách sử dụng self.index
. Vì vậy, để xem tổng số điểm của riêng bạn, bạn sẽ sử dụng scores[self.index]
. Bạn cũng có thể truy cập vào end_score
trò chơi bằng cách sử dụng self.end_score
, nhưng bạn có thể cho rằng nó sẽ là 40 cho thử thách này.
Bạn được phép tạo các hàm trợ giúp trong lớp. Bạn cũng có thể ghi đè các hàm hiện có trong Bot
lớp cha, ví dụ nếu bạn muốn thêm nhiều thuộc tính lớp. Bạn không được phép sửa đổi trạng thái của trò chơi theo bất kỳ cách nào ngoại trừ năng suất True
hoặc False
.
Bạn có thể tự do tìm kiếm nguồn cảm hứng từ bài đăng này và sao chép bất kỳ hai bot mà tôi đã đưa vào đây. Tuy nhiên, tôi sợ rằng chúng không đặc biệt hiệu quả ...
Cho phép các ngôn ngữ khác
Trong cả hộp cát và trên Mười chín Byte, chúng tôi đã có các cuộc thảo luận về việc cho phép gửi bằng các ngôn ngữ khác. Sau khi đọc về các triển khai như vậy và nghe các đối số từ cả hai phía, tôi đã quyết định chỉ giới hạn thử thách này với Python. Điều này là do hai yếu tố: thời gian cần thiết để hỗ trợ nhiều ngôn ngữ và tính ngẫu nhiên của thử thách này đòi hỏi số lần lặp cao để đạt được sự ổn định. Tôi hy vọng rằng bạn vẫn sẽ tham gia và nếu bạn muốn tìm hiểu một số Python cho thử thách này, tôi sẽ cố gắng có mặt trong cuộc trò chuyện thường xuyên nhất có thể.
Đối với bất kỳ câu hỏi mà bạn có thể có, bạn có thể viết trong phòng trò chuyện cho thử thách này . Hẹn gặp bạn ở đó
Quy tắc
- Phá hoại được cho phép, và khuyến khích. Đó là, phá hoại đối với người chơi khác
- Bất kỳ nỗ lực nào để sửa lại bộ điều khiển, thời gian chạy hoặc các bài nộp khác sẽ bị loại. Tất cả các đệ trình chỉ nên làm việc với các đầu vào và lưu trữ mà chúng được đưa ra.
- Bất kỳ bot nào sử dụng bộ nhớ lớn hơn 500 MB để đưa ra quyết định sẽ bị loại (nếu bạn cần nhiều bộ nhớ đó, bạn nên suy nghĩ lại về lựa chọn của mình)
- Một bot không được thực hiện chiến lược chính xác giống như một chiến lược hiện có, cố ý hay vô tình.
- Bạn được phép cập nhật bot của mình trong thời gian thử thách. Tuy nhiên, bạn cũng có thể đăng một bot khác nếu cách tiếp cận của bạn khác.
Thí dụ
class GoToTenBot(Bot):
def make_throw(self, scores, last_round):
while sum(self.current_throws) < 10:
yield True
yield False
Bot này sẽ tiếp tục cho đến khi nó có số điểm ít nhất là 10 cho vòng, hoặc nó ném 6. Lưu ý rằng bạn không cần bất kỳ logic nào để xử lý ném 6. Ngoài ra, hãy lưu ý rằng nếu lần ném đầu tiên của bạn là 6, make_throw
là không bao giờ được gọi, vì vòng của bạn là ngay lập tức kết thúc.
Đối với những người chưa quen với Python (và mới biết về yield
khái niệm này), nhưng muốn thử, yield
từ khóa này tương tự như trả về theo một số cách, nhưng khác về các cách khác. Bạn có thể đọc về khái niệm ở đây . Về cơ bản, một khi bạn yield
, chức năng của bạn sẽ dừng lại và giá trị bạn yield
ed sẽ được gửi lại cho bộ điều khiển. Ở đó, bộ điều khiển xử lý logic của nó cho đến khi bot của bạn đưa ra quyết định khác. Sau đó, bộ điều khiển gửi cho bạn cú ném xúc xắc, và make_throw
chức năng của bạn sẽ tiếp tục thực thi ngay khi dừng trước, về cơ bản trên dòng sau yield
câu lệnh trước .
Bằng cách này, bộ điều khiển trò chơi có thể cập nhật trạng thái mà không yêu cầu một lệnh gọi chức năng bot riêng cho mỗi lần ném xúc xắc.
Đặc điểm kỹ thuật
Bạn có thể sử dụng bất kỳ thư viện Python nào có sẵn trong pip
. Để đảm bảo rằng tôi có thể đạt được mức trung bình tốt, bạn có giới hạn thời gian 100 mili giây mỗi vòng. Tôi thực sự rất vui nếu kịch bản của bạn nhanh hơn thế, để tôi có thể chạy nhiều vòng hơn.
Đánh giá
Để tìm ra người chiến thắng, tôi sẽ lấy tất cả các bot và chạy chúng trong các nhóm ngẫu nhiên 8. Nếu có ít hơn 8 lớp được gửi, tôi sẽ chạy chúng trong các nhóm 4 ngẫu nhiên để tránh luôn có tất cả các bot trong mỗi vòng. Tôi sẽ chạy mô phỏng trong khoảng 8 giờ và người chiến thắng sẽ là bot có tỷ lệ thắng cao nhất. Tôi sẽ chạy bắt đầu các mô phỏng cuối cùng vào đầu năm 2019, mang đến cho bạn tất cả Giáng sinh để mã hóa bot của bạn! Ngày cuối cùng sơ bộ là ngày 4 tháng 1, nhưng nếu đó là quá ít thời gian tôi có thể thay đổi nó thành một ngày sau đó.
Cho đến lúc đó, tôi sẽ cố gắng thực hiện một mô phỏng hàng ngày với thời gian CPU 30-60 phút và cập nhật bảng điểm. Đây sẽ không phải là điểm chính thức, nhưng nó sẽ phục vụ như một hướng dẫn để xem bot nào hoạt động tốt nhất. Tuy nhiên, với Giáng sinh sắp tới, tôi hy vọng bạn có thể hiểu rằng tôi sẽ không có mặt mọi lúc. Tôi sẽ làm hết sức mình để chạy mô phỏng và trả lời bất kỳ câu hỏi nào liên quan đến thử thách.
Tự kiểm tra
Nếu bạn muốn chạy mô phỏng của riêng mình, đây là mã đầy đủ cho bộ điều khiển chạy mô phỏng, bao gồm hai bot mẫu.
Bộ điều khiển
Đây là bộ điều khiển cập nhật cho thử thách này. Nó hỗ trợ đầu ra ANSI, đa luồng và thu thập các số liệu thống kê bổ sung nhờ AKroell ! Khi tôi thay đổi bộ điều khiển, tôi sẽ cập nhật bài đăng sau khi tài liệu hoàn tất.
Nhờ BMO , bộ điều khiển hiện có thể tải xuống tất cả các bot từ bài đăng này bằng -d
cờ. Chức năng khác là không thay đổi trong phiên bản này. Điều này sẽ đảm bảo rằng tất cả các thay đổi mới nhất của bạn được mô phỏng càng sớm càng tốt!
#!/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)
Nếu bạn muốn truy cập vào bộ điều khiển gốc cho thử thách này, nó có sẵn trong lịch sử chỉnh sửa. Bộ điều khiển mới có logic chính xác tương tự để chạy trò chơi, sự khác biệt duy nhất là hiệu suất, bộ sưu tập chỉ số và in ấn đẹp hơn.
Bots
Trên máy của tôi, các bot được giữ trong tệp forty_game_bots.py
. Nếu bạn sử dụng bất kỳ tên nào khác cho tệp, bạn phải cập nhật import
câu lệnh ở đầu bộ điều khiển.
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
Chạy mô phỏng
Để chạy một mô phỏng, lưu cả hai đoạn mã được đăng ở trên vào hai tệp riêng biệt. Tôi đã cứu họ như forty_game_controller.py
và forty_game_bots.py
. Sau đó, bạn chỉ cần sử dụng python forty_game_controller.py
hoặc python3 forty_game_controller.py
tùy thuộc vào cấu hình Python của bạn. Làm theo các hướng dẫn từ đó nếu bạn muốn định cấu hình mô phỏng của mình hơn nữa hoặc thử sửa đổi mã nếu bạn muốn.
Thống kê trò chơi
Nếu bạn đang tạo một bot nhắm đến một số điểm nhất định mà không xem xét các bot khác, thì đây là các phần trăm điểm chiến thắng:
+----------+-----+
|Percentile|Score|
+----------+-----+
| 50.00| 44|
| 75.00| 48|
| 90.00| 51|
| 95.00| 54|
| 99.00| 58|
| 99.90| 67|
| 99.99| 126|
+----------+-----+
Điểm cao
Khi có nhiều câu trả lời được đăng, tôi sẽ cố gắng cập nhật danh sách này. Nội dung của danh sách sẽ luôn là từ mô phỏng mới nhất. Các bot ThrowTwiceBot
và GoToTenBot
là các bot từ mã ở trên, và được sử dụng làm tài liệu tham khảo. Tôi đã thực hiện một mô phỏng với 10 ^ 8 trò chơi, mất khoảng 1 giờ. Sau đó, tôi thấy rằng trò chơi đạt đến sự ổn định so với các lần chạy của tôi với 10 ^ 7 trò chơi. Tuy nhiên, với những người vẫn đăng bot, tôi sẽ không thực hiện mô phỏng nào nữa cho đến khi tần suất phản hồi giảm.
Tôi cố gắng thêm tất cả các bot mới và thêm bất kỳ thay đổi nào bạn đã thực hiện cho các bot hiện có. Nếu có vẻ như tôi đã bỏ lỡ bot của bạn hoặc bất kỳ thay đổi mới nào bạn có, hãy viết trong cuộc trò chuyện và tôi chắc chắn sẽ có phiên bản mới nhất của bạn trong mô phỏng tiếp theo.
Bây giờ chúng tôi có nhiều số liệu thống kê hơn cho mỗi bot nhờ AKroell ! Ba cột mới chứa điểm tối đa trên tất cả các trò chơi, điểm trung bình cho mỗi trò chơi và điểm trung bình khi chiến thắng cho mỗi bot.
Như đã chỉ ra trong các bình luận, có một vấn đề với logic trò chơi khiến các bot có chỉ số cao hơn trong trò chơi có thêm một vòng trong một số trường hợp. Điều này đã được sửa chữa và điểm số dưới đây phản ánh điều này.
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|
+-----------------------+----+--------+--------+------+------+-------+------+--------+
Các bot sau (ngoại trừ Rebel
) được tạo ra để bẻ cong các quy tắc và những người sáng tạo đã đồng ý không tham gia giải đấu chính thức. Tuy nhiên, tôi vẫn nghĩ rằng ý tưởng của họ là sáng tạo, và họ xứng đáng được đề cập đến. Rebel cũng nằm trong danh sách này vì nó sử dụng một chiến lược thông minh để tránh phá hoại, và thực sự hoạt động tốt hơn với bot phá hoại đang chơi.
Các bot NeoBot
và KwisatzHaderach
không tuân theo các quy tắc, nhưng sử dụng kẽ hở bằng cách dự đoán trình tạo ngẫu nhiên. Vì các bot này cần rất nhiều tài nguyên để mô phỏng, tôi đã thêm số liệu thống kê của nó từ một mô phỏng với ít trò chơi hơn. Bot HarkonnenBot
đạt được chiến thắng bằng cách vô hiệu hóa tất cả các bot khác, điều này hoàn toàn trái với quy tắc.
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|
+----------+-----+