Skip to content

Commit e52cb94

Browse files
committed
refactor by simplying interface
Signed-off-by: Rodolfo Sanchez <me@dolfo.codes>
1 parent 2db6b92 commit e52cb94

27 files changed

Lines changed: 507 additions & 1552 deletions

README.md

Lines changed: 62 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,111 +3,106 @@
33
[![Go Reference](https://pkg.go.dev/badge/github.com/dolfolife/aoctl#section-readme.svg)](https://pkg.go.dev/github.com/dolfolife/aoctl#section-readme)
44
[![codecov](https://codecov.io/github/dolfolife/aoctl/graph/badge.svg?token=GTFZX1J2WX)](https://codecov.io/github/dolfolife/aoctl)
55

6-
This is a personal project for myself to learn more about writing CLI tools in GoLang. The idea behind this project is to help me solve the [Advent of Code](adventofcode.com/) since I have noticed some patterns while solving them.
6+
A simple CLI tool to organize and solve [Advent of Code](https://adventofcode.com/) problems. It handles downloading inputs, generating boilerplate code, and running your solutions, so you can focus on the logic.
77

8-
> Disclaimer: This is yet another CLI that solves a common problem and there are probably `n` solutions out there. My motivation is not only to provide a CLI but to learn how to write, develop, release, and test them.
8+
## Features
99

10-
I hope you enjoy it and feel free to help me understand the best practices of writing CLI tools or GoLang packages by writing an issue or using the [CONTRIBUTING.md](./CONTRIBUTING.md) to contribute. PRs are welcome.
10+
- **Project Initialization**: Sets up a clean workspace for your AoC solutions.
11+
- **Synchronization**: Downloads puzzle descriptions (README.md) and inputs for all available days.
12+
- **Code Generation**: Generates simple Go boilerplate for each day.
13+
- **Zero Friction**: No complex interfaces to implement. Just write `Part1` and `Part2` functions.
1114

12-
## Advent Of Code Series
15+
## Installation
1316

14-
Advent of Code is a website made by Eric Wastl. I recommend watching [Advent of Code: Behind the Scenes](https://www.youtube.com/watch?v=CFWuwNDOnIo&ab_channel=CodingTech).
15-
16-
## The Why
17-
18-
Advent of Code application only cares about your solution, but there is no way to link your code and the problem at hand. The main idea of this tool is to give you a link between the application of [adventofcode.com](https://adventofcode.com/) and your working space.
19-
20-
## Alternatives
21-
22-
I found [Advent Of Code Go](https://github.com/alexchao26/advent-of-code-go) and it inspired me to build my own tool and learn more GoLang.
23-
24-
# AoC CLI Documentation
25-
26-
## Pre-requisite
27-
28-
You need your session ID from the Advent of Code website. I opened an issue for this: [Session ID is a manual process issue](https://github.com/dolfolife/aoctl/issues/1).
29-
30-
## Puzzles
31-
32-
The Advent of Code puzzles have two parts. Each part has a single string input and a single output.
17+
```bash
18+
go install github.com/dolfolife/aoctl@latest
19+
```
3320

34-
By that, we have to create an interface that allows us to run each solution against the input and submit the solution to the server of Advent of Code.
21+
## Usage
3522

36-
## Commands
23+
### 1. Initialize Project
3724

38-
### Init
39-
Initialize the aoc project in your local machine.
25+
Start by creating a new directory for your Advent of Code solutions and initializing it:
4026

4127
```bash
42-
aoc init <path>
28+
mkdir my-aoc-solutions
29+
cd my-aoc-solutions
30+
aoctl init .
4331
```
4432

45-
### Version
46-
Print current version of the cli
33+
### 2. Configure Session
34+
35+
To download inputs, you need to provide your Advent of Code session cookie. You can find this in your browser's developer tools (Application -> Cookies) when logged into adventofcode.com.
4736

4837
```bash
49-
aoc version
38+
aoctl session --session <your-session-cookie>
5039
```
5140

52-
#### Options
53-
54-
```
55-
--path -p Path to initialize the project
56-
```
41+
### 3. Synchronize Puzzles
5742

58-
### Puzzle
59-
Get The puzzle information with the option to save it locally in the repository.
43+
Download the puzzles and generate the boilerplate code for the current year (or previous years).
6044

6145
```bash
62-
aoc puzzles [OPTIONS]
46+
aoctl synchronize
6347
```
6448

65-
#### Options
49+
This will create a directory structure like this:
6650

6751
```
68-
-s --session (required) Advent of Code Session. You can get this in the Cookies of the website.
52+
events/
53+
2024/
54+
01/
55+
README.md # The puzzle description
56+
input/
57+
input.txt # The puzzle input
58+
main.go # Entry point to run your solution
59+
solution.go # Where you write your code
60+
solution_test.go # Tests for your solution
6961
```
7062

71-
```
72-
-d --day (required) Day of the puzzle you want to get.
73-
```
63+
### 4. Solve the Puzzle
7464

75-
```
76-
-y --year (required) Year of the puzzle you want to get.
77-
```
65+
Open `events/2024/01/solution.go` and implement `Part1` and `Part2`.
7866

79-
```
80-
--sync (optional) To save the puzzle locally at `<year>/<day>/puzzle.md
67+
```go
68+
package main
69+
70+
func Part1(input string) (string, error) {
71+
// Your logic here
72+
return "answer", nil
73+
}
74+
75+
func Part2(input string) (string, error) {
76+
// Your logic here
77+
return "answer", nil
78+
}
8179
```
8280

83-
### test
84-
Locally test your answers. This relies that each `<year>/<day>` solution has its own tests.
81+
### 5. Run and Test
8582

86-
> not yet implemented
83+
Run your solution:
8784

8885
```bash
89-
aoc test [OPTIONS]
86+
cd events/2024/01
87+
go run .
9088
```
9189

92-
### submit
93-
Submit your answer to the Advent of Code.
94-
95-
> not yet implemented
90+
Run tests:
9691

9792
```bash
98-
aoc submit [OPTIONS]
93+
go test .
9994
```
10095

101-
#### Options
96+
## Helper Functions
10297

103-
```
104-
-s --session (required) Advent of Code Session. You can get this in the Cookies of the website.
105-
```
98+
The `pkg/puzzle` package provides a simple helper to read the input file:
10699

107-
```
108-
-d --day (required) Day of the puzzle you want to get.
109-
```
100+
```go
101+
import "github.com/dolfolife/aoctl/pkg/puzzle"
110102

103+
input, err := puzzle.ReadInput("input/input.txt")
111104
```
112-
-y --year (required) Year of the puzzle you want to get.
113-
```
105+
106+
## Contributing
107+
108+
PRs are welcome! Please check [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.

examples/golang/2016/go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/dolfolife/aoctl/examples/golang/2016
22

3-
go 1.21
3+
go 1.24.0
44

55
require (
66
github.com/dolfolife/aoctl v0.0.0-20230829133931-d2eb61e5a257
@@ -9,8 +9,9 @@ require (
99

1010
require (
1111
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/joho/godotenv v1.5.1 // indirect
1213
github.com/pmezard/go-difflib v1.0.0 // indirect
13-
golang.org/x/net v0.13.0 // indirect
14+
golang.org/x/net v0.47.0 // indirect
1415
gopkg.in/yaml.v3 v3.0.1 // indirect
1516
)
1617

examples/golang/2016/go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
4+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
35
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
46
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
57
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
68
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7-
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
8-
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
9+
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
10+
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
911
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1012
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1113
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

examples/golang/2022/01/main.go

Lines changed: 12 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,26 @@ package main
22

33
import (
44
"fmt"
5-
"strconv"
6-
"strings"
7-
"io/ioutil"
8-
"os"
9-
"path"
10-
"path/filepath"
11-
)
12-
13-
func gmap[T any, M any](data []T, f func(T) (M, error)) ([]M, error) {
14-
15-
mapped := make([]M, len(data))
16-
17-
for i, e := range data {
18-
v, err := f(e)
19-
if err != nil {
20-
return nil, err
21-
}
22-
mapped[i] = v
23-
}
24-
25-
return mapped, nil
26-
}
5+
"log"
276

28-
func fromBagToCalories(bag string) (int, error) {
29-
bc, err := gmap(strings.Split(bag, "\n"), strconv.Atoi)
7+
"github.com/dolfolife/aoctl/pkg/puzzle"
8+
)
309

10+
func main() {
11+
input, err := puzzle.ReadInput("input/input.txt")
3112
if err != nil {
32-
return 0, err
13+
log.Fatal(err)
3314
}
3415

35-
s := 0
36-
37-
for _, a := range bc {
38-
s = s + a
39-
}
40-
41-
return s, nil
42-
}
43-
44-
func part1(bags []int) int {
45-
46-
maxb := 0
47-
for i := 0; i < len(bags); i++ {
48-
if maxb < bags[i] {
49-
maxb = bags[i]
50-
}
51-
}
52-
53-
return maxb
54-
}
55-
56-
func part2(bags []int) int {
57-
58-
maxb1, maxb2, maxb3 := 0, 0, 0
59-
for i := 0; i < len(bags); i++ {
60-
if bags[i] > maxb1 {
61-
maxb3 = maxb2
62-
maxb2 = maxb1
63-
maxb1 = bags[i]
64-
} else if bags[i] > maxb2 {
65-
maxb3 = maxb2
66-
maxb2 = bags[i]
67-
} else if bags[i] > maxb3 {
68-
maxb3 = bags[i]
69-
}
70-
}
71-
72-
return (maxb1 + maxb2 + maxb3)
73-
}
74-
75-
func main() {
76-
folder, _ := os.Executable()
77-
inputfile := filepath.Join(path.Dir(folder), "input.txt" )
78-
fmt.Println(inputfile)
79-
b, err := ioutil.ReadFile(inputfile)
16+
p1, err := Part1(input)
8017
if err != nil {
81-
fmt.Println("error processing the file")
18+
log.Fatal(err)
8219
}
20+
fmt.Printf("Part 1: %s\n", p1)
8321

84-
e := strings.Split(string(b), "\n\n")
85-
bags, err := gmap(e[:len(e)-1], fromBagToCalories)
86-
22+
p2, err := Part2(input)
8723
if err != nil {
88-
fmt.Println(err)
24+
log.Fatal(err)
8925
}
90-
91-
fmt.Println("part 1 => ", part1(bags))
92-
fmt.Println("part 2 => ", part2(bags))
26+
fmt.Printf("Part 2: %s\n", p2)
9327
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
)
8+
9+
func gmap[T any, M any](data []T, f func(T) (M, error)) ([]M, error) {
10+
mapped := make([]M, len(data))
11+
for i, e := range data {
12+
v, err := f(e)
13+
if err != nil {
14+
return nil, err
15+
}
16+
mapped[i] = v
17+
}
18+
return mapped, nil
19+
}
20+
21+
func fromBagToCalories(bag string) (int, error) {
22+
bc, err := gmap(strings.Split(bag, "\n"), strconv.Atoi)
23+
if err != nil {
24+
return 0, err
25+
}
26+
s := 0
27+
for _, a := range bc {
28+
s = s + a
29+
}
30+
return s, nil
31+
}
32+
33+
func Part1(input string) (string, error) {
34+
e := strings.Split(input, "\n\n")
35+
// The original code did e[:len(e)-1] which suggests the last element might be empty due to trailing newline
36+
// We should check if the last element is empty
37+
if len(e) > 0 && strings.TrimSpace(e[len(e)-1]) == "" {
38+
e = e[:len(e)-1]
39+
}
40+
41+
bags, err := gmap(e, fromBagToCalories)
42+
if err != nil {
43+
return "", err
44+
}
45+
46+
maxb := 0
47+
for i := 0; i < len(bags); i++ {
48+
if maxb < bags[i] {
49+
maxb = bags[i]
50+
}
51+
}
52+
return fmt.Sprint(maxb), nil
53+
}
54+
55+
func Part2(input string) (string, error) {
56+
e := strings.Split(input, "\n\n")
57+
if len(e) > 0 && strings.TrimSpace(e[len(e)-1]) == "" {
58+
e = e[:len(e)-1]
59+
}
60+
61+
bags, err := gmap(e, fromBagToCalories)
62+
if err != nil {
63+
return "", err
64+
}
65+
66+
maxb1, maxb2, maxb3 := 0, 0, 0
67+
for i := 0; i < len(bags); i++ {
68+
if bags[i] > maxb1 {
69+
maxb3 = maxb2
70+
maxb2 = maxb1
71+
maxb1 = bags[i]
72+
} else if bags[i] > maxb2 {
73+
maxb3 = maxb2
74+
maxb2 = bags[i]
75+
} else if bags[i] > maxb3 {
76+
maxb3 = bags[i]
77+
}
78+
}
79+
return fmt.Sprint(maxb1 + maxb2 + maxb3), nil
80+
}

0 commit comments

Comments
 (0)