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