diff --git a/Makefile b/Makefile index f5eeb62..a630d5b 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,12 @@ all: venv install # Create virtual environment if it doesn't exist venv: - @echo "Creating virtual environment..." @if [ ! -d $(VENV_DIR) ]; then \ + echo "Creating virtual environment..."; \ python3 -m venv $(VENV_DIR); \ fi + @echo "Source virtualenv with:" + @echo "source $(VENV_DIR)/bin/activate" install: venv @$(PYTHON) -m pip install --upgrade pip @@ -23,4 +25,3 @@ install: venv clean: @echo "Removing virtual environment..." @rm -rf $(VENV_DIR) - diff --git a/README.md b/README.md index de5e693..dc96036 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,23 @@ $ pip install -e . ### Run a single day ```shell -$ aoc +$ aoc run ``` ### Run a whole year ```shell -$ aoc +$ aoc run ``` +## Run all years + +```shell +$ aoc run all +``` + +## Init a new day + +```shell +$ aoc init +``` diff --git a/adventofcode/2019/day13/__init__.py b/adventofcode/2019/day13/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/adventofcode/2019/day13/day13.py b/adventofcode/2019/day13/day13.py index 2f9d1d2..c2206ef 100644 --- a/adventofcode/2019/day13/day13.py +++ b/adventofcode/2019/day13/day13.py @@ -1,14 +1,11 @@ #!/usr/bin/env python3 -# TODO replace PYTHONPATH hack with a proper solution, like making intcode an -# installed module https://stackoverflow.com/a/50194143 import sys import time -from itertools import zip_longest -from pathlib import Path -from collections import defaultdict, Counter from dataclasses import dataclass +from itertools import zip_longest + +from adventofcode.intcode import Interpreter, interpret_intcode -from adventofcode.intcode import interpret_intcode, Interpreter @dataclass class State: @@ -28,7 +25,7 @@ def grouper(n, iterable): def part1(program): interpreter = interpret_intcode(program) - game = { (x, y): tile for x, y, tile in grouper(3, interpreter.stdout) } + game = {(x, y): tile for x, y, tile in grouper(3, interpreter.stdout)} print("Part 1: ", len([x for x in game.values() if x == 2])) @@ -39,11 +36,10 @@ def parse_map(output): if (x, y) == (-1, 0): score = tile else: - grid[x,y] = tile + grid[x, y] = tile paddle = next((k for k, v in grid.items() if v == 3), None) ball = next((k for k, v in grid.items() if v == 4), None) - bounds = (max(x for x, y in grid.keys()), - max(y for x, y in grid.keys())) + bounds = (max(x for x, y in grid.keys()), max(y for x, y in grid.keys())) return grid, bounds, score, paddle, ball @@ -64,7 +60,7 @@ def update_state(state: State, stdout): else: # merge grid for (x, y), v in grid.items(): - state.grid[x,y] = v + state.grid[x, y] = v return state @@ -77,7 +73,7 @@ def part2(program): state = update_state(state, interpreter.stdout) if state.stopped: break - #print_map(state) + # print_map(state) interpreter.stdout.clear() paddle_x, ball_x = state.paddle_pos[0], state.ball_pos[0] if paddle_x < ball_x: # move right @@ -91,16 +87,16 @@ def part2(program): def print_map(state): clear = "\033[2J" - tiles = { 0: " ", 1: "#", 2: "~", 3: "_", 4: "O" } + tiles = {0: " ", 1: "#", 2: "~", 3: "_", 4: "O"} max_x, max_y = state.bounds print(clear) for y in range(max_y + 1): l = [] for x in range(max_x + 1): - tile = state.grid[x,y] + tile = state.grid[x, y] l.append(tiles[tile]) print("".join(l)) - time.sleep(1/60) + time.sleep(1 / 60) def main(inp): @@ -108,9 +104,10 @@ def main(inp): part1(program) part2(program) + if __name__ == "__main__": import sys + infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt" with open(infile) as raw_input: main(raw_input) - diff --git a/adventofcode/aoc.py b/adventofcode/aoc.py index 5750be0..3154174 100644 --- a/adventofcode/aoc.py +++ b/adventofcode/aoc.py @@ -1,29 +1,6 @@ import argparse -from pathlib import Path -from adventofcode.helper import ( - MAX_YEAR, - MIN_YEAR, - get_auth, - get_input_file, - get_max_day, - run, - run_all, -) - -TEMPLATE = """#!/usr/bin/env python3 -import fileinput - - -def main(inp): - for l in inp: - print(l) - - -if __name__ == '__main__': - lines = [x.rstrip() for x in fileinput.input()] - main(lines) -""" +from adventofcode.helper import init, run, run_all def year_or_all(value): @@ -69,35 +46,7 @@ def main(): def handle_init(year, day): - if not MIN_YEAR <= year <= MAX_YEAR: - print(f"Invalid year: {year}. Must be between {MIN_YEAR} and {MAX_YEAR}.") - return - - max_day = get_max_day(year) - if not 1 <= day <= max_day: - print(f"Invalid day: {day}. Must be between 1 and {max_day}.") - return - - root = Path(__file__).parent - day_dir = root / str(year) / f"day{day}" - day_dir.mkdir(parents=True, exist_ok=True) - - script_path = day_dir / f"day{day}.py" - if not script_path.exists(): - script_path.write_text(TEMPLATE) - print(f"Created {script_path}") - else: - print(f"{script_path} already exists") - - input_path = day_dir / "input.txt" - if not input_path.exists(): - try: - get_auth() - res = get_input_file(year, day) - input_path.write_bytes(res.read()) - print(f"Downloaded {input_path}") - except Exception as e: - print(f"Could not download input: {e}", file=__import__("sys").stderr) + init(year, day) def handle_run(year, day): diff --git a/adventofcode/helper.py b/adventofcode/helper.py index e2f255e..3ee0060 100644 --- a/adventofcode/helper.py +++ b/adventofcode/helper.py @@ -9,6 +9,22 @@ from pathlib import Path MIN_YEAR = 2015 MAX_YEAR = 2025 ROOTPATH = Path(os.path.dirname(os.path.realpath(__file__))) +TEMPLATE = """#!/usr/bin/env python3 +import fileinput + + +def main(inp): + for l in inp: + print(l) + + +if __name__ == '__main__': + lines = [x.rstrip() for x in fileinput.input()] + main(lines) +""" + + +_auth = None def get_max_day(year): @@ -16,7 +32,16 @@ def get_max_day(year): return 25 if year < 2025 else 12 -_auth = None +def validate_day(day, year): + if not 1 <= day <= get_max_day(year): + return f"Invalid day: {day} for year {year}. Must be between 1 and {get_max_day(year)}" + return None + + +def validate_year(year): + if not MIN_YEAR <= year <= MAX_YEAR: + return f"Invalid year: {year}. Must be between {MIN_YEAR} and {MAX_YEAR}" + return None def _load_env_file(env_path: Path) -> dict: @@ -69,25 +94,56 @@ def resolve_paths(year, day): return script_path, input_path +def init(year, day): + if (err := validate_year(year)) is not None: + print(err) + return + + if (err := validate_day(day, year)) is not None: + print(err) + return + + path = ROOTPATH / Path(f"{year}/day{day}") + path.mkdir(parents=True, exist_ok=True) + + script_path, input_path = resolve_paths(year, day) + if script_path.exists(): + print(f"Script {script_path} already exists.") + else: + script_path.write_text(TEMPLATE) + print(f"Created {script_path}") + + if input_path.exists(): + print(f"Input {input_path} already exists.") + else: + try: + get_auth() + res = get_input_file(year, day) + input_path.write_bytes(res.read()) + print(f"Downloaded {input_path}") + except Exception as e: + print(f"Could not download input: {e}", file=sys.stderr) + + def run_all(): - for year in range(2015, MAX_YEAR + 1): + for year in range(MIN_YEAR, MAX_YEAR + 1): print(f"Running year {year}") run(year, None) def run(year, day): - if not MIN_YEAR <= year <= MAX_YEAR: - print(f"Invalid year {year}", file=sys.stderr) + if (err := validate_year(year)) is not None: + print(err, file=sys.stderr) exit(1) if day is not None: - if not 1 <= day <= get_max_day(year): - print(f"Invalid day {day}", file=sys.stderr) + if (err := validate_day(day, year)) is not None: + print(err, file=sys.stderr) exit(1) script_path, input_path = resolve_paths(year, day) if not script_path.exists(): - print(f"Invalid day {day}", file=sys.stderr) + print(f"Script file {script_path} does not exist", file=sys.stderr) exit(1) if not input_path.exists(): print(f"Downloading input file {input_path}") @@ -98,7 +154,7 @@ def run(year, day): run_day(script_path, input_path) else: - for day in range(1, 26): + for day in range(1, get_max_day(year) + 1): script_path, input_path = resolve_paths(year, day) if script_path.exists(): if not input_path.exists():