├── .gitignore
├── .vscode
└── launch.json
├── README.md
├── analysis_options.yaml
├── day_generator.dart
├── input
└── aoc01.txt
├── main.dart
├── pubspec.lock
├── pubspec.yaml
├── solutions
├── day01.dart
└── index.dart
├── tool
├── generic_day.dart
└── session_token.dart
└── utils
├── field.dart
├── index.dart
├── input_util.dart
└── parse_util.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | .dart_tool
2 | .dart_tool/package_config.json
3 | .dart_tool/aoc
4 | .packages
5 | build/
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "AdventOfCode-2024",
9 | "request": "launch",
10 | "type": "dart",
11 | "program": "main.dart"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # AdventOfCode-Starter-Dart
4 |
5 | This is a Starter project for [AdventOfCode](https://adventofcode.com/2024), written in `Dart`. Feel free to use it for your own adventures with the christmas-themed puzzles!
6 |
7 | ## How to use
8 |
9 | Feel free to fork this repository to use it as a starting point for your own solutions.
10 |
11 | ### Boilerplate Generation
12 |
13 | In the root of your directory, run
14 |
15 | ```console
16 | dart run day_generator.dart
17 | ```
18 |
19 | This will request your session token if it has not been stored previously. More info can be found in the [Session token](<#session-token>) section.
20 |
21 | This will create an input and test file and a solution file with all the needed boilerplate to have a quick start. It also adds the solution to the corresponding index file, so the solution get imported into `main` automatically.
22 |
23 | ### Session token
24 |
25 | When running the `day_generator.dart` script for the first time, you will be asked to provide your session token. This is needed to automatically download your input files. If you need to do this manually for any reason, you can find the instructions below.
26 |
27 | Please visit the [AdventOfCode](https://adventofcode.com) site and log in.
28 |
29 | After that, get your session token from the cookie:
30 |
31 | - Open DevTools (F12)
32 | - "Application" -> Cookies ->
33 | - Copy the value of the `session` cookie
34 |
35 | By default, the session token is stored in `.dart_tool/aoc/.session_token`. You can either store it there manually, or run the `day_generator.dart` script and paste it when prompted.
36 |
37 | ### Main
38 |
39 | **To add a new solution, all you have to do is add `DayXX()` to the `day` List.**
40 |
41 | Running main automatically prints either all your solutions, or just the last one, depending on your settings.
42 |
43 | It also measures the time it takes to run each solution, and prints it to the console.
44 |
45 | You can run the main file by running
46 |
47 | ```console
48 | dart run main.dart
49 | ```
50 |
51 | in the root of your directory.
52 |
53 | By default the main file will only show the last solution. If you want to see all of them, you can use the `-a` or `--all` flag.
54 | You can list all the command line arguments by using the `-h` or `--help` flag.
55 |
56 | ### Tests
57 |
58 | A test file is automatically generated for each day. It contains tests for both parts of the example and the real input.
59 |
60 | All you have to do is **fill out the variables given at the top of the test file.**
61 |
62 | ---
63 |
64 | ## Class Documentation
65 |
66 | Below you can find a short documentation of the classes and methods provided by this starter project.
67 |
68 | ### Naming conventions
69 |
70 | When using the Boilerplate generator, everything is done for you automatically. However, if you create a solution or input file by yourself: make sure it has a 2-digit number. Concretely, pad days 1-9 as `day01.dart` for solutions and `aoc01.txt` for input.
71 |
72 | ### Generic Day
73 |
74 | The abstract class all individual days subclass from. When constructed with the correct `day`, it automatically ready the corresponding input file and provides it with the `InputUtil`. To access it, just call `input` inside your class.
75 |
76 | ### Input Util
77 |
78 | Automatically reads the input files and provides different methods to parse it.
79 |
80 | - `.asString` to get the whole input as a single String
81 | - `.getPerLine()` splits on `\n` characters, returning a List with single lines as elements.
82 | - `.getPerWhitespace()` splits on `\s` and `\n`, essentially returning a List with all the single characters.
83 | - `.getBy(pattern)` lets you define your own split logic. It essentially calls Dart's native `.split(pattern)`
84 |
85 | ### Parse Util
86 |
87 | A place to store useful parsing operations, like creating a `List` from a `List`. There will be a lot of opportunities during AoC for you to extend this.
88 |
89 | ### Field Class
90 |
91 | A helper class for 2D data, as often present in AoC. Any data can be represented. For Integers specifically, there are convenience methods in `IntegerField`. For all available methods, have a look at the abundantly-documented code.
92 |
93 | ### Helper Packages
94 |
95 | **Tuple** enables operations on pairs/triplets etc of any type. Absolutely needed for most of the puzzles.
96 | **Collection** provides many methods for ...collections... Most importantly, a `groupBy` and a collection equality interface.
97 | **Quiver** is an awesome toolbox of helper methods for Dart. We mostly use `/iterables` (similar to Pythons `itertools`).
98 |
99 | ## Contributing
100 |
101 | Contributing is greatly appreciated, just fork this project and create a Pull Request, or open an Issue!
102 |
103 | # Happy Holidays
104 |
105 |
106 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.5.1.0.yaml
2 |
3 | analyzer:
4 | exclude:
5 |
6 | linter:
7 | rules:
8 | flutter_style_todos: false
9 | avoid_print: false
10 |
--------------------------------------------------------------------------------
/day_generator.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 |
4 | import 'tool/session_token.dart';
5 |
6 | /// Small Program to be used to generate files and boilerplate for a given day.\
7 | /// Call with `dart run day_generator.dart `
8 | void main(List args) async {
9 | const year = '2024';
10 | final session = getSessionToken();
11 |
12 | if (args.length > 1) {
13 | print('Please call with: ');
14 | return;
15 | }
16 |
17 | String? dayNumber;
18 |
19 | // input through terminal
20 | if (args.isEmpty) {
21 | print('Please enter a day for which to generate files');
22 | final input = stdin.readLineSync();
23 | if (input == null) {
24 | print('No input given, exiting');
25 | return;
26 | }
27 | // pad day number to have 2 digits
28 | dayNumber = int.parse(input).toString().padLeft(2, '0');
29 | // input from CLI call
30 | } else {
31 | dayNumber = int.parse(args[0]!).toString().padLeft(2, '0');
32 | }
33 |
34 | // inform user
35 | print('Creating day: $dayNumber');
36 |
37 | // Create lib file
38 | final dayFileName = 'day$dayNumber.dart';
39 | unawaited(
40 | File('solutions/$dayFileName')
41 | .create(recursive: true)
42 | .then((file) => file.writeAsString(_dayTemplate(dayNumber!))),
43 | );
44 |
45 | // Create test file
46 | final testFileName = 'day${dayNumber}_test.dart';
47 | unawaited(
48 | File('test/$testFileName')
49 | .create(recursive: true)
50 | .then((file) => file.writeAsString(_testTemplate(dayNumber!))),
51 | );
52 |
53 | final exportFile = File('solutions/index.dart');
54 | final exports = exportFile.readAsLinesSync();
55 | final content = "export 'day$dayNumber.dart';\n";
56 | var found = false;
57 | // check if line already exists
58 | for (final line in exports) {
59 | if (line.contains('day$dayNumber.dart')) {
60 | found = true;
61 | break;
62 | }
63 | }
64 |
65 | // export new day in index file if not present
66 | if (!found) {
67 | await exportFile.writeAsString(
68 | content,
69 | mode: FileMode.append,
70 | );
71 | }
72 |
73 | // Create input file
74 | print('Loading input from adventofcode.com...');
75 | try {
76 | final request = await HttpClient().getUrl(
77 | Uri.parse(
78 | 'https://adventofcode.com/$year/day/${int.parse(dayNumber)}/input',
79 | ),
80 | );
81 | request.cookies.add(Cookie('session', session));
82 | final response = await request.close();
83 | if (response.statusCode != 200) {
84 | print('''
85 | Received status code ${response.statusCode} from server.
86 |
87 | You might need to refresh your session token.
88 | You can do so by deleting the file at $sessionTokenPath and restarting the generator.''');
89 | return;
90 | }
91 | final dataPath = 'input/aoc$dayNumber.txt';
92 | // unawaited(File(dataPath).create());
93 | await response.pipe(File(dataPath).openWrite());
94 | } catch (e) {
95 | print('Error loading file: $e');
96 | }
97 |
98 | print('All set, Good luck!');
99 | }
100 |
101 | String _dayTemplate(String dayNumber) {
102 | return '''
103 | import '../utils/index.dart';
104 |
105 | class Day$dayNumber extends GenericDay {
106 | Day$dayNumber() : super(${int.parse(dayNumber)});
107 |
108 | @override
109 | parseInput() {
110 |
111 | }
112 |
113 | @override
114 | int solvePart1() {
115 |
116 | return 0;
117 | }
118 |
119 | @override
120 | int solvePart2() {
121 |
122 | return 0;
123 | }
124 | }
125 |
126 | ''';
127 | }
128 |
129 | String _testTemplate(String day) {
130 | return '''
131 | // ignore_for_file: unnecessary_null_comparison
132 |
133 | import 'package:test/test.dart';
134 |
135 | import '../solutions/day$day.dart';
136 |
137 | // *******************************************************************
138 | // Fill out the variables below according to the puzzle description!
139 | // The test code should usually not need to be changed, apart from uncommenting
140 | // the puzzle tests for regression testing.
141 | // *******************************************************************
142 |
143 | /// Paste in the small example that is given for the FIRST PART of the puzzle.
144 | /// It will be evaluated against the `_exampleSolutionPart1` below.
145 | /// Make sure to respect the multiline string format to avoid additional
146 | /// newlines at the end.
147 | const _exampleInput1 = \'''
148 | \''';
149 |
150 | /// Paste in the small example that is given for the SECOND PART of the puzzle.
151 | /// It will be evaluated against the `_exampleSolutionPart2` below.
152 | ///
153 | /// In case the second part uses the same example, uncomment below line instead:
154 | // const _exampleInput2 = _exampleInput1;
155 | const _exampleInput2 = \'''
156 | \''';
157 |
158 | /// The solution for the FIRST PART's example, which is given by the puzzle.
159 | const _exampleSolutionPart1 = 0;
160 |
161 | /// The solution for the SECOND PART's example, which is given by the puzzle.
162 | const _exampleSolutionPart2 = 0;
163 |
164 | /// The actual solution for the FIRST PART of the puzzle, based on your input.
165 | /// This can only be filled out after you have solved the puzzle and is used
166 | /// for regression testing when refactoring.
167 | /// As long as the variable is `null`, the tests will be skipped.
168 | const _puzzleSolutionPart1 = null;
169 |
170 | /// The actual solution for the SECOND PART of the puzzle, based on your input.
171 | /// This can only be filled out after you have solved the puzzle and is used
172 | /// for regression testing when refactoring.
173 | /// As long as the variable is `null`, the tests will be skipped.
174 | const _puzzleSolutionPart2 = null;
175 |
176 | void main() {
177 | group(
178 | 'Day $day - Example Input',
179 | () {
180 | test('Part 1', () {
181 | final day = Day$day()..inputForTesting = _exampleInput1;
182 | expect(day.solvePart1(), _exampleSolutionPart1);
183 | });
184 | test('Part 2', () {
185 | final day = Day$day()..inputForTesting = _exampleInput2;
186 | expect(day.solvePart2(), _exampleSolutionPart2);
187 | });
188 | },
189 | );
190 | group(
191 | 'Day $day - Puzzle Input',
192 | () {
193 | final day = Day$day();
194 | test(
195 | 'Part 1',
196 | skip: _puzzleSolutionPart1 == null
197 | ? 'Skipped because _puzzleSolutionPart1 is null'
198 | : false,
199 | () => expect(day.solvePart1(), _puzzleSolutionPart1),
200 | );
201 | test(
202 | 'Part 2',
203 | skip: _puzzleSolutionPart2 == null
204 | ? 'Skipped because _puzzleSolutionPart2 is null'
205 | : false,
206 | () => expect(day.solvePart2(), _puzzleSolutionPart2),
207 | );
208 | },
209 | );
210 | }
211 | ''';
212 | }
213 |
--------------------------------------------------------------------------------
/input/aoc01.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/S-ecki/AdventOfCode-Starter-Dart/9ff173463dd8201bc352e8c5581930d0b4f76ce3/input/aoc01.txt
--------------------------------------------------------------------------------
/main.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_function_literals_in_foreach_calls
2 |
3 | import 'solutions/index.dart';
4 | import 'tool/generic_day.dart';
5 |
6 | /// List holding all the solution classes.
7 | final days = [
8 | Day01(),
9 | ];
10 |
11 | void main(List args) {
12 | var onlyShowLast = true;
13 |
14 | if (args.length == 1 && args[0].isHelperArgument()) {
15 | printHelper();
16 | return;
17 | }
18 |
19 | if (args.length == 1 && args[0].isAllArgument()) {
20 | onlyShowLast = false;
21 | }
22 |
23 | onlyShowLast
24 | ? days.last.printSolutions()
25 | : days.forEach((day) => day.printSolutions());
26 | }
27 |
28 | void printHelper() {
29 | print(
30 | '''
31 | Usage: dart main.dart
32 |
33 | Global Options:
34 | -h, --help Show this help message
35 | -a, --all Show all solutions
36 | ''',
37 | );
38 | }
39 |
40 | extension ArgsMatcher on String? {
41 | bool isHelperArgument() {
42 | return this == '-h' || this == '--help';
43 | }
44 |
45 | bool isAllArgument() {
46 | return this == '-a' || this == '--all';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | _fe_analyzer_shared:
5 | dependency: transitive
6 | description:
7 | name: _fe_analyzer_shared
8 | sha256: "36a321c3d2cbe01cbcb3540a87b8843846e0206df3e691fa7b23e19e78de6d49"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "65.0.0"
12 | analyzer:
13 | dependency: transitive
14 | description:
15 | name: analyzer
16 | sha256: dfe03b90ec022450e22513b5e5ca1f01c0c01de9c3fba2f7fd233cb57a6b9a07
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "6.3.0"
20 | args:
21 | dependency: transitive
22 | description:
23 | name: args
24 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.4.2"
28 | async:
29 | dependency: transitive
30 | description:
31 | name: async
32 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "2.11.0"
36 | boolean_selector:
37 | dependency: transitive
38 | description:
39 | name: boolean_selector
40 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "2.1.1"
44 | collection:
45 | dependency: "direct main"
46 | description:
47 | name: collection
48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.18.0"
52 | convert:
53 | dependency: transitive
54 | description:
55 | name: convert
56 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "3.1.1"
60 | coverage:
61 | dependency: transitive
62 | description:
63 | name: coverage
64 | sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb"
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "1.6.4"
68 | crypto:
69 | dependency: transitive
70 | description:
71 | name: crypto
72 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "3.0.3"
76 | file:
77 | dependency: transitive
78 | description:
79 | name: file
80 | sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "6.1.4"
84 | frontend_server_client:
85 | dependency: transitive
86 | description:
87 | name: frontend_server_client
88 | sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
89 | url: "https://pub.dev"
90 | source: hosted
91 | version: "3.2.0"
92 | glob:
93 | dependency: transitive
94 | description:
95 | name: glob
96 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
97 | url: "https://pub.dev"
98 | source: hosted
99 | version: "2.1.2"
100 | http_multi_server:
101 | dependency: transitive
102 | description:
103 | name: http_multi_server
104 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
105 | url: "https://pub.dev"
106 | source: hosted
107 | version: "3.2.1"
108 | http_parser:
109 | dependency: transitive
110 | description:
111 | name: http_parser
112 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
113 | url: "https://pub.dev"
114 | source: hosted
115 | version: "4.0.2"
116 | io:
117 | dependency: transitive
118 | description:
119 | name: io
120 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
121 | url: "https://pub.dev"
122 | source: hosted
123 | version: "1.0.4"
124 | js:
125 | dependency: transitive
126 | description:
127 | name: js
128 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
129 | url: "https://pub.dev"
130 | source: hosted
131 | version: "0.6.7"
132 | json_annotation:
133 | dependency: transitive
134 | description:
135 | name: json_annotation
136 | sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
137 | url: "https://pub.dev"
138 | source: hosted
139 | version: "4.8.1"
140 | logging:
141 | dependency: transitive
142 | description:
143 | name: logging
144 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
145 | url: "https://pub.dev"
146 | source: hosted
147 | version: "1.2.0"
148 | matcher:
149 | dependency: transitive
150 | description:
151 | name: matcher
152 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
153 | url: "https://pub.dev"
154 | source: hosted
155 | version: "0.12.16"
156 | meta:
157 | dependency: "direct main"
158 | description:
159 | name: meta
160 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
161 | url: "https://pub.dev"
162 | source: hosted
163 | version: "1.11.0"
164 | mime:
165 | dependency: transitive
166 | description:
167 | name: mime
168 | sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
169 | url: "https://pub.dev"
170 | source: hosted
171 | version: "1.0.4"
172 | node_preamble:
173 | dependency: transitive
174 | description:
175 | name: node_preamble
176 | sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
177 | url: "https://pub.dev"
178 | source: hosted
179 | version: "2.0.2"
180 | package_config:
181 | dependency: transitive
182 | description:
183 | name: package_config
184 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
185 | url: "https://pub.dev"
186 | source: hosted
187 | version: "2.1.0"
188 | path:
189 | dependency: transitive
190 | description:
191 | name: path
192 | sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb"
193 | url: "https://pub.dev"
194 | source: hosted
195 | version: "1.8.0"
196 | pool:
197 | dependency: transitive
198 | description:
199 | name: pool
200 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
201 | url: "https://pub.dev"
202 | source: hosted
203 | version: "1.5.1"
204 | pub_semver:
205 | dependency: transitive
206 | description:
207 | name: pub_semver
208 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
209 | url: "https://pub.dev"
210 | source: hosted
211 | version: "2.1.4"
212 | quiver:
213 | dependency: "direct main"
214 | description:
215 | name: quiver
216 | sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
217 | url: "https://pub.dev"
218 | source: hosted
219 | version: "3.2.1"
220 | shelf:
221 | dependency: transitive
222 | description:
223 | name: shelf
224 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
225 | url: "https://pub.dev"
226 | source: hosted
227 | version: "1.4.1"
228 | shelf_packages_handler:
229 | dependency: transitive
230 | description:
231 | name: shelf_packages_handler
232 | sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
233 | url: "https://pub.dev"
234 | source: hosted
235 | version: "3.0.2"
236 | shelf_static:
237 | dependency: transitive
238 | description:
239 | name: shelf_static
240 | sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
241 | url: "https://pub.dev"
242 | source: hosted
243 | version: "1.1.2"
244 | shelf_web_socket:
245 | dependency: transitive
246 | description:
247 | name: shelf_web_socket
248 | sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
249 | url: "https://pub.dev"
250 | source: hosted
251 | version: "1.0.4"
252 | source_map_stack_trace:
253 | dependency: transitive
254 | description:
255 | name: source_map_stack_trace
256 | sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
257 | url: "https://pub.dev"
258 | source: hosted
259 | version: "2.1.1"
260 | source_maps:
261 | dependency: transitive
262 | description:
263 | name: source_maps
264 | sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
265 | url: "https://pub.dev"
266 | source: hosted
267 | version: "0.10.12"
268 | source_span:
269 | dependency: transitive
270 | description:
271 | name: source_span
272 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
273 | url: "https://pub.dev"
274 | source: hosted
275 | version: "1.10.0"
276 | stack_trace:
277 | dependency: transitive
278 | description:
279 | name: stack_trace
280 | sha256: f8d9f247e2f9f90e32d1495ff32dac7e4ae34ffa7194c5ff8fcc0fd0e52df774
281 | url: "https://pub.dev"
282 | source: hosted
283 | version: "1.10.0"
284 | stream_channel:
285 | dependency: transitive
286 | description:
287 | name: stream_channel
288 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
289 | url: "https://pub.dev"
290 | source: hosted
291 | version: "2.1.2"
292 | string_scanner:
293 | dependency: transitive
294 | description:
295 | name: string_scanner
296 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
297 | url: "https://pub.dev"
298 | source: hosted
299 | version: "1.2.0"
300 | term_glyph:
301 | dependency: transitive
302 | description:
303 | name: term_glyph
304 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
305 | url: "https://pub.dev"
306 | source: hosted
307 | version: "1.2.1"
308 | test:
309 | dependency: "direct main"
310 | description:
311 | name: test
312 | sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f
313 | url: "https://pub.dev"
314 | source: hosted
315 | version: "1.24.9"
316 | test_api:
317 | dependency: transitive
318 | description:
319 | name: test_api
320 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
321 | url: "https://pub.dev"
322 | source: hosted
323 | version: "0.6.1"
324 | test_core:
325 | dependency: transitive
326 | description:
327 | name: test_core
328 | sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a
329 | url: "https://pub.dev"
330 | source: hosted
331 | version: "0.5.9"
332 | timing:
333 | dependency: "direct main"
334 | description:
335 | name: timing
336 | sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
337 | url: "https://pub.dev"
338 | source: hosted
339 | version: "1.0.1"
340 | typed_data:
341 | dependency: transitive
342 | description:
343 | name: typed_data
344 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
345 | url: "https://pub.dev"
346 | source: hosted
347 | version: "1.3.2"
348 | very_good_analysis:
349 | dependency: "direct main"
350 | description:
351 | name: very_good_analysis
352 | sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8"
353 | url: "https://pub.dev"
354 | source: hosted
355 | version: "5.1.0"
356 | vm_service:
357 | dependency: transitive
358 | description:
359 | name: vm_service
360 | sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
361 | url: "https://pub.dev"
362 | source: hosted
363 | version: "11.10.0"
364 | watcher:
365 | dependency: transitive
366 | description:
367 | name: watcher
368 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
369 | url: "https://pub.dev"
370 | source: hosted
371 | version: "1.1.0"
372 | web_socket_channel:
373 | dependency: transitive
374 | description:
375 | name: web_socket_channel
376 | sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
377 | url: "https://pub.dev"
378 | source: hosted
379 | version: "2.4.0"
380 | webkit_inspection_protocol:
381 | dependency: transitive
382 | description:
383 | name: webkit_inspection_protocol
384 | sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
385 | url: "https://pub.dev"
386 | source: hosted
387 | version: "1.2.1"
388 | yaml:
389 | dependency: transitive
390 | description:
391 | name: yaml
392 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
393 | url: "https://pub.dev"
394 | source: hosted
395 | version: "3.1.2"
396 | sdks:
397 | dart: ">=3.0.0 <4.0.0"
398 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: aoc
2 | version: 1.0.0
3 |
4 | environment:
5 | sdk: ">=3.0.0 <4.0.0"
6 | dependencies:
7 | collection: ^1.18.0
8 | meta: ^1.11.0
9 | quiver: ^3.2.1
10 | test: ^1.24.9
11 | timing: ^1.0.1
12 | very_good_analysis: ^5.1.0
13 |
--------------------------------------------------------------------------------
/solutions/day01.dart:
--------------------------------------------------------------------------------
1 | import '../utils/index.dart';
2 |
3 | /// Every day should extend [GenericDay] to have access to the corresponding
4 | /// input and a common interface.
5 | ///
6 | /// Naming convention is set to pad any single-digit day with `0` to have proper
7 | /// ordering of files and correct mapping between input for days and the day
8 | /// files.
9 | class Day01 extends GenericDay {
10 | // call the superclass with an integer == today´s day
11 | Day01() : super(1);
12 |
13 | /// The [InputUtil] can be accessed through the superclass variable `input`. \
14 | /// There are several methods in that class that parse the input in different
15 | /// ways, an example is given below
16 | ///
17 | /// The return type of this is `dynamic` for [GenericDay], so you can decide
18 | /// on a day-to-day basis what this function should return.
19 | @override
20 | List parseInput() {
21 | final lines = input.getPerLine();
22 | // exemplary usage of ParseUtil class
23 | return ParseUtil.stringListToIntList(lines);
24 | }
25 |
26 | /// The `solvePartX` methods always return a int, the puzzle solution. This
27 | /// solution will be printed in main.
28 | @override
29 | int solvePart1() {
30 | // TODO implement
31 | return 0;
32 | }
33 |
34 | @override
35 | int solvePart2() {
36 | // TODO implement
37 | return 0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/solutions/index.dart:
--------------------------------------------------------------------------------
1 | // This is an index file for grouped exports of all solutions. It is used to
2 | // avoid cluttering main with imports for every day. Additionally, it makes
3 | // boilerplate code creation with the CLI easier.
4 | export 'day01.dart';
5 |
--------------------------------------------------------------------------------
/tool/generic_day.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 | import 'package:timing/timing.dart';
3 |
4 | import '../utils/input_util.dart';
5 |
6 | typedef SolveFunction = int Function();
7 | typedef SolutionWithDuration = (int, Duration);
8 |
9 | /// Provides the [InputUtil] for given day and a [printSolutions] method to show
10 | /// the puzzle solutions for given day.
11 | abstract class GenericDay {
12 | GenericDay(this.day) : input = InputUtil(day);
13 | final int day;
14 | InputUtil input;
15 |
16 | /// This setter must only be used to mutate the input of an existing day
17 | /// implementation for testing purposes.
18 | @visibleForTesting
19 | // ignore: avoid_setters_without_getters
20 | set inputForTesting(String example) =>
21 | input = InputUtil.fromMultiLineString(example);
22 |
23 | dynamic parseInput();
24 | int solvePart1();
25 | int solvePart2();
26 |
27 | void printSolutions() {
28 | final result1 = _solveAndTrackTime(solvePart1);
29 | final result2 = _solveAndTrackTime(solvePart2);
30 |
31 | print('-------------------------');
32 | print(' Day $day ');
33 | print('Solution for part one: ${_formatResult(result1)}');
34 | print('Solution for part two: ${_formatResult(result2)}');
35 | print('\n');
36 | }
37 |
38 | SolutionWithDuration _solveAndTrackTime(SolveFunction solve) {
39 | final tracker = SyncTimeTracker();
40 | late final int solution;
41 | tracker.track(() => solution = solve());
42 | return (solution, tracker.duration);
43 | }
44 |
45 | String _formatResult(SolutionWithDuration result) {
46 | final (solution, duration) = result;
47 | return '$solution - Took ${duration.inMilliseconds} milliseconds';
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tool/session_token.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:quiver/strings.dart';
4 |
5 | const sessionTokenPath = '.dart_tool/aoc/.session_token';
6 |
7 | String getSessionToken() {
8 | final sessionToken = _readSessionToken();
9 | if (sessionToken != null) {
10 | return sessionToken;
11 | }
12 |
13 | final token = _promptSessionToken();
14 | _writeSessionToken(token);
15 | return token;
16 | }
17 |
18 | String? _readSessionToken() {
19 | try {
20 | final token = File(sessionTokenPath).readAsStringSync();
21 | if (token.isNotEmpty) {
22 | return token;
23 | }
24 | } catch (e) {
25 | print('Error reading session token: $e');
26 | }
27 | return null;
28 | }
29 |
30 | void _writeSessionToken(String token) {
31 | try {
32 | File(sessionTokenPath)
33 | .create(recursive: true)
34 | .then((file) => file.writeAsStringSync(token));
35 | } catch (e) {
36 | print('Error writing session token: $e');
37 | }
38 | }
39 |
40 | String _promptSessionToken() {
41 | stdout.write(_kSessionTokenPromptMessage);
42 | final token = stdin.readLineSync();
43 | stdout.writeln();
44 | if (isBlank(token)) {
45 | print('Session token cannot be empty!');
46 | return _promptSessionToken();
47 | }
48 | return token!;
49 | }
50 |
51 | const String _kSessionTokenPromptMessage = '''
52 | ╔════════════════════════════════════════════════════════════════════════════╗
53 | ║ Session token required! ║
54 | ║ ║
55 | ║ Please visit https://adventofcode.com and log in. ║
56 | ║ ║
57 | ║ Once logged in, get your session token from the cookie: ║
58 | ║ 1. Open DevTools (F12) ║
59 | ║ 2. Go to Application > Cookies > https://adventofcode.com ║
60 | ║ 3. Copy the value of the `session` cookie ║
61 | ║ ║
62 | ╚════════════════════════════════════════════════════════════════════════════╝
63 |
64 | Please enter your session token:
65 | ''';
66 |
--------------------------------------------------------------------------------
/utils/field.dart:
--------------------------------------------------------------------------------
1 | import 'package:quiver/iterables.dart';
2 |
3 | typedef Position = (int x, int y);
4 | typedef VoidFieldCallback = void Function(int, int);
5 |
6 | /// A helper class for easier work with 2D data.
7 | class Field {
8 | Field(List> field)
9 | : assert(field.isNotEmpty, 'Field must not be empty'),
10 | assert(field[0].isNotEmpty, 'First position must not be empty'),
11 | // creates a deep copy by value from given field to prevent unwarranted
12 | // overrides
13 | field = List>.generate(
14 | field.length,
15 | (y) => List.generate(field[0].length, (x) => field[y][x]),
16 | ),
17 | height = field.length,
18 | width = field[0].length;
19 |
20 | final List> field;
21 | int height;
22 | int width;
23 |
24 | /// Returns the value at the given position.
25 | T getValueAtPosition(Position position) {
26 | final (x, y) = position;
27 | return field[y][x];
28 | }
29 |
30 | /// Returns the value at the given coordinates.
31 | T getValueAt(int x, int y) => getValueAtPosition((x, y));
32 |
33 | /// Sets the value at the given Position.
34 | void setValueAtPosition(Position position, T value) {
35 | final (x, y) = position;
36 | field[y][x] = value;
37 | }
38 |
39 | /// Sets the value at the given coordinates.
40 | void setValueAt(int x, int y, T value) => setValueAtPosition((x, y), value);
41 |
42 | /// Returns whether the given position is inside of this field.
43 | bool isOnField(Position position) {
44 | final (x, y) = position;
45 | return x >= 0 && y >= 0 && x < width && y < height;
46 | }
47 |
48 | /// Returns the whole row with given row index.
49 | Iterable getRow(int row) => field[row];
50 |
51 | /// Returns the whole column with given column index.
52 | Iterable getColumn(int column) => field.map((row) => row[column]);
53 |
54 | /// Returns the minimum value in this field.
55 | T get minValue => min(field.expand((element) => element))!;
56 |
57 | /// Returns the maximum value in this field.
58 | T get maxValue => max(field.expand((element) => element))!;
59 |
60 | /// Executes the given callback for every position on this field.
61 | void forEach(VoidFieldCallback callback) {
62 | for (var y = 0; y < height; y++) {
63 | for (var x = 0; x < width; x++) {
64 | callback(x, y);
65 | }
66 | }
67 | }
68 |
69 | /// Returns the number of occurrences of given object in this field.
70 | int count(T searched) => field
71 | .expand((element) => element)
72 | .fold(0, (acc, elem) => elem == searched ? acc + 1 : acc);
73 |
74 | /// Executes the given callback for all given positions.
75 | void forPositions(
76 | Iterable positions,
77 | VoidFieldCallback callback,
78 | ) {
79 | for (final (x, y) in positions) {
80 | callback(x, y);
81 | }
82 | }
83 |
84 | /// Returns all adjacent cells to the given position. This does `NOT` include
85 | /// diagonal neighbours.
86 | Iterable adjacent(int x, int y) {
87 | return {
88 | (x, y - 1),
89 | (x, y + 1),
90 | (x - 1, y),
91 | (x + 1, y),
92 | }..removeWhere(
93 | (pos) {
94 | final (x, y) = pos;
95 | return x < 0 || y < 0 || x >= width || y >= height;
96 | },
97 | );
98 | }
99 |
100 | /// Returns all positional neighbours of a point. This includes the adjacent
101 | /// `AND` diagonal neighbours.
102 | Iterable neighbours(int x, int y) {
103 | return {
104 | // positions are added in a circle, starting at the top middle
105 | (x, y - 1),
106 | (x + 1, y - 1),
107 | (x + 1, y),
108 | (x + 1, y + 1),
109 | (x, y + 1),
110 | (x - 1, y + 1),
111 | (x - 1, y),
112 | (x - 1, y - 1),
113 | }..removeWhere(
114 | (pos) {
115 | final (x, y) = pos;
116 | return x < 0 || y < 0 || x >= width || y >= height;
117 | },
118 | );
119 | }
120 |
121 | /// Returns a deep copy by value of this [Field].
122 | Field copy() {
123 | final newField = List>.generate(
124 | height,
125 | (y) => List.generate(width, (x) => field[y][x]),
126 | );
127 | return Field(newField);
128 | }
129 |
130 | @override
131 | String toString() {
132 | final result = StringBuffer();
133 | for (final row in field) {
134 | for (final elem in row) {
135 | result.write(elem.toString());
136 | }
137 | result.write('\n');
138 | }
139 | return result.toString();
140 | }
141 | }
142 |
143 | /// Extension for [Field]s where `T` is of type [int].
144 | extension IntegerField on Field {
145 | /// Increments the values of Position `x` `y`.
146 | dynamic increment(int x, int y) => setValueAt(x, y, getValueAt(x, y) + 1);
147 |
148 | /// Convenience method to create a Field from a single String, where the
149 | /// String is a "block" of integers.
150 | static Field fromString(String string) {
151 | final lines = string
152 | .split('\n')
153 | .map((line) => line.trim().split('').map(int.parse).toList())
154 | .toList();
155 | return Field(lines);
156 | }
157 | }
158 |
159 | // extension CoordinateLocator on Position {
160 | // int get x => item1;
161 | // int get y => item2;
162 | // }
163 |
--------------------------------------------------------------------------------
/utils/index.dart:
--------------------------------------------------------------------------------
1 | // package auto imports
2 | export 'package:collection/collection.dart';
3 | export 'package:quiver/core.dart';
4 | export 'package:quiver/iterables.dart';
5 | export 'package:quiver/strings.dart';
6 |
7 | export '../tool/generic_day.dart';
8 | export 'field.dart';
9 | export 'input_util.dart';
10 | export 'parse_util.dart';
11 |
--------------------------------------------------------------------------------
/utils/input_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | /// Automatically reads reads the contents of the input file for given `day`. \
4 | /// Note that file name and location must align.
5 | class InputUtil {
6 | InputUtil(int day)
7 | : _inputAsString = _readInputDay(day),
8 | _inputAsList = _readInputDayAsList(day);
9 |
10 | /// Reads the entire input contents as one String.
11 | /// This is useful for parsing the test input.
12 | ///
13 | /// Example:
14 | /// ```dart
15 | /// final input = InputUtil.fromMultiLineString('''
16 | /// two1nine
17 | /// eightwothree
18 | /// abcone2threexyz
19 | /// xtwone3four
20 | /// 4nineeightseven2
21 | /// zoneight234
22 | /// 7pqrstsixteen''');
23 | /// final lines = input.getPerLine();
24 | /// ```
25 | InputUtil.fromMultiLineString(String input)
26 | : _inputAsString = input,
27 | _inputAsList = input.split('\n');
28 |
29 | final String _inputAsString;
30 | final List _inputAsList;
31 |
32 | static String _createInputPath(int day) {
33 | final dayString = day.toString().padLeft(2, '0');
34 | return './input/aoc$dayString.txt';
35 | }
36 |
37 | static String _readInputDay(int day) {
38 | return _readInput(_createInputPath(day));
39 | }
40 |
41 | static String _readInput(String input) {
42 | return File(input).readAsStringSync();
43 | }
44 |
45 | static List _readInputDayAsList(int day) {
46 | return _readInputAsList(_createInputPath(day));
47 | }
48 |
49 | static List _readInputAsList(String input) {
50 | return File(input).readAsLinesSync();
51 | }
52 |
53 | /// Returns input as one String.
54 | String get asString => _inputAsString;
55 |
56 | /// Reads the entire input contents as lines of text.
57 | List getPerLine() => _inputAsList;
58 |
59 | /// Splits the input String by `whitespace` and `newline`.
60 | List getPerWhitespace() {
61 | return _inputAsString.split(RegExp(r'\s\n'));
62 | }
63 |
64 | /// Splits the input String by given pattern.
65 | List getBy(String pattern) {
66 | return _inputAsString.split(pattern);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/utils/parse_util.dart:
--------------------------------------------------------------------------------
1 | class ParseUtil {
2 | /// Throws an exception if any given String is not parseable.
3 | static List stringListToIntList(List strings) {
4 | return strings.map(int.parse).toList();
5 | }
6 |
7 | /// Returns decimal number from binary string
8 | static int binaryToDecimal(String binary) {
9 | return int.parse(binary, radix: 2);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------