├── .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 | Dart Logo 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 | --------------------------------------------------------------------------------