浏览代码

Solutions for advent of code, day 4 - 13. (#4750)

A collection of additional Carbon examples demonstrating current
language capabilities and some limitations of the current state of the
toolchain.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 1 年之前
父节点
当前提交
d2d5c5520b
共有 42 个文件被更改,包括 1839 次插入47 次删除
  1. 18 47
      examples/advent2024/BUILD
  2. 6 0
      examples/advent2024/README.md
  3. 29 0
      examples/advent2024/day10_common.carbon
  4. 99 0
      examples/advent2024/day10_part1.carbon
  5. 72 0
      examples/advent2024/day10_part2.carbon
  6. 31 0
      examples/advent2024/day11_common.carbon
  7. 20 0
      examples/advent2024/day11_part1.carbon
  8. 106 0
      examples/advent2024/day11_part2.carbon
  9. 120 0
      examples/advent2024/day12_common.carbon
  10. 30 0
      examples/advent2024/day12_part1.carbon
  11. 69 0
      examples/advent2024/day12_part2.carbon
  12. 156 0
      examples/advent2024/day13_common.carbon
  13. 22 0
      examples/advent2024/day13_part1.carbon
  14. 24 0
      examples/advent2024/day13_part2.carbon
  15. 2 0
      examples/advent2024/day1_common.carbon
  16. 2 0
      examples/advent2024/day1_part1.carbon
  17. 2 0
      examples/advent2024/day1_part2.carbon
  18. 2 0
      examples/advent2024/day2_common.carbon
  19. 2 0
      examples/advent2024/day2_part1.carbon
  20. 2 0
      examples/advent2024/day2_part2.carbon
  21. 2 0
      examples/advent2024/day3_common.carbon
  22. 2 0
      examples/advent2024/day3_part1.carbon
  23. 2 0
      examples/advent2024/day3_part2.carbon
  24. 58 0
      examples/advent2024/day4_common.carbon
  25. 38 0
      examples/advent2024/day4_part1.carbon
  26. 33 0
      examples/advent2024/day4_part2.carbon
  27. 139 0
      examples/advent2024/day5_common.carbon
  28. 26 0
      examples/advent2024/day5_part1.carbon
  29. 31 0
      examples/advent2024/day5_part2.carbon
  30. 110 0
      examples/advent2024/day6_common.carbon
  31. 16 0
      examples/advent2024/day6_part1.carbon
  32. 63 0
      examples/advent2024/day6_part2.carbon
  33. 58 0
      examples/advent2024/day7_common.carbon
  34. 22 0
      examples/advent2024/day7_part1.carbon
  35. 22 0
      examples/advent2024/day7_part2.carbon
  36. 29 0
      examples/advent2024/day8_common.carbon
  37. 52 0
      examples/advent2024/day8_part1.carbon
  38. 76 0
      examples/advent2024/day8_part2.carbon
  39. 137 0
      examples/advent2024/day9_common.carbon
  40. 16 0
      examples/advent2024/day9_part1.carbon
  41. 16 0
      examples/advent2024/day9_part2.carbon
  42. 77 0
      examples/advent2024/io_utils.carbon

+ 18 - 47
examples/advent2024/BUILD

@@ -9,50 +9,21 @@ utils = [
     "sort.carbon",
 ]
 
-carbon_binary(
-    name = "day1_part1",
-    srcs = [
-        "day1_common.carbon",
-        "day1_part1.carbon",
-    ] + utils,
-)
-
-carbon_binary(
-    name = "day1_part2",
-    srcs = [
-        "day1_common.carbon",
-        "day1_part2.carbon",
-    ] + utils,
-)
-
-carbon_binary(
-    name = "day2_part1",
-    srcs = [
-        "day2_common.carbon",
-        "day2_part1.carbon",
-    ] + utils,
-)
-
-carbon_binary(
-    name = "day2_part2",
-    srcs = [
-        "day2_common.carbon",
-        "day2_part2.carbon",
-    ] + utils,
-)
-
-carbon_binary(
-    name = "day3_part1",
-    srcs = [
-        "day3_common.carbon",
-        "day3_part1.carbon",
-    ] + utils,
-)
-
-carbon_binary(
-    name = "day3_part2",
-    srcs = [
-        "day3_common.carbon",
-        "day3_part2.carbon",
-    ] + utils,
-)
+# A list of examples that should be excluded because they don't build any more.
+# For example, to exclude day3_part2, use:
+# excluded = [(3, 2)]
+excluded = []
+
+# Produce a binary "dayX_partY" for each matching `.carbon` file.
+[
+    carbon_binary(
+        name = "day{0}_part{1}".format(day, part),
+        srcs = [
+            "day{0}_common.carbon".format(day),
+            "day{0}_part{1}.carbon".format(day, part),
+        ] + utils,
+    )
+    for day in range(1, 14)
+    for part in range(1, 3)
+    if (day, part) not in excluded
+]

+ 6 - 0
examples/advent2024/README.md

@@ -15,3 +15,9 @@ style and idioms. Instead, the purpose of these examples are to test the current
 state of the toolchain against larger code examples than those that are present
 in the toolchain's own tests, to find bugs in the toolchain, and to drive
 feature development in the toolchain by presenting somewhat realistic testcases.
+
+If one of these examples stops building after a change to the toolchain, please:
+
+-   Make sure that the build break is an expected consequence of the change.
+-   Update the `BUILD` file to exclude that example.
+-   File an issue and assign it to @zygoloid.

+ 29 - 0
examples/advent2024/day10_common.carbon

@@ -0,0 +1,29 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/10
+
+library "day10_common";
+
+import Core library "io";
+import library "io_utils";
+
+class Terrain {
+  fn Read() -> Terrain {
+    returned var me: Terrain;
+    var y: i32 = 0;
+    while (y < 43) {
+      var x: i32 = 0;
+      while (x < 43) {
+        me.height[x][y] = ReadChar() - 0x30;
+        ++x;
+      }
+      SkipNewline();
+      ++y;
+    }
+    return var;
+  }
+
+  var height: [[i32; 43]; 43];
+}

+ 99 - 0
examples/advent2024/day10_part1.carbon

@@ -0,0 +1,99 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/10
+
+import Core library "io";
+
+import library "day10_common";
+import library "io_utils";
+
+// TODO: Add this to the prelude.
+fn PopCount(n: u256) -> i32 {
+  var bit: u256 = 1;
+  var total: i32 = 0;
+  while (bit != 0) {
+    if (n & bit != 0) {
+      ++total;
+    }
+    bit = bit << 1;
+  }
+  return total;
+}
+
+class Reachable {
+  fn Make(terrain: Terrain) -> Reachable {
+    returned var me: Reachable;
+    var next: u256 = 1;
+    var y: i32 = 0;
+    while (y < 43) {
+      var x: i32 = 0;
+      while (x < 43) {
+        if (terrain.height[x][y] == 0) {
+          me.trailheads[x][y] = next;
+          next = next << 1;
+        }
+        ++x;
+      }
+      ++y;
+    }
+    return var;
+  }
+
+  fn AddLevel[addr self: Self*](terrain: Terrain, level: i32) {
+    let adj: [(i32, i32); 4] = ((-1, 0), (0, -1), (1, 0), (0, 1));
+    var y: i32 = 0;
+    while (y < 43) {
+      var x: i32 = 0;
+      while (x < 43) {
+        if (terrain.height[x][y] == level) {
+          var reach: u256 = 0;
+          var i: i32 = 0;
+          while (i < 4) {
+            let adj_x: i32 = x + adj[i].0;
+            let adj_y: i32 = y + adj[i].1;
+            if (adj_x >= 0 and adj_x < 43 and
+                adj_y >= 0 and adj_y < 43 and
+                terrain.height[adj_x][adj_y] == level - 1) {
+              reach = reach | self->trailheads[adj_x][adj_y];
+            }
+            ++i;
+          }
+          self->trailheads[x][y] = reach;
+        }
+        ++x;
+      }
+      ++y;
+    }
+  }
+
+  fn Count[self: Self](terrain: Terrain, level: i32) -> i32 {
+    var total: i32 = 0;
+    var y: i32 = 0;
+    while (y < 43) {
+      var x: i32 = 0;
+      while (x < 43) {
+        if (terrain.height[x][y] == level) {
+          total += PopCount(self.trailheads[x][y]);
+        }
+        ++x;
+      }
+      ++y;
+    }
+    return total;
+  }
+
+  var trailheads: [[u256; 43]; 43];
+}
+
+fn Run() {
+  var terrain: Terrain = Terrain.Read();
+  var reachable: Reachable = Reachable.Make(terrain);
+  var i: i32 = 1;
+  while (i <= 9) {
+    reachable.AddLevel(terrain, i);
+    ++i;
+  }
+  Core.Print(reachable.Count(terrain, 9));
+}

+ 72 - 0
examples/advent2024/day10_part2.carbon

@@ -0,0 +1,72 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/10
+
+import Core library "io";
+
+import library "day10_common";
+import library "io_utils";
+
+class PathsToTop {
+  fn Make(terrain: Terrain) -> PathsToTop {
+    returned var me: PathsToTop;
+    var y: i32 = 0;
+    while (y < 43) {
+      var x: i32 = 0;
+      while (x < 43) {
+        // TODO: We shouldn't need an explicit cast here.
+        me.paths[x][y] =
+          if terrain.height[x][y] == 9 then 1 as i64 else 0;
+        ++x;
+      }
+      ++y;
+    }
+    return var;
+  }
+
+  fn AddLevel[addr self: Self*](terrain: Terrain, level: i32) -> i64 {
+    var total: i64 = 0;
+    let adj: [(i32, i32); 4] = ((-1, 0), (0, -1), (1, 0), (0, 1));
+    var y: i32 = 0;
+    while (y < 43) {
+      var x: i32 = 0;
+      while (x < 43) {
+        if (terrain.height[x][y] == level) {
+          var paths: i64 = 0;
+          var i: i32 = 0;
+          while (i < 4) {
+            let adj_x: i32 = x + adj[i].0;
+            let adj_y: i32 = y + adj[i].1;
+            if (adj_x >= 0 and adj_x < 43 and
+                adj_y >= 0 and adj_y < 43 and
+                terrain.height[adj_x][adj_y] == level + 1) {
+              paths = paths + self->paths[adj_x][adj_y];
+            }
+            ++i;
+          }
+          self->paths[x][y] = paths;
+          total = total + paths;
+        }
+        ++x;
+      }
+      ++y;
+    }
+    return total;
+  }
+
+  var paths: [[i64; 43]; 43];
+}
+
+fn Run() {
+  var terrain: Terrain = Terrain.Read();
+  var paths: PathsToTop = PathsToTop.Make(terrain);
+  var i: i32 = 8;
+  var total: i64;
+  while (i >= 0) {
+    total = paths.AddLevel(terrain, i);
+    --i;
+  }
+  PrintInt64(total);
+}

+ 31 - 0
examples/advent2024/day11_common.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/11
+
+library "day11_common";
+
+import Core library "io";
+import library "io_utils";
+
+fn Next(n: i64) -> (i64, i64) {
+  if (n == 0) { return (1, -1); }
+  var pow10: i64 = 10;
+  var pow100: i64 = 1;
+  while (n / pow100 >= 100) {
+    pow100 = pow100 * 100;
+    pow10 = pow10 * 10;
+  }
+  if (n / pow100 >= 10) {
+    return (n / pow10, n % pow10);
+  }
+  return (n * 2024, -1);
+}
+
+fn Count(n: i64, depth: i32) -> i32 {
+  if (n == -1) { return 0; }
+  if (depth == 0) { return 1; }
+  let next: (i64, i64) = Next(n);
+  return Count(next.0, depth - 1) + Count(next.1, depth - 1);
+}

+ 20 - 0
examples/advent2024/day11_part1.carbon

@@ -0,0 +1,20 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/11
+
+import Core library "io";
+
+import library "day11_common";
+import library "io_utils";
+
+fn Run() {
+  var n: i64;
+  var total: i32 = 0;
+  while (ReadInt64(&n)) {
+    total = total + Count(n, 25);
+    SkipSpaces();
+  }
+  Core.Print(total);
+}

+ 106 - 0
examples/advent2024/day11_part2.carbon

@@ -0,0 +1,106 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/11
+
+import Core library "io";
+
+import library "day11_common";
+import library "io_utils";
+
+class Digits {
+  fn Make() -> Digits {
+    returned var me: Digits;
+    var digit: i32 = 0;
+    while (digit < 10) {
+      var depth: i32 = 0;
+      while (depth < 75) {
+        me.count[digit][depth] = 0;
+        ++depth;
+      }
+      ++digit;
+    }
+    return var;
+  }
+
+  fn Print[self: Self](max_depth: i32) {
+    var digit: i32 = 0;
+    while (digit < 10) {
+      Core.PrintChar(digit + 0x30);
+      Core.PrintChar(0x3A);
+      var depth: i32 = 0;
+      while (depth <= max_depth) {
+        Core.PrintChar(0x20);
+        PrintInt64NoNewline(self.count[digit][depth]);
+        ++depth;
+      }
+      Core.PrintChar(0x0A);
+      ++digit;
+    }
+    Core.PrintChar(0x0A);
+  }
+
+  var count: [[i64; 75]; 10];
+}
+
+// TODO: Add a builtin to perform integer conversion / truncation.
+fn I64DigitToI32(a: i64) -> i32 {
+  if (a == 0) { return 0; }
+  if (a == 1) { return 1; }
+  if (a == 2) { return 2; }
+  if (a == 3) { return 3; }
+  if (a == 4) { return 4; }
+  if (a == 5) { return 5; }
+  if (a == 6) { return 6; }
+  if (a == 7) { return 7; }
+  if (a == 8) { return 8; }
+  return 9;
+}
+
+fn ReduceToDigits(n: i64, depth: i32, multiplicity: i64, digits: Digits*) -> i64 {
+  if (n == -1) { return 0; }
+  if (depth == 0) { return multiplicity; }
+  if (n < 10) {
+    let count: i64* = &digits->count[I64DigitToI32(n)][depth - 1];
+    *count = *count + multiplicity;
+    return 0;
+  }
+  let next: (i64, i64) = Next(n);
+  return ReduceToDigits(next.0, depth - 1, multiplicity, digits) +
+         ReduceToDigits(next.1, depth - 1, multiplicity, digits);
+}
+
+fn Run() {
+  let max_depth: i32 = 75;
+  var total: i64 = 0;
+  var digits: Digits = Digits.Make();
+
+  var n: i64;
+  while (ReadInt64(&n)) {
+    total = total + ReduceToDigits(n, max_depth, 1, &digits);
+    PrintInt64(total);
+    digits.Print(max_depth - 1);
+    SkipSpaces();
+  }
+
+  var depth: i32 = max_depth - 1;
+  while (depth >= 0) {
+    PrintInt64(total);
+    digits.Print(depth);
+    var digit: i64 = 0;
+    while (digit < 10) {
+      let m: i64 = digits.count[I64DigitToI32(digit)][depth];
+      if (m > 0) {
+        let next: (i64, i64) = Next(digit);
+        total = total +
+                ReduceToDigits(next.0, depth, m, &digits) +
+                ReduceToDigits(next.1, depth, m, &digits);
+      }
+      digit = digit + 1;
+    }
+    --depth;
+  }
+
+  PrintInt64(total);
+}

+ 120 - 0
examples/advent2024/day12_common.carbon

@@ -0,0 +1,120 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/12
+
+library "day12_common";
+
+import Core library "io";
+import library "io_utils";
+
+class Map {
+  fn Read() -> Map {
+    returned var me: Self;
+    var y: i32 = 0;
+    while (y < 140) {
+      var x: i32 = 0;
+      while (x < 140) {
+        me.kind[x][y] = ReadChar();
+        ++x;
+      }
+      SkipNewline();
+      ++y;
+    }
+    return var;
+  }
+
+  fn At[self: Self](x: i32, y: i32) -> i32 {
+    return if x < 0 or x >= 140 or y < 0 or y >= 140
+           then -1
+           else self.kind[x][y];
+  }
+
+  var kind: [[i32; 140]; 140];
+}
+
+class DisjointSetForest {
+  fn Make() -> DisjointSetForest {
+    returned var me: Self;
+    var i: i32 = 0;
+    while (i < 140 * 140) {
+      me.nodes[i].next = i;
+      me.nodes[i].weight = 1;
+      me.nodes[i].unions = 0;
+      ++i;
+    }
+    return var;
+  }
+
+  fn Lookup[addr self: Self*](a: i32) -> i32 {
+    let next: i32 = self->nodes[a].next;
+    if (next == a) {
+      return next;
+    }
+    let resolved: i32 = self->Lookup(next);
+    self->nodes[a].next = resolved;
+    return resolved;
+  }
+
+  fn Unions[self: Self](canon_a: i32) -> i32 {
+    return self.nodes[canon_a].unions;
+  }
+
+  fn Weight[self: Self](canon_a: i32) -> i32 {
+    return self.nodes[canon_a].weight;
+  }
+
+  fn Union[addr self: Self*](a: i32, b: i32) {
+    let canon_b: i32 = self->Lookup(b);
+    self->Set(a, canon_b);
+    ++self->nodes[canon_b].unions;
+  }
+
+  private fn Set[addr self: Self*](a: i32, canon_b: i32) {
+    let next: i32 = self->nodes[a].next;
+    self->nodes[a].next = canon_b;
+    if (next == a) {
+      if (a != canon_b) {
+        self->nodes[canon_b].weight += self->nodes[a].weight;
+        self->nodes[canon_b].unions += self->nodes[a].unions;
+      }
+    } else {
+      self->Set(next, canon_b);
+    }
+  }
+
+  // TODO: Consider adding ranked choice.
+  // TODO: Make this generic in the payload data.
+  var nodes: [{.next: i32, .weight: i32, .unions: i32}; 140 * 140];
+}
+
+fn MakeRegions(map: Map) -> DisjointSetForest {
+  returned var forest: DisjointSetForest =
+    DisjointSetForest.Make();
+
+  var x: i32 = 0;
+  while (x < 140) {
+    var y: i32 = 0;
+    while (y < 140) {
+      var a: i32 = 0;
+      while (a < 2) {
+        // TODO: Crashes toolchain:
+        // let adj: (i32, i32) = if a == 0 then (x - 1, y) else (x, y - 1);
+        // if (map.At(adj.0, adj.1) == map.At(x, y)) {
+        //   forest.Union(y * 140 + x, adj.1 * 140 + adj.0);
+        // }
+        let adj_x: i32 = if a == 0 then x - 1 else x;
+        let adj_y: i32 = if a == 0 then y else y - 1;
+        if (map.At(adj_x, adj_y) == map.At(x, y)) {
+          forest.Union(y * 140 + x, adj_y * 140 + adj_x);
+        }
+        ++a;
+      }
+      ++y;
+    }
+    ++x;
+  }
+
+  return var;
+}

+ 30 - 0
examples/advent2024/day12_part1.carbon

@@ -0,0 +1,30 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/12
+
+import Core library "io";
+
+import library "day12_common";
+import library "io_utils";
+
+fn Run() {
+  var map: Map = Map.Read();
+  var regions: DisjointSetForest = MakeRegions(map);
+
+  var total: i32 = 0;
+
+  var i: i32 = 0;
+  while (i < 140 * 140) {
+    if (regions.Lookup(i) == i) {
+      let area: i32 = regions.Weight(i);
+      let internal_edges: i32 = regions.Unions(i);
+      let perimeter: i32 = area * 4 - internal_edges * 2;
+      total += area * perimeter;
+    }
+    ++i;
+  }
+
+  Core.Print(total);
+}

+ 69 - 0
examples/advent2024/day12_part2.carbon

@@ -0,0 +1,69 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/12
+
+import Core library "io";
+
+import library "day12_common";
+import library "io_utils";
+
+fn CountExtensions(map: Map, regions: DisjointSetForest*) -> [i32; 140 * 140] {
+  returned var extensions: [i32; 140 * 140];
+  var i: i32 = 0;
+  while (i < 140 * 140) {
+    extensions[i] = 0;
+    ++i;
+  }
+
+  var ext: [{.same: (i32, i32), .adj: (i32, i32)}; 4] = (
+    {.same = (-1, 0), .adj = (0, -1)},
+    {.same = (-1, 0), .adj = (0, 1)},
+    {.same = (0, -1), .adj = (-1, 0)},
+    {.same = (0, -1), .adj = (1, 0)},
+  );
+
+  var x: i32 = 0;
+  while (x < 140) {
+    var y: i32 = 0;
+    while (y < 140) {
+      let kind: i32 = map.At(x, y);
+      var e: i32 = 0;
+      while (e < 4) {
+        if (map.At(x + ext[e].same.0, y + ext[e].same.1) == kind and
+            map.At(x + ext[e].adj.0, y + ext[e].adj.1) != kind and
+            map.At(x + ext[e].same.0 + ext[e].adj.0,
+                   y + ext[e].same.1 + ext[e].adj.1) != kind) {
+          ++extensions[regions->Lookup(y * 140 + x)];
+        }
+        ++e;
+      }
+      ++y;
+    }
+    ++x;
+  }
+  return var;
+}
+
+fn Run() {
+  var map: Map = Map.Read();
+  var regions: DisjointSetForest = MakeRegions(map);
+  var ext: [i32; 140 * 140] = CountExtensions(map, &regions);
+
+  var total: i32 = 0;
+
+  var i: i32 = 0;
+  while (i < 140 * 140) {
+    if (regions.Lookup(i) == i) {
+      let area: i32 = regions.Weight(i);
+      let internal_edges: i32 = regions.Unions(i);
+      let extensions: i32 = ext[i];
+      let perimeter: i32 = area * 4 - internal_edges * 2 - extensions;
+      total += area * perimeter;
+    }
+    ++i;
+  }
+
+  Core.Print(total);
+}

+ 156 - 0
examples/advent2024/day13_common.carbon

@@ -0,0 +1,156 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/13
+
+library "day13_common";
+
+import Core library "io";
+import library "io_utils";
+
+// Returns m and n so that am + bn = gcd.
+fn Euclid(a: i64, b: i64) -> {.m: i64, .n: i64, .gcd: i64} {
+  if (a < b) {
+    let reverse: {.m: i64, .n: i64, .gcd: i64} = Euclid(b, a);
+    return {.m = reverse.n, .n = reverse.m, .gcd = reverse.gcd};
+  }
+  if (b == 0) {
+    return {.m = 1, .n = 0, .gcd = a};
+  }
+  let next: {.m: i64, .n: i64, .gcd: i64} = Euclid(b, a % b);
+  return {.m = next.n, .n = next.m - next.n * (a / b), .gcd = next.gcd};
+}
+
+class Machine {
+  fn Read() -> Machine {
+    returned var me: Machine;
+    // "Button A: X+"
+    SkipNChars(12);
+    ReadInt64(&me.a.0);
+    // ", Y+"
+    SkipNChars(4);
+    ReadInt64(&me.a.1);
+    // "\nButton B: X+"
+    SkipNChars(13);
+    ReadInt64(&me.b.0);
+    // ", Y+"
+    SkipNChars(4);
+    ReadInt64(&me.b.1);
+    // "\nPrize: X="
+    SkipNChars(10);
+    ReadInt64(&me.prize.0);
+    // ", Y="
+    SkipNChars(4);
+    ReadInt64(&me.prize.1);
+    SkipNewline();
+    return var;
+  }
+
+  var a: (i64, i64);
+  var b: (i64, i64);
+  var prize: (i64, i64);
+}
+
+// Set of solutions to 'm a + n b = c'.
+class BezoutSolutionSet {
+  fn Make(a: i64, b: i64, c: i64) -> BezoutSolutionSet {
+    var e: {.m: i64, .n: i64, .gcd: i64} = Euclid(a, b);
+    if (c % e.gcd != 0) {
+      // Impossible.
+      return {.m0 = -1, .n0 = -1, .m_step = -1, .n_step = -1};
+    }
+
+    // Find an initial solution. Note that m and n might be negative.
+    let num_gcds: i64 = c / e.gcd;
+    e.m = e.m * num_gcds;
+    e.n = e.n * num_gcds;
+
+    // Pick the smallest positive m we can.
+    let a_over_gcd: i64 = a / e.gcd;
+    let b_over_gcd: i64 = b / e.gcd;
+    var adj: i64 = 0;
+    // This is e.m / b rounded towards -inf.
+    // TODO: Should there be a way of expressing this directly?
+    if (e.m < 0) {
+      adj = (e.m - b_over_gcd + 1) / b_over_gcd;
+    } else {
+      adj = e.m / b_over_gcd;
+    }
+    e.m = e.m - adj * b_over_gcd;
+    e.n = e.n + adj * a_over_gcd;
+    return {.m0 = e.m, .n0 = e.n,
+            .m_step = b_over_gcd, .n_step = -a_over_gcd};
+  }
+
+  fn Valid[self: Self]() -> bool {
+    return self.m_step >= 0;
+  }
+
+  fn Solution[self: Self](k: i64) -> (i64, i64) {
+    return (self.m0 + k * self.m_step, self.n0 + k * self.n_step);
+  }
+
+  // m_k * a + n_k * b == c, where:
+  //   m_k = m0 + k * m_step
+  //   n_k = n0 * k * n_step
+  // m0 is the minimum non-negative m value, and n_step is negative.
+  var m0: i64;
+  var n0: i64;
+  var m_step: i64;
+  var n_step: i64;
+}
+
+// Given two sets of points s and t, find the intersection in the first quadrant
+// with the minimum x coordinate. Returns the intersection point, or (-1, -1) if
+// there is no intersection.
+fn FirstIntersection(s: BezoutSolutionSet, t: BezoutSolutionSet) -> (i64, i64) {
+  if (not s.Valid() or not t.Valid()) {
+    // One of the sets is empty.
+    return (-1, -1);
+  }
+
+  // Easy case: lines meet at a point. This happens unless the lines are
+  // parallel.
+  let d: i64 = s.m_step * t.n_step - t.m_step * s.n_step;
+  if (d != 0) {
+    let u: i64 = (t.m0 - s.m0) * t.n_step - (t.n0 - s.n0) * t.m_step;
+    if (u % d != 0) {
+      // Lines don't meet at an integer point.
+      return (-1, -1);
+    }
+    let j: i64 = u / d;
+    if (j < 0 or s.n0 + j * s.n_step < 0) {
+      // Lines don't meet in first quadrant.
+      return (-1, -1);
+    }
+    return s.Solution(j);
+  }
+
+  // Hard case: lines are parallel. We also get here if either "line" is a
+  // point, but that doesn't happen in this exercise. We know all integer points
+  // on the line are solutions, so the first intersection is the greater of the
+  // first point in s and the first point in t, if that point is on both lines.
+  if (s.m0 < t.m0) {
+    // (t.m0, t.n0) is the solution if it's in s.
+    if ((t.m0 - s.m0) % s.m_step == 0 and
+        (t.n0 - s.n0) % s.n_step == 0) {
+      return (t.m0, t.n0);
+    }
+  } else {
+    // (s.m0, s.n0) is the solution if it's in t.
+    if ((s.m0 - t.m0) % t.m_step == 0 and
+        (s.n0 - t.n0) % t.n_step == 0) {
+      return (s.m0, s.n0);
+    }
+  }
+  return (-1, -1);
+}
+
+fn CostIfPossible(m: Machine) -> i64 {
+  let x: BezoutSolutionSet = BezoutSolutionSet.Make(m.a.0, m.b.0, m.prize.0);
+  let y: BezoutSolutionSet = BezoutSolutionSet.Make(m.a.1, m.b.1, m.prize.1);
+  let ab: (i64, i64) = FirstIntersection(x, y);
+  if (ab.0 == -1) { return 0; }
+  return ab.0 * 3 + ab.1;
+}

+ 22 - 0
examples/advent2024/day13_part1.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/13
+
+import Core library "io";
+
+import library "day13_common";
+import library "io_utils";
+
+fn Run() {
+  var total_cost: i64 = 0;
+  while (true) {
+    var m: Machine = Machine.Read();
+    total_cost = total_cost + CostIfPossible(m);
+    if (not SkipNewline()) {
+      break;
+    }
+  }
+  PrintInt64(total_cost);
+}

+ 24 - 0
examples/advent2024/day13_part2.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/13
+
+import Core library "io";
+
+import library "day13_common";
+import library "io_utils";
+
+fn Run() {
+  var total_cost: i64 = 0;
+  while (true) {
+    var m: Machine = Machine.Read();
+    m.prize.0 = m.prize.0 + 10000000000000;
+    m.prize.1 = m.prize.1 + 10000000000000;
+    total_cost = total_cost + CostIfPossible(m);
+    if (not SkipNewline()) {
+      break;
+    }
+  }
+  PrintInt64(total_cost);
+}

+ 2 - 0
examples/advent2024/day1_common.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/1
+
 library "day1_common";
 
 import library "io_utils";

+ 2 - 0
examples/advent2024/day1_part1.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/1
+
 import Core library "io";
 
 import library "day1_common";

+ 2 - 0
examples/advent2024/day1_part2.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/1
+
 import Core library "io";
 
 import library "day1_common";

+ 2 - 0
examples/advent2024/day2_common.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/2
+
 library "day2_common";
 
 fn Abs(n: i32) -> i32 {

+ 2 - 0
examples/advent2024/day2_part1.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/2
+
 import Core library "io";
 
 import library "day2_common";

+ 2 - 0
examples/advent2024/day2_part2.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/2
+
 import Core library "io";
 
 import library "day2_common";

+ 2 - 0
examples/advent2024/day3_common.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/3
+
 library "day3_common";
 
 import library "io_utils";

+ 2 - 0
examples/advent2024/day3_part1.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/3
+
 import Core library "io";
 
 import library "day3_common";

+ 2 - 0
examples/advent2024/day3_part2.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+// https://adventofcode.com/2024/day/3
+
 import Core library "io";
 
 import library "day3_common";

+ 58 - 0
examples/advent2024/day4_common.carbon

@@ -0,0 +1,58 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/4
+
+library "day4_common";
+
+import library "io_utils";
+
+class Wordsearch {
+  fn Read() -> Wordsearch {
+    returned var s: Wordsearch;
+    // TODO: Use for loops once they're implemented.
+    var y: i32 = 0;
+    while (y < 140) {
+      var x: i32 = 0;
+      while (x < 140) {
+        // TODO: Assert on failure.
+        s.grid[x][y] = ReadChar();
+        ++x;
+      }
+      // TODO: Assert on failure.
+      SkipNewline();
+      ++y;
+    }
+    return var;
+  }
+
+  fn At[self: Self](x: i32, y: i32) -> i32 {
+    return if x < 0 or x >= 140 or y < 0 or y >= 140 then -1 else self.grid[x][y];
+  }
+
+  // TODO: Make this generic in the length of the search query.
+  fn Check4[self: Self](xmas: [i32; 4], x: i32, y: i32, dx: i32, dy: i32) -> bool {
+    var i: i32 = 0;
+    while (i < 4) {
+      if (self.At(x + i * dx, y + i * dy) != xmas[i]) {
+        return false;
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  fn Check3[self: Self](mas: [i32; 3], x: i32, y: i32, dx: i32, dy: i32) -> bool {
+    var i: i32 = 0;
+    while (i < 3) {
+      if (self.At(x + i * dx, y + i * dy) != mas[i]) {
+        return false;
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  var grid: [[i32; 140]; 140];
+}

+ 38 - 0
examples/advent2024/day4_part1.carbon

@@ -0,0 +1,38 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/4
+
+import Core library "io";
+
+import library "day4_common";
+import library "io_utils";
+
+fn Run() {
+  var search: Wordsearch = Wordsearch.Read();
+  var xmas: [i32; 4] = (0x58, 0x4D, 0x41, 0x53);
+  var found: i32 = 0;
+
+  // TODO: Use for loops once they're implemented.
+  var y: i32 = 0;
+  while (y < 140) {
+    var x: i32 = 0;
+    while (x < 140) {
+      var dy: i32 = -1;
+      while (dy <= 1) {
+        var dx: i32 = -1;
+        while (dx <= 1) {
+          if (search.Check4(xmas, x, y, dx, dy)) {
+            ++found;
+          }
+          ++dx;
+        }
+        ++dy;
+      }
+      ++x;
+    }
+    ++y;
+  }
+  Core.Print(found);
+}

+ 33 - 0
examples/advent2024/day4_part2.carbon

@@ -0,0 +1,33 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/4
+
+import Core library "io";
+
+import library "day4_common";
+import library "io_utils";
+
+fn Run() {
+  var search: Wordsearch = Wordsearch.Read();
+  var mas: [i32; 3] = (0x4D, 0x41, 0x53);
+  var found: i32 = 0;
+
+  // TODO: Use for loops once they're implemented.
+  var y: i32 = 1;
+  while (y < 139) {
+    var x: i32 = 1;
+    while (x < 139) {
+      if ((search.Check3(mas, x - 1, y - 1, 1, 1) or
+           search.Check3(mas, x + 1, y + 1, -1, -1)) and
+          (search.Check3(mas, x - 1, y + 1, 1, -1) or
+           search.Check3(mas, x + 1, y - 1, -1, 1))) {
+        ++found;
+      }
+      ++x;
+    }
+    ++y;
+  }
+  Core.Print(found);
+}

+ 139 - 0
examples/advent2024/day5_common.carbon

@@ -0,0 +1,139 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/5
+
+library "day5_common";
+
+import library "io_utils";
+
+// fn LeftShift(a: Core.UInt(100), b: i32) -> Core.UInt(100) = "int.left_shift";
+fn LeftShift_u100(a: Core.UInt(100), b: Core.UInt(100)) -> Core.UInt(100) = "int.left_shift";
+
+// TODO: Add a builtin that can do this!
+fn Promote(a: i32) -> Core.UInt(100) {
+  var result: Core.UInt(100) = 0;
+  var bit_i32: i32 = 1;
+  var bit_u100: Core.UInt(100) = 1;
+  while (bit_i32 != 0) {
+    if (a & bit_i32 != 0) {
+      result = result | bit_u100;
+    }
+    bit_i32 <<= 1;
+    // TODO: bit_u100 <<= 1;
+    bit_u100 = bit_u100 << 1;
+  }
+  return result;
+}
+
+fn LeftShift(a: Core.UInt(100), b: i32) -> Core.UInt(100) {
+  return LeftShift_u100(a, Promote(b));
+}
+
+fn PageMask(page: i32) -> Core.UInt(100) {
+  return LeftShift(1 as Core.UInt(100), page);
+}
+
+class Rules {
+  fn Read() -> Rules {
+    returned var rules: Rules;
+    var i: i32 = 0;
+    while (i < 100) {
+      rules.disallowed_before[i] = 0;
+      ++i;
+    }
+
+    var a: i32;
+    var b: i32;
+    while (ReadInt(&a) and ConsumeChar(0x7C) and ReadInt(&b)) {
+      // TODO: rules.disallowed_before[a] |= PageMask(b);
+      rules.disallowed_before[a] = rules.disallowed_before[a] | PageMask(b);
+      SkipNewline();
+    }
+    return var;
+  }
+
+  fn IsValidOrder[self: Self](a: i32, b: i32) -> bool {
+    return self.disallowed_before[b] & PageMask(a) == 0;
+  }
+
+  var disallowed_before: [Core.UInt(100); 100];
+};
+
+class PageList {
+  fn Empty() -> PageList {
+    returned var me: PageList;
+    me.num_pages = 0;
+    return var;
+  }
+
+  fn Read() -> PageList {
+    returned var me: PageList = Empty();
+
+    var page: i32;
+    if (not ReadInt(&page)) {
+      return var;
+    }
+    me.Add(page);
+    while (ConsumeChar(0x2C)) {
+      ReadInt(&page);
+      me.Add(page);
+    }
+    SkipNewline();
+    return var;
+  }
+
+  fn Add[addr self: Self*](page: i32) {
+    self->pages[self->num_pages] = page;
+    ++self->num_pages;
+  }
+
+  fn FollowsRules[self: Self](rules: Rules) -> bool {
+    var seen: Core.UInt(100) = 0;
+    var i: i32 = 0;
+    while (i < self.num_pages) {
+      let page: i32 = self.pages[i];
+      if (seen & rules.disallowed_before[page] != 0) {
+        return false;
+      }
+      // TODO: seen |= PageMask(page);
+      seen = seen | PageMask(page);
+      ++i;
+    }
+    return true;
+  }
+
+  fn IsPossibleFirstPage[self: Self](rules: Rules, page: i32) -> bool {
+    var i: i32 = 0;
+    while (i < self.num_pages) {
+      if (not rules.IsValidOrder(page, self.pages[i])) {
+        return false;
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  fn ExtractPossibleFirstPage[addr self: Self*](rules: Rules) -> i32 {
+    var i: i32 = 0;
+    while (i < self->num_pages) {
+      var page: i32 = self->pages[i];
+      if (self->IsPossibleFirstPage(rules, page)) {
+        self->pages[i] = self->pages[self->num_pages - 1];
+        --self->num_pages;
+        return page;
+      }
+      ++i;
+    }
+    // TODO: Assert.
+    return 0;
+  }
+
+  fn MiddlePage[self: Self]() -> i32 {
+    return self.pages[self.num_pages / 2];
+  }
+
+  var pages: [i32; 24];
+  var num_pages: i32;
+};

+ 26 - 0
examples/advent2024/day5_part1.carbon

@@ -0,0 +1,26 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/5
+
+import Core library "io";
+
+import library "day5_common";
+import library "io_utils";
+
+fn Run() {
+  var rules: Rules = Rules.Read();
+  SkipNewline();
+  var total: i32 = 0;
+  while (true) {
+    var page_list: PageList = PageList.Read();
+    if (page_list.num_pages == 0) {
+      break;
+    }
+    if (page_list.FollowsRules(rules)) {
+      total += page_list.MiddlePage();
+    }
+  }
+  Core.Print(total);
+}

+ 31 - 0
examples/advent2024/day5_part2.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/5
+
+import Core library "io";
+
+import library "day5_common";
+import library "io_utils";
+
+fn Run() {
+  var rules: Rules = Rules.Read();
+  SkipNewline();
+  var total: i32 = 0;
+  while (true) {
+    var page_list: PageList = PageList.Read();
+    if (page_list.num_pages == 0) {
+      break;
+    }
+    if (page_list.FollowsRules(rules)) {
+      continue;
+    }
+    var new_page_list: PageList = PageList.Empty();
+    while (page_list.num_pages != 0) {
+      new_page_list.Add(page_list.ExtractPossibleFirstPage(rules));
+    }
+    total += new_page_list.MiddlePage();
+  }
+  Core.Print(total);
+}

+ 110 - 0
examples/advent2024/day6_common.carbon

@@ -0,0 +1,110 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/6
+
+library "day6_common";
+
+import Core library "io";
+import library "io_utils";
+
+// TODO: Use a choice type.
+fn Empty() -> i8 { return 0; }
+fn Visited() -> i8 { return 1; }
+fn Wall() -> i8 { return 2; }
+
+class Maze {
+  fn Read() -> Maze {
+    returned var me: Maze;
+
+    var y: i32 = 0;
+    while (y < 130) {
+      var x: i32 = 0;
+      while (x < 130) {
+        var cell: i32 = Core.ReadChar();
+        if (cell == 0x23) {
+          me.data[x][y] = Wall();
+        } else if (cell == 0x5E) {
+          // TODO: Handle other starting directions?
+          me.data[x][y] = Visited();
+          me.loc = (x, y);
+          me.dir = (0, -1);
+        } else {
+          me.data[x][y] = Empty();
+        }
+        ++x;
+      }
+      SkipNewline();
+      ++y;
+    }
+    return var;
+  }
+
+  fn Copy[self: Self]() -> Maze {
+    returned var copy: Maze;
+    var y: i32 = 0;
+    while (y < 130) {
+      var x: i32 = 0;
+      while (x < 130) {
+        copy.data[x][y] = self.data[x][y];
+        ++x;
+      }
+      ++y;
+    }
+    copy.loc = self.loc;
+    copy.dir = self.dir;
+    return var;
+  }
+
+  fn Step[addr self: Self*]() -> bool {
+    let x: i32 = self->loc.0 + self->dir.0;
+    let y: i32 = self->loc.1 + self->dir.1;
+    if (x < 0 or x >= 130 or y < 0 or y >= 130) {
+      return false;
+    }
+    if (self->data[x][y] == Wall()) {
+      // TODO: Should this work?
+      // self->dir = (-self->dir.1, self->dir.0);
+      let d: (i32, i32) = self->dir;
+      self->dir = (-d.1, d.0);
+    } else {
+      self->loc = (x, y);
+      self->data[x][y] = Visited();
+    }
+    return true;
+  }
+
+  fn AddObstacle[addr self: Self*]() -> bool {
+    let x: i32 = self->loc.0 + self->dir.0;
+    let y: i32 = self->loc.1 + self->dir.1;
+    if (x < 0 or x >= 130 or y < 0 or y >= 130) {
+      return false;
+    }
+    if (self->data[x][y] != Empty()) {
+      return false;
+    }
+    self->data[x][y] = Wall();
+    return true;
+  }
+
+  fn CountVisited[self: Self]() -> i32 {
+    var total: i32 = 0;
+    var y: i32 = 0;
+    while (y < 130) {
+      var x: i32 = 0;
+      while (x < 130) {
+        if (self.data[x][y] == Visited()) {
+          ++total;
+        }
+        ++x;
+      }
+      ++y;
+    }
+    return total;
+  }
+
+  var data: [[i8; 130]; 130];
+  var loc: (i32, i32);
+  var dir: (i32, i32);
+}

+ 16 - 0
examples/advent2024/day6_part1.carbon

@@ -0,0 +1,16 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/6
+
+import Core library "io";
+
+import library "day6_common";
+
+fn Run() {
+  var maze: Maze = Maze.Read();
+  while (maze.Step()) {
+  }
+  Core.Print(maze.CountVisited());
+}

+ 63 - 0
examples/advent2024/day6_part2.carbon

@@ -0,0 +1,63 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/6
+
+import Core library "io";
+
+import library "day6_common";
+
+class LoopDetector {
+  fn Make() -> LoopDetector {
+    return {.last = ((-1, -1), (-1, -1)), .steps = 1, .next_steps = 1};
+  }
+
+  fn Check[addr self: Self*](next: ((i32, i32), (i32, i32))) -> bool {
+    // TODO: if (next == self->last) {
+    // TODO: The lexer mishandles `next.0.0` as `next` `.` `0.0`.
+    if (next.0 .0 == self->last.0 .0 and next.0 .1 == self->last.0 .1 and
+        next.1 .0 == self->last.1 .0 and next.1 .1 == self->last.1 .1) {
+      return true;
+    }
+    --self->steps;
+    if (self->steps == 0) {
+      self->steps = self->next_steps;
+      self->next_steps <<= 1;
+      self->last = next;
+    }
+    return false;
+  }
+
+  var last: ((i32, i32), (i32, i32));
+  var steps: i32;
+  var next_steps: i32;
+}
+
+fn AddingObstacleMakesALoop(position: Maze) -> bool {
+  var maze: Maze = position.Copy();
+  var loop: LoopDetector = LoopDetector.Make();
+  if (not maze.AddObstacle()) {
+    return false;
+  }
+  while (maze.Step()) {
+    if (loop.Check((maze.loc, maze.dir))) {
+      return true;
+    }
+  }
+  return false;
+}
+
+fn Run() {
+  var maze: Maze = Maze.Read();
+  var loops: i32 = 0;
+  while (true) {
+    if (AddingObstacleMakesALoop(maze)) {
+      ++loops;
+    }
+    if (not maze.Step()) {
+      break;
+    }
+  }
+  Core.Print(loops);
+}

+ 58 - 0
examples/advent2024/day7_common.carbon

@@ -0,0 +1,58 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/7
+
+library "day7_common";
+
+import Core library "io";
+import library "io_utils";
+
+fn Concat(a_val: i64, b_val: i64) -> i64 {
+  var a: i64 = a_val;
+  var b: i64 = b_val;
+  if (b == 0) {
+    return a * 10;
+  }
+  while (b != 0) {
+    a = a * 10;
+    b = b / 10;
+  }
+  return a + b_val;
+}
+
+class Equation {
+  fn Read() -> Equation {
+    returned var me: Equation;
+    me.num_operands = 0;
+    ReadInt64(&me.result);
+    ConsumeChar(0x3A);
+    while (ConsumeChar(0x20)) {
+      ReadInt64(&me.operands[me.num_operands]);
+      ++me.num_operands;
+    }
+    while (SkipNewline()) {}
+    return var;
+  }
+
+  fn SolveFrom[self: Self](start: i32, value: i64, concat: bool) -> bool {
+    if (value > self.result) {
+      return false;
+    }
+    if (start == self.num_operands) {
+      return value == self.result;
+    }
+    return self.SolveFrom(start + 1, value + self.operands[start], concat) or
+           self.SolveFrom(start + 1, value * self.operands[start], concat) or
+           (concat and self.SolveFrom(start + 1, Concat(value, self.operands[start]), true));
+  }
+
+  fn Solve[self: Self](concat: bool) -> bool {
+    return self.SolveFrom(1, self.operands[0], concat);
+  }
+
+  var operands: [i64; 16];
+  var num_operands: i32;
+  var result: i64;
+}

+ 22 - 0
examples/advent2024/day7_part1.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/7
+
+import Core library "io";
+
+import library "day7_common";
+import library "io_utils";
+
+fn Run() {
+  var total: i64 = 0;
+  while (PeekChar() != Core.EOF()) {
+    var eq: Equation = Equation.Read();
+    if (eq.Solve(false)) {
+      // TODO: total += eq.result;
+      total = total + eq.result;
+    }
+  }
+  PrintInt64(total);
+}

+ 22 - 0
examples/advent2024/day7_part2.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/7
+
+import Core library "io";
+
+import library "day7_common";
+import library "io_utils";
+
+fn Run() {
+  var total: i64 = 0;
+  while (PeekChar() != Core.EOF()) {
+    var eq: Equation = Equation.Read();
+    if (eq.Solve(true)) {
+      // TODO: total += eq.result;
+      total = total + eq.result;
+    }
+  }
+  PrintInt64(total);
+}

+ 29 - 0
examples/advent2024/day8_common.carbon

@@ -0,0 +1,29 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/8
+
+library "day8_common";
+
+import Core library "io";
+import library "io_utils";
+
+class Grid {
+  fn Read() -> Grid {
+    returned var me: Grid;
+    var y: i32 = 0;
+    while (y < 50) {
+      var x: i32 = 0;
+      while (x < 50) {
+        me.data[x][y] = ReadChar();
+        ++x;
+      }
+      SkipNewline();
+      ++y;
+    }
+    return var;
+  }
+
+  var data: [[i32; 50]; 50];
+}

+ 52 - 0
examples/advent2024/day8_part1.carbon

@@ -0,0 +1,52 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/8
+
+import Core library "io";
+
+import library "day8_common";
+import library "io_utils";
+
+fn IsAntinode(grid: Grid, ox: i32, oy: i32) -> bool {
+  var ay: i32 = 0;
+  while (ay < 50) {
+    let by: i32 = ay * 2 - oy;
+    if (by >= 0 and by < 50) {
+      var ax: i32 = 0;
+      while (ax < 50) {
+        let bx: i32 = ax * 2 - ox;
+        if (bx >= 0 and bx < 50 and (ax != bx or ay != by)) {
+          if (grid.data[ax][ay] != 0x2E and
+              grid.data[ax][ay] == grid.data[bx][by]) {
+            return true;
+          }
+        }
+        ++ax;
+      }
+    }
+    ++ay;
+  }
+  return false;
+}
+
+fn CountAntinodes(grid: Grid) -> i32 {
+  var count: i32 = 0;
+  var y: i32 = 0;
+  while (y < 50) {
+    var x: i32 = 0;
+    while (x < 50) {
+      if (IsAntinode(grid, x, y)) {
+        ++count;
+      }
+      ++x;
+    }
+    ++y;
+  }
+  return count;
+}
+
+fn Run() {
+  Core.Print(CountAntinodes(Grid.Read()));
+}

+ 76 - 0
examples/advent2024/day8_part2.carbon

@@ -0,0 +1,76 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/8
+
+import Core library "io";
+
+import library "day8_common";
+import library "io_utils";
+
+fn MarkAndCount(marks: [[bool; 50]; 50]*, x: i32, y: i32) -> i32 {
+  if (not (*marks)[x][y]) {
+    (*marks)[x][y] = true;
+    return 1;
+  }
+  return 0;
+}
+
+fn MarkAndCountAntinodesFor(grid: Grid, marks: [[bool; 50]; 50]*, ax: i32, ay: i32) -> i32 {
+  var count: i32 = 0;
+  var by: i32 = 0;
+  while (by < 50) {
+    var bx: i32 = 0;
+    while (bx < 50) {
+      let dx: i32 = bx - ax;
+      let dy: i32 = by - ay;
+      if (grid.data[bx][by] != grid.data[ax][ay] or (dx == 0 and dy == 0)) {
+        ++bx;
+        continue;
+      }
+
+      var x: i32 = bx;
+      var y: i32 = by;
+      while (x >= 0 and x < 50 and y >= 0 and y < 50) {
+        count += MarkAndCount(marks, x, y);
+        x += dx;
+        y += dy;
+      }
+      ++bx;
+    }
+    ++by;
+  }
+  return count;
+}
+
+fn MarkAndCountAntinodes(grid: Grid, marks: [[bool; 50]; 50]*) -> i32 {
+  var count: i32 = 0;
+  var y: i32 = 0;
+  while (y < 50) {
+    var x: i32 = 0;
+    while (x < 50) {
+      if (grid.data[x][y] != 0x2E) {
+        count += MarkAndCountAntinodesFor(grid, marks, x, y);
+      }
+      ++x;
+    }
+    ++y;
+  }
+  return count;
+}
+
+fn Run() {
+  var marks: [[bool; 50]; 50];
+  var y: i32 = 0;
+  while (y < 50) {
+    var x: i32 = 0;
+    while (x < 50) {
+      marks[x][y] = false;
+      ++x;
+    }
+    ++y;
+  }
+
+  Core.Print(MarkAndCountAntinodes(Grid.Read(), &marks));
+}

+ 137 - 0
examples/advent2024/day9_common.carbon

@@ -0,0 +1,137 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/9
+
+library "day9_common";
+
+import Core library "io";
+import library "io_utils";
+
+// TODO: Add a builtin that can do this!
+fn Promote(a: i32) -> i64 {
+  var result: i64 = 0;
+  var bit_i32: i32 = 1;
+  var bit_i64: i64 = 1;
+  while (bit_i32 != 0) {
+    if (a & bit_i32 != 0) {
+      result = result | bit_i64;
+    }
+    bit_i32 <<= 1;
+    // TODO: bit_i64 <<= 1;
+    bit_i64 = bit_i64 << 1;
+  }
+  return result;
+}
+
+class SectorList {
+  fn Read() -> SectorList {
+    returned var me: SectorList;
+    me.size = 0;
+    var sector: i32 = 0;
+    var used: bool = true;
+    // TODO: An assignment expression would be useful here.
+    var c: i32 = ReadChar();
+    while (c != Core.EOF() and c != 0xA) {
+      let v: i32 = if used then sector else -1;
+      var n: i32 = c - 0x30;
+      while (n > 0) {
+        me.data[me.size] = v;
+        ++me.size;
+        --n;
+      }
+      if (used) {
+        ++sector;
+      }
+      used = not used;
+      c = ReadChar();
+    }
+    return var;
+  }
+
+  fn DefragBlocks[addr self: Self*]() {
+    var i: i32 = 0;
+    var j: i32 = self->size - 1;
+    while (j > i) {
+      if (self->data[i] != -1) {
+        ++i;
+      } else if (self->data[j] == -1) {
+        --j;
+      } else {
+        self->data[i] = self->data[j];
+        ++i;
+        --j;
+      }
+    }
+    self->size = j;
+  }
+
+  fn HasSpace[addr self: Self*](start: i32, size: i32) -> bool {
+    var i: i32 = start;
+    while (i < start + size) {
+      if (self->data[i] != -1) {
+        return false;
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  fn Fill[addr self: Self*](start: i32, size: i32, sector: i32) {
+    var i: i32 = start;
+    while (i < start + size) {
+      self->data[i] = sector;
+      ++i;
+    }
+  }
+
+  fn DefragFiles[addr self: Self*]() {
+    var j: i32 = self->size - 1;
+    while (j >= 0) {
+      // Skip empty sectors.
+      while (j >= 0 and self->data[j] == -1) {
+        --j;
+      }
+
+      // Find the file size and sector and delete the file.
+      let last: i32 = j;
+      let sector: i32 = self->data[last];
+      while (j >= 0 and self->data[j] == sector) {
+        self->data[j] = -1;
+        --j;
+      }
+      let first: i32 = j + 1;
+      let size: i32 = last - first + 1;
+
+      // Find the first available space for the file.
+      var i: i32 = 0;
+      while (not self->HasSpace(i, size)) {
+        ++i;
+        if (i + size > first) {
+          // If it doesn't fit to the left of its old position, put it back
+          // where it was.
+          i = first;
+        }
+      }
+
+      // Insert it.
+      self->Fill(i, size, sector);
+    }
+  }
+
+  fn Checksum[self: Self]() -> i64 {
+    var total: i64 = 0;
+    var i: i32 = 0;
+    while (i < self.size) {
+      if (self.data[i] != -1) {
+        total = total + Promote(i * self.data[i]);
+      }
+      ++i;
+    }
+    return total;
+  }
+
+  var data: [i32; 200000];
+  var size: i32;
+}

+ 16 - 0
examples/advent2024/day9_part1.carbon

@@ -0,0 +1,16 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/9
+
+import Core library "io";
+
+import library "day9_common";
+import library "io_utils";
+
+fn Run() {
+  var list: SectorList = SectorList.Read();
+  list.DefragBlocks();
+  PrintInt64(list.Checksum());
+}

+ 16 - 0
examples/advent2024/day9_part2.carbon

@@ -0,0 +1,16 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// https://adventofcode.com/2024/day/9
+
+import Core library "io";
+
+import library "day9_common";
+import library "io_utils";
+
+fn Run() {
+  var list: SectorList = SectorList.Read();
+  list.DefragFiles();
+  PrintInt64(list.Checksum());
+}

+ 77 - 0
examples/advent2024/io_utils.carbon

@@ -26,6 +26,7 @@ fn UnreadChar(c: i32) {
   unread_char = c + 2;
 }
 
+// TODO: Make generic.
 fn ReadInt(p: i32*) -> bool {
   var read_any_digits: bool = false;
   *p = 0;
@@ -44,6 +45,71 @@ fn ReadInt(p: i32*) -> bool {
   return read_any_digits;
 }
 
+fn ReadInt64(p: i64*) -> bool {
+  var read_any_digits: bool = false;
+  *p = 0;
+
+  while (true) {
+    var c: i32 = ReadChar();
+    if (c < 0x30 or c > 0x39) {
+      UnreadChar(c);
+      break;
+    }
+    // TODO: Check for overflow.
+    *p = *p * 10;
+    if (c == 0x31) {
+      *p = *p + 1;
+    } else if (c == 0x32) {
+      *p = *p + 2;
+    } else if (c == 0x33) {
+      *p = *p + 3;
+    } else if (c == 0x34) {
+      *p = *p + 4;
+    } else if (c == 0x35) {
+      *p = *p + 5;
+    } else if (c == 0x36) {
+      *p = *p + 6;
+    } else if (c == 0x37) {
+      *p = *p + 7;
+    } else if (c == 0x38) {
+      *p = *p + 8;
+    } else if (c == 0x39) {
+      *p = *p + 9;
+    }
+    read_any_digits = true;
+  }
+  return read_any_digits;
+}
+
+fn PrintInt64NoNewline(n_val: i64) {
+  var pow10: i64 = 1;
+  var n: i64 = n_val;
+  while (n / 10 >= pow10) {
+    pow10 = pow10 * 10;
+  }
+  while (pow10 != 0) {
+    let d: i64 = n / pow10;
+    // TODO: Core.PrintChar(0x30 + (d as i32));
+    if (d == 0) { Core.PrintChar(0x30); }
+    else if (d == 1) { Core.PrintChar(0x31); }
+    else if (d == 2) { Core.PrintChar(0x32); }
+    else if (d == 3) { Core.PrintChar(0x33); }
+    else if (d == 4) { Core.PrintChar(0x34); }
+    else if (d == 5) { Core.PrintChar(0x35); }
+    else if (d == 6) { Core.PrintChar(0x36); }
+    else if (d == 7) { Core.PrintChar(0x37); }
+    else if (d == 8) { Core.PrintChar(0x38); }
+    else if (d == 9) { Core.PrintChar(0x39); }
+    n = n % pow10;
+    pow10 = pow10 / 10;
+  }
+}
+
+fn PrintInt64(n_val: i64) {
+  PrintInt64NoNewline(n_val);
+  Core.PrintChar(0xA);
+}
+
 fn PeekChar() -> i32 {
   var next: i32 = ReadChar();
   UnreadChar(next);
@@ -74,3 +140,14 @@ fn SkipNewline() -> bool {
   // TODO: Unread the CR if it was present?
   return ConsumeChar(0x0A);
 }
+
+fn SkipNChars(n: i32) -> bool {
+  var i: i32 = 0;
+  while (i < n) {
+    if (ReadChar() == Core.EOF()) {
+      return false;
+    }
+    ++i;
+  }
+  return true;
+}