diff --git a/examples/advent2024/BUILD b/examples/advent2024/BUILD index c9dc82cce0b0..f3bac5ae8440 100644 --- a/examples/advent2024/BUILD +++ b/examples/advent2024/BUILD @@ -11,19 +11,19 @@ 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 = ["day3_part2.carbon"] excluded = [] -# Produce a binary "dayX_partY" for each matching `.carbon` file. +# Produce a binary `dayX_partY` for each matching `.carbon` file. Each binary +# depends on `dayX_partY.carbon` and `dayX_common.carbon`. [ carbon_binary( - name = "day{0}_part{1}".format(day, part), + name = carbon_file.removesuffix(".carbon"), srcs = [ - "day{0}_common.carbon".format(day), - "day{0}_part{1}.carbon".format(day, part), + carbon_file, + carbon_file.rsplit("_", 1)[0] + "_common.carbon", ] + utils, ) - for day in range(1, 14) - for part in range(1, 3) - if (day, part) not in excluded + for carbon_file in glob(["day*_part*.carbon"]) + if carbon_file not in excluded ] diff --git a/examples/advent2024/day14_common.carbon b/examples/advent2024/day14_common.carbon new file mode 100644 index 000000000000..8bafb8e60359 --- /dev/null +++ b/examples/advent2024/day14_common.carbon @@ -0,0 +1,49 @@ +// 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/14 + +library "day14_common"; + +import Core library "io"; +import library "io_utils"; + +fn ReadXY(xy: (i32, i32)*) -> bool { + return ReadInt(ref xy->0) and ConsumeChar(',') and ReadInt(ref xy->1); +} + +fn SkipString(s: str) -> bool { + return SkipNChars(s.Size() as i32); +} + +fn Mod(a: i32, d: i32) -> i32 { + let rem: i32 = a % d; + return if rem < 0 then rem + d else rem; +} + +class Robot { + fn Read() -> Robot { + returned var me: Robot; + SkipString("p="); + ReadXY(&me.p); + SkipString(" v="); + ReadXY(&me.v); + SkipNewline(); + return var; + } + + fn Pos[self: Self](t: i32) -> (i32, i32) { + return (Mod((self.p.0 + self.v.0 * Mod(t, 101)), 101), + Mod((self.p.1 + self.v.1 * Mod(t, 103)), 103)); + } + + impl as Core.Copy { + fn Op[self: Self]() -> Self { + return {.p = self.p, .v = self.v}; + } + } + + var p: (i32, i32); + var v: (i32, i32); +} diff --git a/examples/advent2024/day14_part1.carbon b/examples/advent2024/day14_part1.carbon new file mode 100644 index 000000000000..350658210b2a --- /dev/null +++ b/examples/advent2024/day14_part1.carbon @@ -0,0 +1,21 @@ +// 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/14 + +import Core library "io"; + +import library "day14_common"; +import library "io_utils"; + +fn Run() { + var q: array(array(i32, 3), 3) = ((0,0,0), (0,0,0), (0,0,0)); + while (PeekChar() != CharOrEOF.EOF()) { + var r: Robot = Robot.Read(); + let xy: (i32, i32) = r.Pos(100); + ++q[if xy.0 < 50 then 0 else if xy.0 > 50 then 2 else 1] + [if xy.1 < 51 then 0 else if xy.1 > 51 then 2 else 1]; + } + Core.Print(q[0][0] * q[0][2] * q[2][0] * q[2][2]); +} diff --git a/examples/advent2024/day14_part2.carbon b/examples/advent2024/day14_part2.carbon new file mode 100644 index 000000000000..cc7d86ae1ff6 --- /dev/null +++ b/examples/advent2024/day14_part2.carbon @@ -0,0 +1,70 @@ +// 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/14 + +import Core library "io"; +import Core library "range"; + +import library "day14_common"; +import library "io_utils"; + +// Returns m and n so that am + bn = gcd. +fn Euclid(a: i32, b: i32) -> {.m: i32, .n: i32, .gcd: i32} { + if (a < b) { + let reverse: {.m: i32, .n: i32, .gcd: i32} = 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: i32, .n: i32, .gcd: i32} = Euclid(b, a % b); + return {.m = next.n, .n = next.m - next.n * (a / b), .gcd = next.gcd}; +} + +// Uses Chinese remainder theorem to find the least positive x that solves +// x = a mod p +// x = b mod q +// modulo pq / gcd(p,q), if one exists (if a = b mod gcd(p,q)). +fn CRT(a: i32, b: i32, p: i32, q: i32) -> i32 { + let e: {.m: i32, .n: i32, .gcd: i32} = Euclid(p, q); + let x: i32 = a * (q / e.gcd) * e.n + b * (p / e.gcd) * e.m; + return Mod(x, p * (q / e.gcd)); +} + +fn PrintState(r: array(Robot, 500), t: i32) { + var display: array(array(char, 101), 103); + + for (y: i32 in Core.Range(103)) { + for (x: i32 in Core.Range(101)) { + display[y][x] = '.'; + } + } + + for (ri: Robot in r) { + let xy: (i32, i32) = ri.Pos(t); + display[xy.1][xy.0] = '#'; + } + + for (y: i32 in Core.Range(103)) { + for (x: i32 in Core.Range(101)) { + Core.PrintChar(display[y][x]); + } + Core.PrintChar('\n'); + } +} + +fn Run() { + var r: array(Robot, 500); + for (i: i32 in Core.Range(500)) { + r[i] = Robot.Read(); + } + + let first_match_x: i32 = 28; + let first_match_y: i32 = 84; + + var t: i32 = CRT(first_match_x, first_match_y, 101, 103); + Core.Print(t); + PrintState(r, t); +} diff --git a/examples/advent2024/day15_common.carbon b/examples/advent2024/day15_common.carbon new file mode 100644 index 000000000000..f630340dd9d7 --- /dev/null +++ b/examples/advent2024/day15_common.carbon @@ -0,0 +1,134 @@ +// 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/15 + +library "day15_common"; + +import Core library "io"; +import library "io_utils"; + +// TODO: Use a choice type. +class Square { + adapt char; + + impl as Core.Copy { + fn Op[self: Self]() -> Self { + return (self as char).(Core.Copy.Op)() as Self; + } + } + impl as Core.EqWith(Square) { + fn Equal[self: Self](other: Self) -> bool { + // TODO: Use a `char` comparison when it's supported. + return (self as u8) == (other as u8); + } + fn NotEqual[self: Self](other: Self) -> bool { + // TODO: Use a `char` comparison when it's supported. + return (self as u8) != (other as u8); + } + } + + fn Make(c: CharOrEOF) -> Square { + // TODO: Use `match`. + if (c == '#') { return ('#' as char) as Square; } + if (c == 'O') { return ('O' as char) as Square; } + if (c == '.') { return ('.' as char) as Square; } + if (c == '@') { return ('@' as char) as Square; } + return ('!' as char) as Square; + } +} + +let Wall: Square = ('#' as char) as Square; +let Box: Square = ('O' as char) as Square; +let Empty: Square = ('.' as char) as Square; +let Robot: Square = ('@' as char) as Square; + +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] = Square.Make(ReadChar()); + if (me.data[x][y] == Robot) { + me.robot = (x, y); + } + ++x; + } + SkipNewline(); + ++y; + } + return var; + } + + fn Move[ref self: Self](d: (i32, i32)) { + var distance: i32 = 1; + while (self.data[self.robot.0 + distance * d.0][self.robot.1 + distance * d.1] == Box) { + ++distance; + } + if (self.data[self.robot.0 + distance * d.0][self.robot.1 + distance * d.1] == Empty) { + self.data[self.robot.0][self.robot.1] = Empty; + self.data[self.robot.0 + distance * d.0][self.robot.1 + distance * d.1] = Box; + self.data[self.robot.0 + d.0][self.robot.1 + d.1] = Robot; + self.robot.0 += d.0; + self.robot.1 += d.1; + } + } + + fn Print[self: Self]() { + var y: i32 = 0; + while (y < 50) { + var x: i32 = 0; + while (x < 50) { + Core.PrintChar(self.data[x][y] as char); + ++x; + } + Core.PrintChar('\n'); + ++y; + } + Core.PrintChar('\n'); + } + + fn Signature[self: Self]() -> i32 { + var s: i32 = 0; + var y: i32 = 0; + while (y < 50) { + var x: i32 = 0; + while (x < 50) { + if (self.data[x][y] == Box) { + s += y * 100 + x; + } + ++x; + } + ++y; + } + return s; + } + + var data: array(array(Square, 50), 50); + var robot: (i32, i32); +} + +fn ReadAndExecuteActions(ref g: Grid) { + while (true) { + var m: CharOrEOF = ReadChar(); + // To watch the progress, uncomment the following: + // g.Print(); + // Core.PrintChar(((m as i32) as u8) as char); + // Core.PrintChar('\n'); + // TODO: Use `match` once it's available. + if (m == '^') { + g.Move((0, -1)); + } else if (m == '<') { + g.Move((-1, 0)); + } else if (m == '>') { + g.Move((1, 0)); + } else if (m == 'v') { + g.Move((0, 1)); + } else if (m == CharOrEOF.EOF()) { + return; + } + } +} diff --git a/examples/advent2024/day15_part1.carbon b/examples/advent2024/day15_part1.carbon new file mode 100644 index 000000000000..44a1b0622cd6 --- /dev/null +++ b/examples/advent2024/day15_part1.carbon @@ -0,0 +1,15 @@ +// 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/15 + +import Core library "io"; + +import library "day15_common"; + +fn Run() { + var g: Grid = Grid.Read(); + ReadAndExecuteActions(ref g); + Core.Print(g.Signature()); +} diff --git a/examples/advent2024/io_utils.carbon b/examples/advent2024/io_utils.carbon index 3469e1b72530..57d91aa6b5fd 100644 --- a/examples/advent2024/io_utils.carbon +++ b/examples/advent2024/io_utils.carbon @@ -90,8 +90,24 @@ fn UnreadChar(c: CharOrEOF) { unread_char = Core.Optional(CharOrEOF).Some(c); } +fn PeekChar() -> CharOrEOF { + var next: CharOrEOF = ReadChar(); + UnreadChar(next); + return next; +} + +fn ConsumeChar(c: char) -> bool { + var next: CharOrEOF = ReadChar(); + if (next != c) { + UnreadChar(next); + return false; + } + return true; +} + fn ReadInt[N:! Core.IntLiteral()](ref p: Core.Int(N)) -> bool { var read_any_digits: bool = false; + let negative: bool = ConsumeChar('-'); // TODO: `p = 0;` crashes the toolchain, see // https://github.com/carbon-language/carbon-lang/issues/6500. p = (0 as i32) as Core.Int(N); @@ -108,6 +124,12 @@ fn ReadInt[N:! Core.IntLiteral()](ref p: Core.Int(N)) -> bool { p += (c - '0') as Core.Int(N); read_any_digits = true; } + if (negative) { + p = -p; + if (not read_any_digits) { + UnreadChar('-'); + } + } return read_any_digits; } @@ -135,21 +157,6 @@ fn PrintInt[N:! Core.IntLiteral()](n_val: Core.Int(N)) { Core.PrintChar('\n'); } -fn PeekChar() -> CharOrEOF { - var next: CharOrEOF = ReadChar(); - UnreadChar(next); - return next; -} - -fn ConsumeChar(c: char) -> bool { - var next: CharOrEOF = ReadChar(); - if (next != c) { - UnreadChar(next); - return false; - } - return true; -} - fn SkipSpaces() -> bool { var skipped_any_spaces: bool = false; while (ConsumeChar(' ')) {