Skip to content

Commit

Permalink
Initial Commit - 2024 day 6, 7, 11, 12, 14 and 17
Browse files Browse the repository at this point in the history
  • Loading branch information
Spacerulerwill committed Jan 3, 2025
0 parents commit c6f5ced
Show file tree
Hide file tree
Showing 20 changed files with 959 additions and 0 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AOC_SESSION_COOKIE=your_aoc_session_cookie_here
18 changes: 18 additions & 0 deletions .github/workflows/mypy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: mypy

on: [push, pull_request]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.12.3'
- name: Install dependencies
run: |
pip install .
- name: Run
run: |
mypy ./
21 changes: 21 additions & 0 deletions .github/workflows/ruff.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: ruff

on: [push, pull_request]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.12.3'
- name: Install dependencies
run: |
pip install .
- name: Format
run: |
ruff format .
- name: Lint
run: |
ruff check .
174 changes: 174 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# PyPI configuration file
.pypirc

.ruff_cache/
input.txt*
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Advent of Code solutions
```bash
git clone https://github.com/SpaceCases/aoc # Clone repository to local machine
cd aoc # Move into directory
python -m venv env # Create the virtual environment
source env/bin/activate # Activate virtual environment
python -m pip install . # Install dependencies
mv .env.example .env # Rename .env.example to .env
```
Then use your preferred text editor of choice to add your session cookie to the `.env`. Once that is setup, you can run the bot:
```bash
python aoc.py run <year> <day> <part> # Run a solution
```
152 changes: 152 additions & 0 deletions aoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import os
import time
import argparse
import requests
from contextlib import suppress
from typing import Literal
from dotenv import load_dotenv
from datetime import datetime
import importlib

PUZZLE_LOCATION = "puzzles"

PUZZLE_STUB = """from typing import Any
def puzzle(input: str) -> Any: ...
"""


def init_puzzle(year: int, day: int, part: int, force: bool) -> None:
folder_path = os.path.join(PUZZLE_LOCATION, f"Y{year}", f"D{day}")
# each year is a separate module
with suppress(FileExistsError):
with open(os.path.join(PUZZLE_LOCATION, f"Y{year}", "__init__.py"), "w"):
pass
puzzle_name = os.path.join(folder_path, f"p{part}.py")
os.makedirs(folder_path, exist_ok=True)

# Create or overwrite the puzzle file
try:
with open(puzzle_name, "x") as f:
f.write(PUZZLE_STUB)
except FileExistsError:
if force:
with open(puzzle_name, "w") as f:
f.write(PUZZLE_STUB)
print(f"Overwrote puzzle {year}/{day} part {part}")
else:
print(f"Puzzle {year}/{day} part {part} exists, use -f flag to overwrite")
return

print(f"Created puzzle {year}/{day} part {part}")


def run_puzzle(year: int, day: int, part: int, aoc_session_cookie: str) -> None:
input_path = os.path.join(PUZZLE_LOCATION, f"Y{year}", f"D{day}", "input.txt")
if not os.path.isfile(input_path):
cookies = {"session": aoc_session_cookie}
r = requests.get(
f"https://adventofcode.com/{year}/day/{day}/input", cookies=cookies
)
r.raise_for_status()
input = r.text
with open(input_path, "w+") as f:
f.write(input)
with open(input_path) as f:
input = f.read()
module = importlib.import_module(f"puzzles.Y{year}.D{day}.p{part}")

puzzle = getattr(module, "puzzle")
print(f"Running Advent of Code {year}, day {day}, part {part}")
start = time.perf_counter()
result = puzzle(input)
end = time.perf_counter()
elapsed_time = end - start
seconds = int(elapsed_time)
milliseconds = int((elapsed_time - seconds) * 1000)

print(f"Output:\n{result}")
print(f"Executed in {seconds}:{milliseconds:03}")


def run_driver() -> None:
# folder setup
os.makedirs(PUZZLE_LOCATION, exist_ok=True)
load_dotenv()
aoc_session_cookie = os.environ["AOC_SESSION_COOKIE"]

# Determine most recent AOC year
now = datetime.now()
current_year = now.year if now.month == 12 else now.year - 1

# Create the root parser
root_parser = argparse.ArgumentParser(
prog="aoc.py", description="Run advent of code solutions"
)
subparsers = root_parser.add_subparsers(required=True, dest="command")
# run command
run_parser = subparsers.add_parser("run", help="Run an advent of code solution")
# init command
init_parser = subparsers.add_parser(
"init", help="Generate boilerplate for a solution"
)
# both commands have the same arguments
for parser in (run_parser, init_parser):
parser.add_argument(
"year", type=int, help=f"which year (2015 - {current_year})"
)
parser.add_argument("day", type=int, help="which day (1 - 25 inclusive)")
parser.add_argument(
"part",
choices=["1", "2", "both"],
help="which part",
)
# init has an optional -f --force flag to overwrite existing files
init_parser.add_argument(
"-f", "--force", action="store_true", help="overwrite existing puzzle code"
)

# Parse the arguments
args = root_parser.parse_args()
command: Literal["run", "init"] = args.command

# Validate year range
year: int = args.year
if year < 2015 or year > current_year:
print(f"Year must be between 2015 and {current_year}.")
return

# Validate day range
day: int = args.day
if day < 1 or day > 25:
print("Day must be in range 1-25 inclusive.")
return

# Get parts
selected_part: Literal["1", "2", "both"] = args.part
if selected_part == "1":
parts = [1]
elif selected_part == "2":
parts = [2]
elif selected_part == "both":
parts = [1, 2]
else:
raise ValueError("Invalid part selection, report as a bug")

if command == "init":
force: bool = args.force
for part in parts:
init_puzzle(year, day, part, force)
elif command == "run":
for part in parts:
try:
run_puzzle(year, day, part, aoc_session_cookie)
except (ModuleNotFoundError, FileNotFoundError):
print(
f"Puzzle {year}/{day} part {part} not found - to create, run python aoc.py init {year} {day} {part}"
)


if __name__ == "__main__":
run_driver()
Loading

0 comments on commit c6f5ced

Please sign in to comment.