Code Runner
So, to make things interesting, I created a script to automatically download the code from every posted answer, compile it if necessary, and then run all of the solutions according to the rules. This way, people can check how they are doing. Just save this script to run_all.py (requires BeautifulSoup) and then:
usage:
To get the latest code: 'python run_all.py get'
To run the submissions: 'python run_all.py run <optional num_runs>'
A few things:
- If you want to add support for more languages, or alternatively remove support for some, see
def submission_type(lang)
.
- Extending the script should be fairly easy, even for languages that require compilation (see
CPPSubmission
). The language type is grabbed from the meta code tag < !-- language: lang-java -- >
, so make sure to add it if you want your code to be run (Remove the extra spaces before and after the <>).
UPDATE: There is now some extremely basic inference to try and detect the language if it is not defined.
- If your code fails to run at all, or fails to finish within the allotted time, it will be added to
blacklist.text
and will be removed from future trials automatically. If you fix your code, just remove your entry from the blacklist and re-run get
,
Currently supported languages:
submission_types = {
'lang-ruby': RubySubmission,
'lang-python': PythonSubmission,
'lang-py': PythonSubmission,
'lang-java': JavaSubmission,
'lang-Java': JavaSubmission,
'lang-javascript': NodeSubmission,
'lang-cpp': CPPSubmission,
'lang-c': CSubmission,
'lang-lua': LuaSubmission,
'lang-r': RSubmission,
'lang-fortran': FortranSubmission,
'lang-bash': BashSubmission
}
Without further ado:
import urllib2
import hashlib
import os
import re
import subprocess
import shutil
import time
import multiprocessing
import tempfile
import sys
from bs4 import BeautifulSoup
__run_java__ = """
public class Run {
public static void main(String[] args) {
String input = "";
Human h = new __REPLACE_ME__();
if(args.length == 1)
input = args[0];
try {
System.out.println(h.takeSides(input));
}
catch(Exception e) {
}
}
}
"""
__human_java__ = """
public abstract class Human {
public abstract String takeSides(String history) throws Exception;
}
"""
class Submission():
def __init__(self, name, code):
self.name = name
self.code = code
def submissions_dir(self):
return 'submission'
def base_name(self):
return 'run'
def submission_path(self):
return os.path.join(self.submissions_dir(), self.name)
def extension(self):
return ""
def save_submission(self):
self.save_code()
def full_command(self, input):
return []
def full_path(self):
file_name = "%s.%s" % (self.base_name(), self.extension())
full_path = os.path.join(self.submission_path(), file_name)
return full_path
def save_code(self):
if not os.path.exists(self.submission_path()):
os.makedirs(self.submission_path())
with open(self.full_path(), 'w') as f:
f.write(self.code)
def write_err(self, err):
with open(self.error_log(), 'w') as f:
f.write(err)
def error_log(self):
return os.path.join(self.submission_path(), 'error.txt')
def run_submission(self, input):
command = self.full_command()
if input is not None:
command.append(input)
try:
output,err,exit_code = run(command,timeout=1)
if len(err) > 0:
self.write_err(err)
return output
except Exception as e:
self.write_err(str(e))
return ""
class CPPSubmission(Submission):
def bin_path(self):
return os.path.join(self.submission_path(), self.base_name())
def save_submission(self):
self.save_code()
compile_cmd = ['g++', '-O3', '-std=c++0x', '-o', self.bin_path(), self.full_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'cpp'
def full_command(self):
return [self.bin_path()]
class CSubmission(Submission):
def bin_path(self):
return os.path.join(self.submission_path(), self.base_name())
def save_submission(self):
self.save_code()
compile_cmd = ['gcc', '-o', self.bin_path(), self.full_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'c'
def full_command(self):
return [self.bin_path()]
class FortranSubmission(Submission):
def bin_path(self):
return os.path.join(self.submission_path(), self.base_name())
def save_submission(self):
self.save_code()
compile_cmd = ['gfortran', '-fno-range-check', '-o', self.bin_path(), self.full_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'f90'
def full_command(self):
return [self.bin_path()]
class JavaSubmission(Submission):
def base_name(self):
class_name = re.search(r'class (\w+) extends', self.code)
file_name = class_name.group(1)
return file_name
def human_base_name(self):
return 'Human'
def run_base_name(self):
return 'Run'
def full_name(self, base_name):
return '%s.%s' % (base_name, self.extension())
def human_path(self):
return os.path.join(self.submission_path(), self.full_name(self.human_base_name()))
def run_path(self):
return os.path.join(self.submission_path(), self.full_name(self.run_base_name()))
def replace_in_file(self, file_name, str_orig, str_new):
old_data = open(file_name).read()
new_data = old_data.replace(str_orig, str_new)
with open(file_name, 'w') as f:
f.write(new_data)
def write_code_to_file(self, code_str, file_name):
with open(file_name, 'w') as f:
f.write(code_str)
def save_submission(self):
self.save_code()
self.write_code_to_file(__human_java__, self.human_path())
self.write_code_to_file(__run_java__, self.run_path())
self.replace_in_file(self.run_path(), '__REPLACE_ME__', self.base_name())
self.replace_in_file(self.full_path(), 'package Humans;', '')
compile_cmd = ['javac', '-cp', self.submission_path(), self.run_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'java'
def full_command(self):
return ['java', '-cp', self.submission_path(), self.run_base_name()]
class PythonSubmission(Submission):
def full_command(self):
return ['python', self.full_path()]
def extension(self):
return 'py'
class RubySubmission(Submission):
def full_command(self):
return ['ruby', self.full_path()]
def extension(self):
return 'rb'
class NodeSubmission(Submission):
def full_command(self):
return ['node', self.full_path()]
def extension(self):
return 'js'
class LuaSubmission(Submission):
def full_command(self):
return ['lua', self.full_path()]
def extension(self):
return 'lua'
class RSubmission(Submission):
def full_command(self):
return ['Rscript', self.full_path()]
def extension(self):
return 'R'
class BashSubmission(Submission):
def full_command(self):
return [self.full_path()]
def extension(self):
return '.sh'
class Scraper():
def download_page(self, url, use_cache = True, force_cache_update = False):
file_name = hashlib.sha1(url).hexdigest()
if not os.path.exists('cache'):
os.makedirs('cache')
full_path = os.path.join('cache', file_name)
file_exists = os.path.isfile(full_path)
if use_cache and file_exists and not force_cache_update:
html = open(full_path, 'r').read()
return html
opener = urllib2.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
response = opener.open(url)
html = response.read()
if use_cache:
f = open(full_path, 'w')
f.write(html)
f.close()
return html
def parse_post(self, post):
name = post.find(text=lambda t: len(t.strip()) > 0)
pre = post.find('pre')
lang = pre.attrs['class'][0] if pre.has_attr('class') else None
code = pre.find('code').text
user = post.find(class_='user-details').find(text=True)
return {'name':name,'lang':lang,'code':code,'user':user}
def parse_posts(self, html):
soup = BeautifulSoup(html)
# Skip the first post
posts = soup.find_all(class_ = 'answercell')
return [self.parse_post(post) for post in posts]
def get_submissions(self, page = 1, force_cache_update = False):
url = "http://codegolf.stackexchange.com/questions/33137/good-versus-evil?page=%i&tab=votes#tab-top" % page
html = self.download_page(url, use_cache = True, force_cache_update = force_cache_update)
submissions = self.parse_posts(html)
return submissions
class Timeout(Exception):
pass
def run(command, timeout=10):
proc = subprocess.Popen(command, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poll_seconds = .250
deadline = time.time()+timeout
while time.time() < deadline and proc.poll() == None:
time.sleep(poll_seconds)
if proc.poll() == None:
if float(sys.version[:3]) >= 2.6:
proc.terminate()
raise Timeout()
stdout, stderr = proc.communicate()
return stdout, stderr, proc.returncode
def guess_lang(code):
if re.search(r'class .* extends Human', code):
return 'lang-java'
if re.search(r'import sys', code):
return 'lang-python'
if re.search(r'puts', code) and (re.search(r'ARGV', code) or re.search(r'\%w', code)):
return 'lang-ruby'
if re.search(r'console\.log', code):
return 'lang-javascript'
if re.search(r'program', code) and re.search(r'subroutine', code):
return 'lang-fortran'
if re.search(r'@echo off', code):
return 'lang-bash'
return None
def submission_type(lang, code):
submission_types = {
'lang-ruby': RubySubmission,
'lang-python': PythonSubmission,
'lang-py': PythonSubmission,
'lang-java': JavaSubmission,
'lang-Java': JavaSubmission,
'lang-javascript': NodeSubmission,
'lang-cpp': CPPSubmission,
'lang-c': CSubmission,
'lang-lua': LuaSubmission,
'lang-r': RSubmission,
'lang-fortran': FortranSubmission,
'lang-bash': BashSubmission
}
klass = submission_types.get(lang)
if klass is None:
lang = guess_lang(code)
klass = submission_types.get(lang)
return klass
def instantiate(submission):
lang = submission['lang']
code = submission['code']
name = submission['name']
klass = submission_type(lang, code)
if klass is not None:
instance = klass(name, code)
return instance
print "Entry %s invalid - lang not supported: %s" % (name, lang)
return None
def get_all_instances(force_update):
scraper = Scraper()
print 'Scraping Submissions..'
pages = [1,2,3]
submissions_by_page = [scraper.get_submissions(page=i, force_cache_update=force_update) for i in pages]
submissions = [item for sublist in submissions_by_page for item in sublist]
# Get instances
raw_instances = [instantiate(s) for s in submissions]
instances = [i for i in raw_instances if i]
print "Using %i/%i Submissions" % (len(instances), len(submissions))
return instances
def save_submissions(instances):
print 'Saving Submissions..'
for instance in instances:
instance.save_submission()
def init_game(save=True, force_update=False):
instances = get_all_instances(force_update)
if save:
save_submissions(instances)
return instances
def one_run(instances, input):
valid = {
'good': 1,
'evil': 0
}
disqualified = []
results = []
for instance in instances:
out = instance.run_submission(input)
res = out.strip().lower()
if res not in valid:
disqualified.append(instance)
else:
results.append(valid[res])
return (results, disqualified)
def get_winner(scores, instances):
max_value = max(scores)
max_index = scores.index(max_value)
instance = instances[max_index]
return (instance.name, max_value)
def update_scores(results, scores, minority_counts, minority_num):
for i in range(len(results)):
if results[i] == minority_num:
minority_counts[i] += 1
scores[i] += (minority_counts[i] - 1)
else:
minority_counts[i] = 0
scores[i] += 3
def try_run_game(instances, num_runs = 1000, blacklist = None):
current_input = None
minority_str = None
num_instances = len(instances)
scores = [0] * num_instances
minority_counts = [0] * num_instances
print "Running with %i instances..." % num_instances
for i in range(num_runs):
print "Round: %i - Last minority was %s" % (i, minority_str)
results, disqualified = one_run(instances, current_input)
if len(disqualified) > 0:
for instance in disqualified:
print "Removing %s!" % instance.name
instances.remove(instance)
if blacklist is not None:
with open(blacklist, 'a') as f:
f.write("%s\n" % instance.name)
return False
latest_result = "".join(map(str,results))
current_input = "%s,%s" % (current_input, latest_result)
minority_num = 1 if results.count(1) < results.count(0) else 0
minority_str = 'good' if minority_num == 1 else 'evil'
update_scores(results, scores, minority_counts, minority_num)
name, score = get_winner(scores, instances)
print "%s is currently winning with a score of %i" % (name, score)
print "The winner is %s with a score of %i!!!" % (name, score)
return True
def find_instance_by_name(instances, name):
for instance in instances:
if instance.name == name:
return instance
return None
def maybe_add_or_remove_baelish(instances, baelish):
num_instances = len(instances)
if num_instances % 2 == 0:
print 'There are %i instances.' % num_instances
try:
instances.remove(baelish)
print 'Baelish Removed!'
except:
instances.append(baelish)
print 'Baelish Added!'
def remove_blacklisted(blacklist, instances):
blacklisted = []
try:
blacklisted = open(blacklist).readlines()
except:
return
print 'Removing blacklisted entries...'
for name in blacklisted:
name = name.strip()
instance = find_instance_by_name(instances, name)
if instance is not None:
print 'Removing %s' % name
instances.remove(instance)
def run_game(instances, num_runs):
blacklist = 'blacklist.txt'
remove_blacklisted(blacklist, instances)
baelish = find_instance_by_name(instances, 'Petyr Baelish')
maybe_add_or_remove_baelish(instances, baelish)
while not try_run_game(instances, num_runs = num_runs, blacklist = blacklist):
print "Restarting!"
maybe_add_or_remove_baelish(instances, baelish)
print "Done!"
if __name__ == '__main__':
param = sys.argv[1] if len(sys.argv) >= 2 else None
if param == 'get':
instances = init_game(save=True, force_update=True)
elif param == 'run':
instances = init_game(save=False, force_update=False)
num_runs = 50
if len(sys.argv) == 3:
num_runs = int(sys.argv[2])
run_game(instances, num_runs)
else:
self_name = os.path.basename(__file__)
print "usage:"
print "To get the latest code: 'python %s get'" % self_name
print "To run the submissions: 'python %s run <optional num_runs>'" % self_name