Skip to content

Commit a7f0923

Browse files
committed
2018
1 parent 3c9d5f7 commit a7f0923

File tree

5 files changed

+287
-17
lines changed

5 files changed

+287
-17
lines changed

2018/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Advent of Code in Rust 🦀
22

33
![AoC2018](https://img.shields.io/badge/Advent_of_Code-2018-8A2BE2)
4-
![Stars: 41](https://img.shields.io/badge/Stars-41⭐-blue)
5-
![Rust: 21](https://img.shields.io/badge/Rust-21-cyan?logo=Rust)
4+
![Stars: 43](https://img.shields.io/badge/Stars-43⭐-blue)
5+
![Rust: 22](https://img.shields.io/badge/Rust-22-cyan?logo=Rust)
66
![Python: 4](https://img.shields.io/badge/Python-4-cyan?logo=Python)
77

8-
## 2018 ([Calendar](https://adventofcode.com/2018)) ([Solutions](../2018/)) : 41
8+
## 2018 ([Calendar](https://adventofcode.com/2018)) ([Solutions](../2018/)) : 43
99

1010
Puzzle | Stars | Languages
1111
------------------------------------------------------------------------------------ | ----- | -----------
@@ -28,5 +28,6 @@ Puzzle
2828
[Day 18: Settlers of The North Pole](https://adventofcode.com/2018/day/18) | ⭐⭐ | [Rust](../2018/day18/day18.rs) [Python](../2018/day18/day18.py)
2929
[Day 19: Go With The Flow](https://adventofcode.com/2018/day/19) | ⭐⭐ | [Rust](../2018/day19/day19.rs)
3030
[Day 21: Chronal Conversion](https://adventofcode.com/2018/day/21) | ⭐⭐ | [Rust](../2018/day21/day21.rs)
31+
[Day 22: Mode Maze](https://adventofcode.com/2018/day/22) | ⭐⭐ | [Rust](../2018/day22/day22.rs)
3132
[Day 23: Experimental Emergency Teleportation](https://adventofcode.com/2018/day/23) | ⭐⭐ | [Rust](../2018/day23/day23.rs)
3233
[Day 25: Four-Dimensional Adventure](https://adventofcode.com/2018/day/25) | ⭐ | [Rust](../2018/day25/day25.rs)

2018/day22/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "day22"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
aoc = { path = "../../aoc" }
8+
9+
[[bin]]
10+
name = "day22"
11+
path = "day22.rs"

2018/day22/day22.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
//! [Day 22: Mode Maze](https://adventofcode.com/2018/day/22)
2+
3+
use aoc::grid::Direction;
4+
use std::collections::{BinaryHeap, HashMap, HashSet};
5+
6+
const ROCKY: u32 = 0;
7+
const WET: u32 = 1;
8+
const NARROW: u32 = 2;
9+
10+
const TORCH: u8 = 0;
11+
const CLIMBING_GEAR: u8 = 1;
12+
const NEITHER: u8 = 2;
13+
14+
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
15+
struct Node {
16+
x: u32,
17+
y: u32,
18+
item: u8,
19+
time: u32,
20+
heuristic: u32,
21+
}
22+
23+
impl PartialOrd for Node {
24+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
25+
Some(self.cmp(other))
26+
}
27+
}
28+
29+
impl Ord for Node {
30+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
31+
other.heuristic.cmp(&self.heuristic)
32+
}
33+
}
34+
35+
impl Node {
36+
/// Construct the initial node.
37+
/// Start at position (0,0) with a torch
38+
fn init(target: (u32, u32)) -> Self {
39+
Node {
40+
x: 0,
41+
y: 0,
42+
item: TORCH,
43+
time: 0,
44+
heuristic: manhattan(0, 0, target),
45+
}
46+
}
47+
48+
/// Switch item, with a cost of 7 seconds.
49+
fn switch_item(&self, target: (u32, u32), region: u32) -> Self {
50+
let new_item = match (region, self.item) {
51+
(ROCKY, TORCH) | (WET, NEITHER) => CLIMBING_GEAR,
52+
(WET, CLIMBING_GEAR) | (NARROW, TORCH) => NEITHER,
53+
(ROCKY, CLIMBING_GEAR) | (NARROW, NEITHER) => TORCH,
54+
_ => panic!(),
55+
};
56+
57+
let new_time = self.time + 7;
58+
59+
Self {
60+
x: self.x,
61+
y: self.y,
62+
item: new_item,
63+
time: new_time,
64+
heuristic: manhattan(self.x, self.y, target) + new_time,
65+
}
66+
}
67+
68+
/// Move to a position with a cost of 1 second.
69+
/// No verification that the movement is legal or not
70+
fn move_to(&self, target: (u32, u32), x: u32, y: u32) -> Self {
71+
Self {
72+
x,
73+
y,
74+
item: self.item,
75+
time: self.time + 1,
76+
heuristic: manhattan(x, y, target) + self.time + 1,
77+
}
78+
}
79+
}
80+
81+
/// Compute Manhattan distance to target.
82+
fn manhattan(x: u32, y: u32, target: (u32, u32)) -> u32 {
83+
x.abs_diff(target.0) + y.abs_diff(target.1)
84+
}
85+
86+
struct Puzzle {
87+
depth: u32,
88+
target: (u32, u32),
89+
90+
lru: HashMap<(u32, u32), u32>,
91+
}
92+
93+
impl Puzzle {
94+
fn new() -> Puzzle {
95+
Puzzle {
96+
depth: 0,
97+
target: (0, 0),
98+
lru: HashMap::new(),
99+
}
100+
}
101+
102+
fn geologic_index(&mut self, x: u32, y: u32) -> u32 {
103+
if let Some(index) = self.lru.get(&(x, y)) {
104+
*index
105+
} else {
106+
let index = if (x, y) == (0, 0) || (x, y) == self.target {
107+
0
108+
} else if y == 0 {
109+
x * 16807
110+
} else if x == 0 {
111+
y * 48271
112+
} else {
113+
self.erosion_level(x - 1, y) * self.erosion_level(x, y - 1)
114+
};
115+
116+
self.lru.insert((x, y), index);
117+
index
118+
}
119+
}
120+
121+
fn erosion_level(&mut self, x: u32, y: u32) -> u32 {
122+
(self.geologic_index(x, y) + self.depth) % 20183
123+
}
124+
125+
fn region(&mut self, x: u32, y: u32) -> u32 {
126+
self.erosion_level(x, y) % 3
127+
}
128+
129+
fn show(&mut self) {
130+
for y in 0..16 {
131+
for x in 0..16 {
132+
let c = if (x, y) == (0, 0) {
133+
'M'
134+
} else if (x, y) == self.target {
135+
'T'
136+
} else {
137+
match self.region(x, y) {
138+
ROCKY => '.',
139+
WET => '=',
140+
NARROW => '|',
141+
_ => panic!(),
142+
}
143+
};
144+
145+
print!("{c}");
146+
}
147+
148+
println!();
149+
}
150+
}
151+
}
152+
153+
impl Puzzle {
154+
/// Get the puzzle input.
155+
fn configure(&mut self, path: &str) {
156+
let data = std::fs::read_to_string(path).unwrap();
157+
158+
for line in data.lines() {
159+
if let Some(target) = line.strip_prefix("target: ") {
160+
let (x, y) = target.split_once(',').unwrap();
161+
self.target = (x.parse().unwrap(), y.parse().unwrap());
162+
} else if let Some(depth) = line.strip_prefix("depth: ") {
163+
self.depth = depth.parse().unwrap();
164+
}
165+
}
166+
}
167+
168+
/// Solve part one.
169+
fn part1(&mut self) -> u32 {
170+
(0..=self.target.0)
171+
.map(|x| (0..=self.target.1).map(|y| self.region(x, y)).sum::<u32>())
172+
.sum()
173+
}
174+
175+
/// Solve part two.
176+
fn part2(&mut self) -> u32 {
177+
// A star algorithm
178+
let mut open_set = BinaryHeap::new();
179+
let mut closed_list = HashSet::new();
180+
181+
open_set.push(Node::init(self.target));
182+
183+
while let Some(e) = open_set.pop() {
184+
// if current node is at goal (target and torch)
185+
// we have found our path
186+
if (e.x, e.y) == self.target && e.item == TORCH {
187+
return e.time;
188+
}
189+
190+
if closed_list.contains(&(e.x, e.y, e.item)) {
191+
continue;
192+
}
193+
closed_list.insert((e.x, e.y, e.item));
194+
195+
// switch item
196+
let n = e.switch_item(self.target, self.region(e.x, e.y));
197+
open_set.push(n);
198+
199+
// move
200+
for (nx, ny, _) in Direction::iter(e.x, e.y, u32::MAX, u32::MAX) {
201+
let can_move = match self.region(nx, ny) {
202+
ROCKY => e.item != NEITHER,
203+
WET => e.item != TORCH,
204+
NARROW => e.item != CLIMBING_GEAR,
205+
_ => panic!(),
206+
};
207+
208+
if can_move && !closed_list.contains(&(nx, ny, e.item)) {
209+
open_set.push(e.move_to(self.target, nx, ny));
210+
}
211+
}
212+
}
213+
0
214+
}
215+
}
216+
217+
fn main() {
218+
let args = aoc::parse_args();
219+
let mut puzzle = Puzzle::new();
220+
puzzle.configure(args.path.as_str());
221+
222+
if args.verbose {
223+
puzzle.show();
224+
}
225+
226+
println!("{}", puzzle.part1());
227+
println!("{}", puzzle.part2());
228+
}
229+
230+
/// Test from puzzle input
231+
#[cfg(test)]
232+
mod test {
233+
use super::*;
234+
235+
#[test]
236+
fn test01() {
237+
let mut puzzle = Puzzle::new();
238+
puzzle.configure("test.txt");
239+
assert_eq!(puzzle.part1(), 114);
240+
}
241+
242+
#[test]
243+
fn test02() {
244+
let mut puzzle = Puzzle::new();
245+
puzzle.configure("test.txt");
246+
assert_eq!(puzzle.part2(), 45);
247+
}
248+
}

2018/day22/test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
depth: 510
2+
target: 10,10

scripts/runall.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
ITALIC = "\033[3m"
3030
BLINK = "\033[6m"
3131
CLEAR_EOL = "\033[0K"
32+
CR = "\r"
3233

3334
LANGUAGES = {
3435
"Python": "{year}/day{day}/day{day}.py",
@@ -74,10 +75,10 @@ def check_cache(key, file_timestamp: Path):
7475
seconds = round((timestamp - e["timestamp"]) / 1000000000)
7576
delta = timedelta(seconds=seconds)
7677

77-
print(f"{FEINT}{ITALIC}entry {key} is out of date for {delta}{RESET}", end="\r")
78+
print(f"{FEINT}{ITALIC}entry {key} is out of date for {delta}{RESET}", end=f"{CR}")
7879

7980
else:
80-
print(f"{FEINT}{ITALIC}missing cache for {key}{RESET}", end="\r")
81+
print(f"{FEINT}{ITALIC}missing cache for {key}{RESET}", end=f"{CR}")
8182

8283

8384
def update_cache(key, timestamp: Path, elapsed, status, answers):
@@ -152,27 +153,31 @@ def make(year: Path, source: Path, dest: Path, cmd: str):
152153
subprocess.check_call(cmdline, shell=True)
153154

154155

155-
def build_all():
156+
def build_all(filter_year):
156157
for year in range(2015, 2024):
158+
if filter_year != 0 and year != filter_year:
159+
continue
157160
year = Path(str(year))
158161
if not year.is_dir():
159162
continue
160163
m = year / "Cargo.toml"
161164
if year.is_dir() and m.is_file():
162-
print(f"{FEINT}{ITALIC}cargo build {m}{RESET}", end=f"{CLEAR_EOL}\r")
165+
print(f"{FEINT}{ITALIC}cargo build {m}{RESET}", end=f"{CLEAR_EOL}{CR}")
163166
subprocess.check_call(["cargo", "build", "--manifest-path", m, "--release", "--quiet"])
164167

165168
for day in range(1, 26):
166169
src = year / f"day{day}" / f"day{day}.c"
167-
print(f"{FEINT}{ITALIC}compile {src}{RESET}", end=f"{CLEAR_EOL}\r")
168-
make(year, src, f"day{day}_c", "cc -std=c11")
170+
if src.is_file():
171+
print(f"{FEINT}{ITALIC}compile {src}{RESET}", end=f"{CLEAR_EOL}{CR}")
172+
make(year, src, f"day{day}_c", "cc -std=c11")
169173

170174
src = year / f"day{day}" / f"day{day}.cpp"
171-
print(f"{FEINT}{ITALIC}compile {src}{RESET}", end=f"{CLEAR_EOL}\r")
172-
make(year, src, f"day{day}_cpp", "c++ -std=c++17")
175+
if src.is_file():
176+
print(f"{FEINT}{ITALIC}compile {src}{RESET}", end=f"{CLEAR_EOL}{CR}")
177+
make(year, src, f"day{day}_cpp", "c++ -std=c++17")
173178

174179

175-
def load_data():
180+
def load_data(filter_year):
176181
inputs = defaultdict(dict)
177182
solutions = defaultdict(dict)
178183

@@ -182,6 +187,9 @@ def load_data():
182187
year = int(f.parent.name)
183188
day = int(f.stem)
184189

190+
if filter_year != 0 and year != filter_year:
191+
continue
192+
185193
e = check_cache(f, f)
186194
if e:
187195
crc = e["status"]
@@ -246,7 +254,7 @@ def run_day(
246254
status_color = {"fail": MAGENTA, "unknown": GRAY, "error": RED, "ok": GREEN}[e["status"]]
247255

248256
line = (
249-
f"\r{RESET}{CLEAR_EOL}"
257+
f"{CR}{RESET}{CLEAR_EOL}"
250258
f"{prefix}"
251259
f" {YELLOW}{lang:<7}{RESET}:"
252260
f" {status_color}{e['status']:7}{RESET}"
@@ -297,8 +305,8 @@ def main():
297305
problems = []
298306
stats_elapsed = dict()
299307

300-
build_all()
301-
inputs, sols = load_data()
308+
build_all(filter_year)
309+
inputs, sols = load_data(filter_year)
302310

303311
for year in range(2015, 2024):
304312
if filter_year != 0 and year != filter_year:
@@ -350,7 +358,7 @@ def main():
350358
t = sum(t)
351359
print(
352360
f"{YELLOW}{lang:<10}{RESET}"
353-
f" : {GREEN}{t:7.3f}s{RESET} for {n:3} puzzle{'s' if n>1 else ' '},"
361+
f" : {GREEN}{t:7.3f}s{RESET} for {WHITE}{n:3}{RESET} puzzle{'s' if n>1 else ' '},"
354362
f" average: {GREEN}{t/n:7.3f}s{RESET}"
355363
)
356364

@@ -374,7 +382,7 @@ def main():
374382
f"{YELLOW}{lang1:<7}{RESET}"
375383
f" vs. {YELLOW}{lang2:<7}{RESET}:"
376384
f" {GREEN}{t1/n:7.3f}s{RESET} vs. {GREEN}{t2/n:7.3f}s{RESET}"
377-
f" (x {faster:4.1f} faster) on {n:3} puzzle{'s' if n>1 else ''}"
385+
f" (x {faster:4.1f} faster) on {WHITE}{n:3}{RESET} puzzle{'s' if n>1 else ''}"
378386
)
379387

380388
if problems:

0 commit comments

Comments
 (0)