py-tools/tools/aoc.py
Stefan Harmuth 6b5d3de95b allow stopwatch to return averages
fix AOCDay._submit()
2021-12-28 21:11:07 +01:00

251 lines
8.8 KiB
Python

import os
import re
import time
import webbrowser
import requests
from bs4 import BeautifulSoup
from tools.datafiles import JSONFile
from tools.stopwatch import StopWatch
from typing import Any, Callable, Dict, List, Tuple, Type, Union
from .tools import get_script_dir
BASE_PATH = get_script_dir()
INPUTS_PATH = os.path.join(BASE_PATH, 'inputs')
class AOCDay:
year: int
day: int
input: List[str] # our input is always a list of str/lines
inputs: List[List[Tuple[Any, str]]]
part_func: List[Callable]
def __init__(self, year: int, day: int):
self.day = day
self.year = year
self.part_func = [self.part1, self.part2]
def part1(self) -> Any:
pass
def part2(self) -> Any:
pass
def runPart(self, part: int, verbose: bool = False, measure_runtime: bool = False, timeit_number: int = 50):
case_count = 0
for solution, input_file in self.inputs[part]:
exec_time = None
answer = None
self._loadInput(input_file)
if not measure_runtime or case_count < len(self.inputs[part]) - 1:
answer = self.part_func[part]()
else:
stopwatch = StopWatch()
for _ in range(timeit_number):
answer = self.part_func[part]()
stopwatch.stop()
exec_time = stopwatch.avg_string(timeit_number)
if solution is None:
printSolution(self.day, part + 1, answer, solution, case_count, exec_time)
if answer not in {u"", b"", None, b"None", u"None"}:
self._submit(part + 1, answer)
else:
if verbose or answer != solution:
printSolution(self.day, part + 1, answer, solution, case_count, exec_time)
if answer != solution:
return False
case_count += 1
if case_count == len(self.inputs[part]) and not verbose:
printSolution(self.day, part + 1, answer, exec_time=exec_time)
def run(self, parts: int = 3, verbose: bool = False, measure_runtime: bool = False, timeit_number: int = 50):
if parts & 1:
self.runPart(0, verbose, measure_runtime, timeit_number)
if parts & 2:
self.runPart(1, verbose, measure_runtime, timeit_number)
def _loadInput(self, filename):
file_path = os.path.join(INPUTS_PATH, filename)
if not os.path.exists(file_path):
self._downloadInput(file_path)
with open(os.path.join(INPUTS_PATH, filename)) as f:
self.input = f.read().splitlines()
def _downloadInput(self, filename: str):
# FIXME: implement wait time for current day before 06:00:00 ?
session_id = open(".session", "r").readlines()[0].strip()
response = requests.get(
"https://adventofcode.com/%d/day/%d/input" % (self.year, self.day),
cookies={'session': session_id}
)
if not response.ok:
print("FAILED to download input: (%s) %s" % (response.status_code, response.text))
return
with open(filename, "wb") as f:
f.write(response.content)
f.flush()
def _submit(self, part: int, answer: Any):
answer_cache = JSONFile("answer_cache.json", create=True)
print(answer_cache)
str_day = str(self.day)
str_part = str(part)
if str_day not in answer_cache:
answer_cache[str_day] = {}
if str_part not in answer_cache[str_day]:
answer_cache[str_day][str_part] = {
'wrong': [],
'correct': None
}
if answer in answer_cache[str_day][str_part]['wrong']:
print("Already tried %s. It was WRONG." % answer)
return
if answer_cache[str_day][str_part]['correct'] is not None:
if answer == answer_cache[str_day][str_part]['correct']:
print("Already submitted %s. It was CORRECT." % answer)
return
else:
print("Already submitted an answer, but another one")
print("CORRECT was: %s" % answer_cache[str_day][str_part]['correct'])
print("Your answer: %s" % answer)
return
print("Submitting %s as answer for %d part %d" % (answer, self.day, part))
session_id = open(".session", "r").readlines()[0].strip()
response = requests.post(
"https://adventofcode.com/%d/day/%d/answer" % (self.year, self.day),
cookies={'session': session_id},
data={'level': part, 'answer': answer}
)
if not response.ok:
print("Failed to submit answer: (%s) %s" % (response.status_code, response.text))
soup = BeautifulSoup(response.text, "html.parser")
message = soup.article.text
if "That's the right answer" in message:
answer_cache[str_day][str_part]['correct'] = answer
print("That's correct!")
webbrowser.open("https://adventofcode.com/%d/day/%d#part2" % (self.year, self.day))
elif "That's not the right answer" in message:
answer_cache[str_day][str_part]['wrong'].append(answer)
print("That's WRONG!")
elif "You gave an answer too recently" in message:
# WAIT and retry
wait_pattern = r"You have (?:(\d+)m )?(\d+)s left to wait"
try:
[(minutes, seconds)] = re.findall(wait_pattern, message)
except ValueError:
print("wait_pattern unable to find wait_time in:")
print(message)
return
seconds = int(seconds)
if minutes:
seconds *= int(minutes) * 60
print("TOO SOON. Waiting %d seconds until auto-retry." % seconds)
time.sleep(seconds)
self._submit(part, answer)
return
else:
print("I don't know what this means:")
print(message)
return
answer_cache.save()
def getInput(self) -> Union[str, List]:
if len(self.input) == 1:
return self.input[0]
else:
return self.input.copy()
def getInputListAsType(self, return_type: Type) -> List:
"""
get input as list casted to return_type, each line representing one list entry
"""
return [return_type(i) for i in self.input]
def getMultiLineInputAsArray(self, return_type: Type = None, join_char: str = None) -> List:
"""
get input for day x as 2d array, split by empty lines
"""
lines = self.input.copy()
lines.append('')
return_array = []
line_array = []
for line in lines:
if not line:
if join_char:
return_array.append(join_char.join(line_array))
else:
return_array.append(line_array)
line_array = []
continue
if return_type:
line_array.append(return_type(line))
else:
line_array.append(line)
return return_array
def getInputAsArraySplit(self, split_char: str = ',', return_type: Union[Type, List[Type]] = None) -> List:
"""
get input for day x with the lines split by split_char
if input has only one line, returns a 1d array with the values
if input has multiple lines, returns a 2d array (a[line][values])
"""
if len(self.input) == 1:
return splitLine(line=self.input[0], split_char=split_char, return_type=return_type)
else:
return_array = []
for line in self.input:
return_array.append(splitLine(line=line, split_char=split_char, return_type=return_type))
return return_array
def printSolution(day: int, part: int, solution: Any, test: Any = None, test_case: int = 0, exec_time: str = None):
if test is not None:
print(
"%s (TEST day%d/part%d/case%d): got '%s'; expected '%s'"
% ("OK" if test == solution else "FAIL", day, part, test_case, solution, test)
)
else:
print(
"Solution to day %s, part %s: %s"
% (
day,
part,
solution,
)
)
if exec_time:
print("Day %s, Part %s - Average run time: %s" % (day, part, exec_time))
def splitLine(line, split_char: str = ',', return_type: Union[Type, List[Type]] = None):
if split_char:
line = line.split(split_char)
if return_type is None:
return line
elif isinstance(return_type, list):
return [return_type[x](i) if len(return_type) > x else i for x, i in enumerate(line)]
else:
return [return_type(i) for i in line]