diff --git a/advent-of-code-2023.csproj b/advent-of-code-2023.csproj
index 0a674b7..18907a5 100644
--- a/advent-of-code-2023.csproj
+++ b/advent-of-code-2023.csproj
@@ -70,6 +70,9 @@
+
+
+
diff --git a/inputs/20.txt b/inputs/20.txt
new file mode 100644
index 0000000..5e74bfd
--- /dev/null
+++ b/inputs/20.txt
@@ -0,0 +1,58 @@
+%hm -> lg
+%ph -> gr, cd
+%xs -> st, zf
+%xc -> vv, bn
+%fx -> lx, st
+%dn -> zc
+%nd -> lg, tc
+%pd -> dn, lg
+%pc -> gr
+%lx -> gp
+%tc -> kj
+%tl -> nn
+%kk -> xs
+%gq -> pd
+%xq -> st, xj
+%nn -> bn, xc
+&st -> kk, fx, hz, lx, zb
+&hb -> rx
+%xj -> st, vq
+%sz -> gr, ph
+%bz -> kx
+%vq -> st
+%vv -> hh, bn
+%gp -> st, hz
+&js -> hb
+%lf -> bn, qg
+broadcaster -> nd, fx, mc, lf
+%cd -> vr
+%vr -> qc, gr
+%kx -> kc, lg
+%jr -> bn, tl
+&gr -> cz, dh, mc, qc, js, nj, cd
+%qg -> hq
+%mc -> gr, cz
+%nl -> st, ch
+%hz -> nl
+%kt -> gr, jc
+%zc -> lg, qn
+%vj -> rs
+&zb -> hb
+%kc -> gq
+%qc -> kt
+&bn -> qg, hq, rs, lf, bs, vj, tl
+%cz -> dh
+&bs -> hb
+%jc -> gr, pc
+%nj -> sz
+%kj -> lg, bz
+%hh -> jv, bn
+%hq -> vj
+%dh -> nj
+%ch -> st, kk
+%jv -> bn
+%rs -> jr
+%zf -> xq, st
+%qn -> hm, lg
+&lg -> gq, bz, tc, nd, rr, kc, dn
+&rr -> hb
\ No newline at end of file
diff --git a/inputs/20a.txt b/inputs/20a.txt
new file mode 100644
index 0000000..9ed10dd
--- /dev/null
+++ b/inputs/20a.txt
@@ -0,0 +1,5 @@
+broadcaster -> a, b, c
+%a -> b
+%b -> c
+%c -> inv
+&inv -> a
\ No newline at end of file
diff --git a/inputs/20b.txt b/inputs/20b.txt
new file mode 100644
index 0000000..4da4379
--- /dev/null
+++ b/inputs/20b.txt
@@ -0,0 +1,5 @@
+broadcaster -> a
+%a -> inv, con
+&inv -> b
+%b -> con
+&con -> output
\ No newline at end of file
diff --git a/src/20.cs b/src/20.cs
new file mode 100644
index 0000000..44e9f87
--- /dev/null
+++ b/src/20.cs
@@ -0,0 +1,191 @@
+namespace aoc2023;
+
+internal class Day20 : Day
+{
+ private record command(bool state, string source, string target);
+
+ abstract class module(string n, IList o)
+ {
+ protected readonly string name = n;
+ public readonly List outputs = [..o];
+
+ public abstract void Signal(string source, bool state, Queue queue);
+ public abstract void Reset();
+ }
+
+ private class flipflop(string n, IList o) : module(n, o)
+ {
+ private bool state;
+
+ public override void Signal(string source, bool pulse, Queue queue)
+ {
+ if (pulse)
+ {
+ return;
+ }
+
+ state = !state;
+ foreach (var r in outputs)
+ {
+ queue.Enqueue(new command(state, name, r));
+ }
+ }
+
+ public override void Reset()
+ {
+ state = false;
+ }
+ }
+
+ private class conjunction(string n, IList o) : module(n, o)
+ {
+ public readonly Dictionary inputs = [];
+
+ public override void Signal(string source, bool state, Queue queue)
+ {
+ inputs[source] = state;
+ bool send = !inputs.All(r => r.Value);
+
+ foreach (var r in outputs)
+ {
+ queue.Enqueue(new command(send, name, r));
+ }
+ }
+
+ public override void Reset()
+ {
+ foreach (var i in inputs)
+ {
+ inputs[i.Key] = false;
+ }
+ }
+ }
+
+ private readonly Dictionary modules = [];
+ private readonly List broadcastReceivers = [];
+
+ internal override void Parse()
+ {
+ var lines = Util.Parsing.ReadAllLines($"{GetDay()}");
+ foreach (var line in lines)
+ {
+ var split = line.Split(" -> ");
+ if (split[0].StartsWith('%'))
+ {
+ var n = split[0][1..];
+ var receivers = split[1].Split(", ");
+ modules.Add(n, new flipflop(n, receivers));
+ }
+ else if (split[0].StartsWith('&'))
+ {
+ var n = split[0][1..];
+ var receivers = split[1].Split(", ");
+ modules.Add(n, new conjunction(n, receivers));
+ }
+ else if (split[0] == "broadcaster")
+ {
+ broadcastReceivers.AddRange(split[1].Split(", "));
+ }
+ else
+ {
+ throw new Exception();
+ }
+ }
+
+ foreach (var m in modules.Where(m => m.Value is conjunction))
+ {
+ foreach (var i in modules.Where(m2 => m2.Value.outputs.Contains(m.Key)))
+ {
+ (m.Value as conjunction)!.inputs.TryAdd(i.Key, false);
+ }
+ }
+ }
+
+ internal override string Part1()
+ {
+ modules.ForEach(m => m.Value.Reset());
+ Queue q = [];
+
+ long lows = 0;
+ long highs = 0;
+ for (int i = 0; i < 1000; i++)
+ {
+ lows++;
+ foreach (var r in broadcastReceivers)
+ {
+ q.Enqueue(new command(false, "broadcaster", r));
+ }
+
+ while (q.TryDequeue(out command? result))
+ {
+ if (result.state)
+ {
+ highs++;
+ }
+ else
+ {
+ lows++;
+ }
+
+ if (modules.TryGetValue(result.target, out module? value))
+ {
+ value.Signal(result.source, result.state, q);
+ }
+ }
+ }
+
+ return $"{lows} low signals * {highs} high signals = <+white>{lows*highs}";
+ }
+
+ internal override string Part2()
+ {
+ modules.ForEach(m => m.Value.Reset());
+ Queue q = [];
+
+ var rxin = modules.First(m => m.Value.outputs.Contains("rx"));
+ var rxinin = modules.Where(m => m.Value.outputs.Contains(rxin.Key)).ToDictionary();
+ Dictionary loops = [];
+
+ bool seenRx = false;
+ long presses = 0;
+ while (!seenRx)
+ {
+ presses++;
+ foreach (var r in broadcastReceivers)
+ {
+ q.Enqueue(new command(false, "broadcaster", r));
+ }
+
+ while (q.TryDequeue(out command? result))
+ {
+ if (result is {target: "rx", state: false})
+ {
+ seenRx = true;
+ break;
+ }
+
+ if (!modules.TryGetValue(result.target, out module? value))
+ {
+ continue;
+ }
+
+ if (!result.state && rxinin.Any(m => m.Key == result.target))
+ {
+ if (loops.TryAdd(result.target, presses))
+ {
+ if (loops.Count == rxinin.Count)
+ {
+ seenRx = true;
+ break;
+ }
+ }
+ }
+
+ value.Signal(result.source, result.state, q);
+ }
+ }
+
+ // technically this should use LCM, but they're all prime.
+ return $"rx will receive a low pulse after <+white>{loops.Aggregate(1L, (curr, m) => curr * m.Value)}<+black> button presses";
+ }
+}