no this isn't about alexandria ocasio-cortez

init and first puzzle

Changed files
+386
cmd
internal
+100
cmd/one/main.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "strconv" 6 + "strings" 7 + 8 + "tangled.org/evan.jarrett.net/aoc2025/internal/puzzle" 9 + ) 10 + 11 + type DayOne struct { 12 + rotations []int 13 + } 14 + 15 + func (d *DayOne) ParseInput(input string) error { 16 + for line := range strings.SplitSeq(strings.TrimSpace(input), "\n") { 17 + line = strings.TrimSpace(line) 18 + if line == "" { 19 + continue 20 + } 21 + line = strings.Replace(strings.Replace(line, "L", "-", 1), "R", "", 1) 22 + n, err := strconv.Atoi(line) 23 + if err != nil { 24 + return fmt.Errorf("invalid rotation: %s, %w", line, err) 25 + } 26 + d.rotations = append(d.rotations, n) 27 + } 28 + return nil 29 + } 30 + 31 + func (d *DayOne) Part1() (int, error) { 32 + position := 50 33 + zeroCount := 0 34 + fmt.Printf("The dial starts by pointing at %d.\n", position) 35 + 36 + for _, rot := range d.rotations { 37 + newPosition, _, err := Rotate(position, rot) 38 + if err != nil { 39 + return 0, err 40 + } 41 + fmt.Printf("The dial is rotated %d to point at %d.\n", rot, newPosition) 42 + position = newPosition 43 + if position == 0 { 44 + zeroCount++ 45 + } 46 + } 47 + 48 + fmt.Printf("Final position: %d\n", position) 49 + return zeroCount, nil 50 + } 51 + 52 + func (d *DayOne) Part2() (int, error) { 53 + position := 50 54 + zeroCount := 0 55 + fmt.Printf("The dial starts by pointing at %d.\n", position) 56 + 57 + for _, rot := range d.rotations { 58 + newPosition, passes, err := Rotate(position, rot) 59 + if err != nil { 60 + return 0, err 61 + } 62 + fmt.Printf("The dial is rotated %d to point at %d.", rot, newPosition) 63 + if passes > 0 { 64 + if newPosition != 0 || passes > 2 { 65 + fmt.Printf(" during this rotation, it points at 0 %d times.", passes) 66 + } 67 + zeroCount += passes 68 + } 69 + fmt.Println() 70 + fmt.Printf("count is now: %d\n", zeroCount) 71 + position = newPosition 72 + } 73 + 74 + fmt.Printf("Final position: %d\n", position) 75 + return zeroCount, nil 76 + } 77 + 78 + func Rotate(position int, amount int) (newPosition int, zeroCount int, err error) { 79 + if position < 0 || position > 99 { 80 + return 0, 0, fmt.Errorf("position must be between 0 and 99, got %d", position) 81 + } 82 + 83 + end := position + amount 84 + newPosition = ((end % 100) + 100) % 100 85 + 86 + if amount < 0 && end <= 0 { 87 + zeroCount = -end / 100 88 + if position != 0 { 89 + zeroCount++ 90 + } 91 + } else if amount > 0 && end >= 100 { 92 + zeroCount = end / 100 93 + } 94 + 95 + return newPosition, zeroCount, nil 96 + } 97 + 98 + func main() { 99 + puzzle.Run(1, &DayOne{}) 100 + }
+124
cmd/one/main_test.go
··· 1 + package main 2 + 3 + import ( 4 + "testing" 5 + ) 6 + 7 + const testInput = `L68 8 + L30 9 + R48 10 + L5 11 + R60 12 + L55 13 + L1 14 + L99 15 + R14 16 + L82` 17 + 18 + func TestPart1(t *testing.T) { 19 + d := &DayOne{} 20 + if err := d.ParseInput(testInput); err != nil { 21 + t.Fatalf("ParseInput failed: %v", err) 22 + } 23 + 24 + got, err := d.Part1() 25 + if err != nil { 26 + t.Fatalf("Part1 failed: %v", err) 27 + } 28 + 29 + want := 3 30 + if got != want { 31 + t.Errorf("Part1() = %d, want %d", got, want) 32 + } 33 + } 34 + 35 + func TestPart2(t *testing.T) { 36 + d := &DayOne{} 37 + if err := d.ParseInput(testInput); err != nil { 38 + t.Fatalf("ParseInput failed: %v", err) 39 + } 40 + 41 + got, err := d.Part2() 42 + if err != nil { 43 + t.Fatalf("Part2 failed: %v", err) 44 + } 45 + 46 + want := 6 47 + if got != want { 48 + t.Errorf("Part2() = %d, want %d", got, want) 49 + } 50 + } 51 + 52 + func TestPart2LargeRotations(t *testing.T) { 53 + tests := []struct { 54 + input string 55 + want int 56 + }{ 57 + {"R555", 6}, 58 + {"L432", 4}, 59 + {"L555", 6}, 60 + } 61 + 62 + for _, tt := range tests { 63 + t.Run(tt.input, func(t *testing.T) { 64 + d := &DayOne{} 65 + if err := d.ParseInput(tt.input); err != nil { 66 + t.Fatalf("ParseInput failed: %v", err) 67 + } 68 + 69 + got, err := d.Part2() 70 + if err != nil { 71 + t.Fatalf("Part2 failed: %v", err) 72 + } 73 + 74 + if got != tt.want { 75 + t.Errorf("Part2(%s) = %d, want %d", tt.input, got, tt.want) 76 + } 77 + }) 78 + } 79 + } 80 + 81 + func TestRotate(t *testing.T) { 82 + tests := []struct { 83 + name string 84 + position int 85 + amount int 86 + wantPos int 87 + wantCount int 88 + }{ 89 + // Starting at 0, no crossing 90 + {"from 0, R50", 0, 50, 50, 0}, 91 + {"from 0, L50", 0, -50, 50, 0}, 92 + // Starting at 0, multiple rotations (end on 0, passed through once) 93 + {"from 0, R200", 0, 200, 0, 2}, // pass 100, land 200 94 + {"from 0, L200", 0, -200, 0, 2}, // pass -100, land -200 95 + // Starting at 0, single rotation ending elsewhere 96 + {"from 0, R150", 0, 150, 50, 1}, // pass 100 97 + {"from 0, L150", 0, -150, 50, 1}, // pass -100 98 + // Starting at 50, landing on 0 99 + {"from 50, R50", 50, 50, 0, 1}, // land 100 100 + {"from 50, L50", 50, -50, 0, 1}, // land 0 101 + // Starting at 50, no crossing 102 + {"from 50, R40", 50, 40, 90, 0}, 103 + {"from 50, L40", 50, -40, 10, 0}, 104 + // Starting at 50, large rotations (pass through multiple times) 105 + {"from 50, R555", 50, 555, 5, 6}, // passes 100,200,300,400,500,600, lands on 5 106 + {"from 50, L432", 50, -432, 18, 4}, // passes 0,-100,-200,-300, lands on 18 107 + } 108 + 109 + for _, tt := range tests { 110 + t.Run(tt.name, func(t *testing.T) { 111 + gotPos, gotCount, err := Rotate(tt.position, tt.amount) 112 + if err != nil { 113 + t.Fatalf("Rotate failed: %v", err) 114 + } 115 + 116 + if gotPos != tt.wantPos { 117 + t.Errorf("Rotate(%d, %d) position = %d, want %d", tt.position, tt.amount, gotPos, tt.wantPos) 118 + } 119 + if gotCount != tt.wantCount { 120 + t.Errorf("Rotate(%d, %d) count = %d, want %d", tt.position, tt.amount, gotCount, tt.wantCount) 121 + } 122 + }) 123 + } 124 + }
+3
go.mod
··· 1 + module tangled.org/evan.jarrett.net/aoc2025 2 + 3 + go 1.24.10
+44
internal/input.go
··· 1 + package internal 2 + 3 + import ( 4 + "fmt" 5 + "io" 6 + "net/http" 7 + "os" 8 + ) 9 + 10 + // GetInput fetches the Advent of Code input for the specified day. 11 + // Requires AOC_SESSION environment variable to be set with your session cookie. 12 + func GetInput(day int) (string, error) { 13 + session := os.Getenv("AOC_SESSION") 14 + if session == "" { 15 + return "", fmt.Errorf("AOC_SESSION environment variable not set") 16 + } 17 + 18 + url := fmt.Sprintf("https://adventofcode.com/2025/day/%d/input", day) 19 + 20 + req, err := http.NewRequest("GET", url, nil) 21 + if err != nil { 22 + return "", fmt.Errorf("failed to create request: %w", err) 23 + } 24 + 25 + req.AddCookie(&http.Cookie{Name: "session", Value: session}) 26 + 27 + client := &http.Client{} 28 + resp, err := client.Do(req) 29 + if err != nil { 30 + return "", fmt.Errorf("failed to fetch input: %w", err) 31 + } 32 + defer resp.Body.Close() 33 + 34 + if resp.StatusCode != http.StatusOK { 35 + return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) 36 + } 37 + 38 + body, err := io.ReadAll(resp.Body) 39 + if err != nil { 40 + return "", fmt.Errorf("failed to read response body: %w", err) 41 + } 42 + 43 + return string(body), nil 44 + }
+7
internal/puzzle/puzzle.go
··· 1 + package puzzle 2 + 3 + type Puzzle[T1 any, T2 any] interface { 4 + ParseInput(input string) error 5 + Part1() (T1, error) 6 + Part2() (T2, error) 7 + }
+31
internal/puzzle/runner.go
··· 1 + package puzzle 2 + 3 + import ( 4 + "fmt" 5 + "log" 6 + 7 + "tangled.org/evan.jarrett.net/aoc2025/internal" 8 + ) 9 + 10 + func Run[T1 any, T2 any](day int, p Puzzle[T1, T2]) { 11 + input, err := internal.GetInput(day) 12 + if err != nil { 13 + log.Fatalf("Failed to get input: %v", err) 14 + } 15 + 16 + if err := p.ParseInput(input); err != nil { 17 + log.Fatalf("Failed to parse input: %v", err) 18 + } 19 + 20 + result1, err := p.Part1() 21 + if err != nil { 22 + log.Fatalf("Failed to solve part 1: %v", err) 23 + } 24 + fmt.Printf("Part 1: %v\n", result1) 25 + 26 + result2, err := p.Part2() 27 + if err != nil { 28 + log.Fatalf("Failed to solve part 2: %v", err) 29 + } 30 + fmt.Printf("Part 2: %v\n", result2) 31 + }
+77
newday.sh
··· 1 + #!/bin/bash 2 + 3 + set -e 4 + 5 + if [ -z "$1" ]; then 6 + echo "Usage: $0 <day>" 7 + echo "Example: $0 5" 8 + exit 1 9 + fi 10 + 11 + DAY=$1 12 + 13 + if ! [[ "$DAY" =~ ^[0-9]+$ ]] || [ "$DAY" -lt 1 ] || [ "$DAY" -gt 12 ]; then 14 + echo "Error: Day must be a number between 1 and 12" 15 + exit 1 16 + fi 17 + 18 + declare -a WORDS=("" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" 19 + "eleven" "twelve") 20 + 21 + DAY_WORD=${WORDS[$DAY]} 22 + 23 + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 24 + CMD_DIR="$SCRIPT_DIR/cmd/$DAY_WORD" 25 + 26 + # Capitalize first letter for struct name 27 + DAY_WORD_CAP="$(echo "${DAY_WORD:0:1}" | tr '[:lower:]' '[:upper:]')${DAY_WORD:1}" 28 + 29 + # Open browser to puzzle page 30 + URL="https://adventofcode.com/2025/day/$DAY" 31 + if command -v xdg-open &> /dev/null; then 32 + xdg-open "$URL" 33 + elif command -v open &> /dev/null; then 34 + open "$URL" 35 + elif command -v start &> /dev/null; then 36 + start "$URL" 37 + else 38 + echo "Could not detect browser opener. Please visit: $URL" 39 + fi 40 + 41 + # Create directory and main.go 42 + if [ -d "$CMD_DIR" ]; then 43 + echo "Directory $CMD_DIR already exists" 44 + else 45 + mkdir -p "$CMD_DIR" 46 + cat > "$CMD_DIR/main.go" << EOF 47 + package main 48 + 49 + import ( 50 + "tangled.org/evan.jarrett.net/aoc2025/internal/puzzle" 51 + ) 52 + 53 + type Day${DAY_WORD_CAP} struct { 54 + // parsed input fields 55 + } 56 + 57 + func (d *Day${DAY_WORD_CAP}) ParseInput(input string) error { 58 + // TODO: parse input 59 + return nil 60 + } 61 + 62 + func (d *Day${DAY_WORD_CAP}) Part1() (int, error) { 63 + // TODO: solve part 1 64 + return 0, nil 65 + } 66 + 67 + func (d *Day${DAY_WORD_CAP}) Part2() (int, error) { 68 + // TODO: solve part 2 69 + return 0, nil 70 + } 71 + 72 + func main() { 73 + puzzle.Run($DAY, &Day${DAY_WORD_CAP}{}) 74 + } 75 + EOF 76 + echo "Created $CMD_DIR/main.go" 77 + fi