Day 13 - [Care Package] 
void Main()
{
var comp = new IntCodeComputer(input.ToArray());
var board = new Dictionary<(long x, long y), int>();
while (!comp.Halted){
comp.GetNext(out var x);
comp.GetNext(out var y);
comp.GetNext(out var t);
if (!comp.Halted)
board[((long)x,(long)y)] = (int)t;
};
var part1 = board.Count(t => t.Value == 2);
part1.Dump();
comp = new IntCodeComputer(input.ToArray());
board = new Dictionary<(long x, long y), int>();
comp._mem[0] = 2;
var score = 0;
do
{
comp.GetNext(out var x);
if (x == null){
var ball = board.First(ti => ti.Value == 4).Key.x;
var paddle = board.First(ti => ti.Value == 3).Key.x;
comp.Input.Enqueue(ball.CompareTo(paddle));
continue;
}
comp.GetNext(out var y);
comp.GetNext(out var t);
if (x == -1 && y == 0)
score = (int)t;
else if (!comp.Halted)
board[((long)x, (long)y)] = (int)t;
} while (!comp.Halted);
var part2 = score;
part2.Dump();
}
class IntCodeComputer
{
public readonly Dictionary<long, long> _mem;
private long ptr = 0;
private long relativebase = 0;
public IntCodeComputer(long[] program)
{
_mem = program.Select((p, i) => new { p, i = (long)(i) }).ToDictionary(o => (long)o.i, o => (long)o.p);
}
public readonly Queue<int> Input = new Queue<int>();
public bool Halted;
public void GetNext(out long? output)
{
(int[] parmModes, int opcode) op;
(int[] parmModes, int opcode) readOpCode(int opCode) => (new[] { (opCode / 100) % 10, (opCode / 1000) % 10, (opCode / 10000) % 10 }, opCode % 100);
long readMem(long pos) => _mem.ContainsKey(pos) ? _mem[pos] : 0;
long getParmVal(int mode, long value) => mode == 0 ? readMem(value) : mode == 2 ? readMem(relativebase + value) : value;
long getParm(long parm) => getParmVal(op.parmModes[parm - 1], _mem[ptr + parm]);
void setParmVal(int mode, long val, long value) { if (mode == 2) _mem[relativebase + val] = value; else _mem[val] = value; }
void setParm(long parm, long val) => setParmVal(op.parmModes[parm - 1], _mem[ptr + parm], val);
output = default;
while (_mem[ptr] != 99)
{
op = readOpCode((int)_mem[ptr]);
switch (op.opcode)
{
case 1: setParm(3, getParm(1) + getParm(2)); ptr += 4; break;
case 2: setParm(3, getParm(1) * getParm(2)); ptr += 4; break;
case 3: if (Input.Count() == 0) return; setParm(1, Input.Dequeue()); ptr += 2; break;
case 4: output = getParm(1); ptr += 2; return; break;
case 5: ptr = getParm(1) != 0 ? getParm(2) : ptr + 3; break;
case 6: ptr = getParm(1) == 0 ? getParm(2) : ptr + 3; break;
case 7: setParm(3, getParm(1) < getParm(2) ? 1 : 0); ptr += 4; break;
case 8: setParm(3, getParm(1) == getParm(2) ? 1 : 0); ptr += 4; break;
case 9: relativebase += getParm(1); ptr += 2; break;
}
}
Halted=true;
}
}
Day 12 - [The N-Body Problem] 
var moons = input.Select(i => Regex.Match(i, @"x=(\-?\d+), y=(\-?\d+), z=(\-?\d+)")).Select(i => (x: int.Parse(i.Groups[1].Value), y: int.Parse(i.Groups[2].Value), z: int.Parse(i.Groups[3].Value))).ToArray();
var velocity = Enumerable.Range(0, 4).Select(e => (x: 0, y: 0, z: 0)).ToArray();
int energy((int x, int y, int z) e) => Math.Abs(e.x) + Math.Abs(e.y) + Math.Abs(e.z);
void ApplyVelocity()
{
for (int i = 0; i < 4; i++)
{
moons[i].x += velocity[i].x;
moons[i].y += velocity[i].y;
moons[i].z += velocity[i].z;
}
}
void ApplyGravity()
{
for (int left = 0; left < 4; left++)
{
for (int right = left + 1; right < 4; right++)
{
if (moons[left].x > moons[right].x)
{
velocity[left].x--;
velocity[right].x++;
}
else if (moons[left].x < moons[right].x)
{
velocity[left].x++;
velocity[right].x--;
}
if (moons[left].y > moons[right].y)
{
velocity[left].y--;
velocity[right].y++;
}
else if (moons[left].y < moons[right].y)
{
velocity[left].y++;
velocity[right].y--;
}
if (moons[left].z > moons[right].z)
{
velocity[left].z--;
velocity[right].z++;
}
else if (moons[left].z < moons[right].z)
{
velocity[left].z++;
velocity[right].z--;
}
}
}
}
for (int step = 0; step < 1000; step++)
{
ApplyGravity();
ApplyVelocity();
}
var part1 = Enumerable.Range(0, 4).Select(e => energy(moons[e]) * energy(velocity[e])).Sum();
part1.Dump();
(int, int, int, int, int, int, int, int) key(Func<(int x, int y, int z), int> sel) => (sel(moons[0]), sel(velocity[0]), sel(moons[1]), sel(velocity[1]), sel(moons[2]), sel(velocity[2]), sel(moons[3]), sel(velocity[3]));
long lcm(long a, long b) => Math.Abs(a * b) / gcd(a, b);
long gcd(long a, long b) => b == 0 ? a : gcd(b, a % b);
var startingx = key(a => a.x);
var startingy = key(a => a.y);
var startingz = key(a => a.z);
long counter = 0, xperiod = 0, yperiod = 0, zperiod = 0;
do
{
counter++;
ApplyGravity();
ApplyVelocity();
if (xperiod == 0 && key(a => a.x) == startingx) xperiod = counter;
if (yperiod == 0 && key(a => a.y) == startingy) yperiod = counter;
if (zperiod == 0 && key(a => a.z) == startingz) zperiod = counter;
} while (xperiod == 0 || yperiod == 0 || zperiod == 0);
var part2 = new[] { xperiod, yperiod, zperiod }.Aggregate(lcm);
part2.Dump();
Day 11 - [Space Police] 
void Main()
{
var comp = new IntCodeComputer(input.ToArray());
var robot = new Robot(0, comp);
var part1 = robot.Panels.Count();
part1.Dump();
comp = new IntCodeComputer(input.ToArray());
robot = new Robot(1, comp);
var minx = robot.Panels.Min(o => o.Key.Item1);
var miny = robot.Panels.Min(o => o.Key.Item2);
var panels = robot.Panels.ToDictionary(p => (p.Key.Item1 + Math.Abs(minx), p.Key.Item2 + Math.Abs(miny)), p => p.Value);
var drawing = Enumerable.Range(0, panels.Max(p => p.Key.Item2) + 1).Select(y =>
new string(Enumerable.Range(0, panels.Max(p => p.Key.Item1) + 1).Select(x => panels.TryGetValue((x, y), out var p) ? (p == 1 ? '8' : '_') : '_').ToArray())).ToList();
for (int i = drawing.Count - 1; i >= 0; i--)
{
drawing[i].Dump();
}
}
class Robot
{
public readonly Dictionary<(int, int), int> Panels = new Dictionary<(int, int), int>();
public Robot(int startingColor, IntCodeComputer comp)
{
int x = 0, y = 0, heading = 0;
Panels[(x, y)] = startingColor;
var done = false;
while (!done)
{
comp.Input.Enqueue(Panels.ContainsKey((x, y)) ? Panels[(x, y)] : 0);
done = comp.GetNext(out var color);
if (!done)
{
Panels[(x, y)] = (int)color;
done = comp.GetNext(out var turn);
if (!done)
{
if (turn == 0)
{
heading = (heading + 270) % 360;
}
else
{
heading = (heading + 90) % 360;
}
switch (heading)
{
case 0: y++; break;
case 90: x++; break;
case 180: y--; break;
case 270: x--; break;
}
}
}
}
}
}
class IntCodeComputer
{
private readonly Dictionary<long, long> _mem;
private long ptr = 0;
private long relativebase = 0;
public IntCodeComputer(long[] program)
{
_mem = program.Select((p, i) => new { p, i = (long)(i) }).ToDictionary(o => (long)o.i, o => (long)o.p);
}
public readonly Queue<int> Input = new Queue<int>();
public bool GetNext(out long? output)
{
(int[] parmModes, int opcode) op;
(int[] parmModes, int opcode) readOpCode(int opCode) => (new[] { (opCode / 100) % 10, (opCode / 1000) % 10, (opCode / 10000) % 10 }, opCode % 100);
long readMem(long pos) => _mem.ContainsKey(pos) ? _mem[pos] : 0;
long getParmVal(int mode, long value) => mode == 0 ? readMem(value) : mode == 2 ? readMem(relativebase + value) : value;
long getParm(long parm) => getParmVal(op.parmModes[parm - 1], _mem[ptr + parm]);
void setParmVal(int mode, long val, long value) { if (mode == 2) _mem[relativebase + val] = value; else _mem[val] = value; }
void setParm(long parm, long val) => setParmVal(op.parmModes[parm - 1], _mem[ptr + parm], val);
output = default;
while (_mem[ptr] != 99)
{
op = readOpCode((int)_mem[ptr]);
switch (op.opcode)
{
case 1: setParm(3, getParm(1) + getParm(2)); ptr += 4; break;
case 2: setParm(3, getParm(1) * getParm(2)); ptr += 4; break;
case 3: if (Input.Count() == 0) return false; setParm(1, Input.Dequeue()); ptr += 2; break;
case 4: output = getParm(1); ptr += 2; return false; break;
case 5: ptr = getParm(1) != 0 ? getParm(2) : ptr + 3; break;
case 6: ptr = getParm(1) == 0 ? getParm(2) : ptr + 3; break;
case 7: setParm(3, getParm(1) < getParm(2) ? 1 : 0); ptr += 4; break;
case 8: setParm(3, getParm(1) == getParm(2) ? 1 : 0); ptr += 4; break;
case 9: relativebase += getParm(1); ptr += 2; break;
}
}
return true;
}
}
Day 10 - [Monitoring Station] 
int gcd(int a, int b) => b == 0 ? a : gcd(b, a % b);
(int, int) reduce((int, int) i) => gcd(i.Item1, i.Item2) == 0 ? (1, 0) : (i.Item1 / gcd(i.Item1, i.Item2), i.Item2 / gcd(i.Item1, i.Item2));
(int, int) getslope((int x, int y) p1, (int x, int y) p2) => reduce((p2.x - p1.x, p2.y - p1.y));
bool isVisible((int x, int y) p1, (int x, int y) p2, HashSet<(int x, int y)> map)
{
if (p1 == p2) return false;
var slope = getslope(p1, p2);
for (int x = Math.Min(p1.x, p2.x); x <= Math.Max(p1.x, p2.x); x++)
{
for (int y = Math.Min(p1.y, p2.y); y <= Math.Max(p1.y, p2.y); y++)
{
if ((x, y) != p1 && (x, y) != p2 && slope == getslope(p1, (x, y)) && map.Contains((x,y))) return false;
}
}
return true;
}
var asteroids = Enumerable.Range(0, input.Length).SelectMany(row => Enumerable.Range(0, input[row].Length).Where(col => input[row][col] == '#').Select(col => (col, row))).ToHashSet();
var monitoringstation = asteroids.Select(asteroid => new { asteroid, count = asteroids.Count(aa => isVisible(asteroid, aa, asteroids)) }).OrderByDescending(a => a.count).First();
var part1 = monitoringstation.count;
part1.Dump();
double todegrees(double radians) => (360 + radians * (180 / Math.PI)) % 360;
double angle((int x, int y) a, (int x, int y) b) => todegrees(Math.Atan2(a.y - b.y, a.x - b.x) - Math.Atan2(1,0));
double distance((int x, int y) b, (int x, int y) a) => Math.Sqrt(Math.Pow(a.x - b.x, 2) + Math.Pow(a.y - b.y, 2));
var asteroidVectors = asteroids
.Where(a => a != monitoringstation.asteroid)
.Select(a => new { asteroid = a, vector = (angle: angle(monitoringstation.asteroid, a), dist: distance(a, monitoringstation.asteroid))})
.GroupBy(a => a.vector.angle)
.OrderBy(a => a.Key)
.Select(a => a.OrderBy(v => v.vector.dist).ToList()).ToList();
var destroyed = new List<(int x, int y)>();
while (asteroidVectors.Any(o => o.Any())){
foreach (var o in asteroidVectors)
{
if (o.Any())
{
destroyed.Add(o.First().asteroid);
o.RemoveAt(0);
}
}
}
var part2 = destroyed[199].x * 100 + destroyed[199].y;
part2.Dump();
Day 9 - [Sensor Boost] 
bool IntCodeComputer(int[] program, IEnumerable<int> inputStream, out IList<long> outputStream)
{
var mem = program.Select((p, i) => new { p, i = (long)(i) }).ToDictionary(o => (long)o.i, o => (long)o.p);
outputStream = new List<long>();
long ptr = 0;
long relativebase = 0;
var inp = inputStream.GetEnumerator();
(int[] parmModes, int opcode) op;
(int[] parmModes, int opcode) readOpCode(int opCode) => (new[] { (opCode / 100) % 10, (opCode / 1000) % 10, (opCode / 10000) % 10 }, opCode % 100);
long getParmVal(int mode, long value) => mode == 0 ? mem[value] : mode == 2 ? mem[relativebase + value] : value;
long getParm(long parm) => getParmVal(op.parmModes[parm - 1], mem[ptr + parm]);
void setParmVal(int mode, long val, long value) { if (mode == 2) mem[relativebase + val]=value; else mem[val]=value; }
void setParm(long parm, long val) => setParmVal(op.parmModes[parm-1], mem[ptr+parm], val);
while (mem[ptr] != 99)
{
op = readOpCode((int)mem[ptr]);
switch (op.opcode)
{
case 1: setParm(3, getParm(1) + getParm(2)); ptr += 4; break;
case 2: setParm(3, getParm(1) * getParm(2)); ptr += 4; break;
case 3: if (!inp.MoveNext()) return false; setParm(1, inp.Current); ptr += 2; break;
case 4: outputStream.Add(getParm(1)); ptr += 2; break;
case 5: ptr = getParm(1) != 0 ? getParm(2) : ptr + 3; break;
case 6: ptr = getParm(1) == 0 ? getParm(2) : ptr + 3; break;
case 7: setParm(3, getParm(1) < getParm(2) ? 1 : 0); ptr += 4; break;
case 8: setParm(3, getParm(1) == getParm(2) ? 1 : 0); ptr += 4; break;
case 9: relativebase += getParm(1); ptr += 2; break;
}
}
return true;
}
IntCodeComputer(input.ToArray(), new [] { 1}, out var p1);
var part1 = p1.Last();
part1.Dump();
IntCodeComputer(input.ToArray(), new [] { 2}, out var p2);
var part2 = p2.Last();
part2.Dump();
Day 8 - [Space Image Format] 
int width = 25, height = 6;
var layersize = width * height;
var chunks = Enumerable.Range(0, input.Length / layersize).Select(i => input.Substring(i * layersize, layersize));
var layers = chunks.Select(c => new { Raw = c, Counts = c.ToCharArray().GroupBy(l => l).ToDictionary(g => g.Key, g => g.Count())}).ToList();
var part1 = layers.OrderBy(l => l.Counts['0']).Select(l => l.Counts['1'] * l.Counts['2']).First();
part1.Dump();
var combined = new string(Enumerable.Range(0, layersize).Select(p => layers.First(l => l.Raw[p] != '2').Raw[p]).ToArray());
combined = combined.Replace('0', '_').Replace('1','8'); // help with image readability
var part2 = Enumerable.Range(0,height).Select(i => combined.Substring(i * width, width)).ToArray();
part2.Dump();
Day 7 - [Amplification Circuit] 
bool IntCodeComputer(int[] mem, IEnumerable<int> inputStream, out IList<int> outputStream)
{
outputStream = new List<int>();
var ptr = 0;
var inp = inputStream.GetEnumerator();
(int[] parmModes, int opcode) op;
(int[] parmModes, int opcode) readOpCode(int opCode) => (new[] { (opCode / 100) % 10, (opCode / 1000) % 10, (opCode / 10000) % 10 }, opCode % 100);
int getParmVal(int mode, int value) => mode == 0 ? mem[value] : value;
int getParm(int parm) => getParmVal(op.parmModes[parm - 1], mem[ptr + parm]);
while (mem[ptr] != 99)
{
op = readOpCode(mem[ptr]);
switch (op.opcode)
{
case 1: mem[mem[ptr + 3]] = getParm(1) + getParm(2); ptr += 4; break;
case 2: mem[mem[ptr + 3]] = getParm(1) * getParm(2); ptr += 4; break;
case 3: if (!inp.MoveNext()) return false; mem[mem[ptr + 1]] = inp.Current; ptr += 2; break;
case 4: outputStream.Add(getParm(1)); ptr += 2; break;
case 5: ptr = getParm(1) != 0 ? getParm(2) : ptr + 3; break;
case 6: ptr = getParm(1) == 0 ? getParm(2) : ptr + 3; break;
case 7: mem[mem[ptr + 3]] = getParm(1) < getParm(2) ? 1 : 0; ptr += 4; break;
case 8: mem[mem[ptr + 3]] = getParm(1) == getParm(2) ? 1 : 0; ptr += 4; break;
}
}
return true;
}
IEnumerable<IEnumerable<T>> Permutations<T>(IEnumerable<T> list, int length = 0)
{
if (length == 0) length = list.Count();
if (length == 1) return list.Select(t => new T[] { t });
return Permutations(list, length - 1)
.SelectMany(t => list.Where(e => !t.Contains(e)),
(t1, t2) => t1.Concat(new T[] { t2 }));
}
var part1 = Permutations(Enumerable.Range(0, 5)).Max(phases =>
{
var inp = 0;
foreach (var phase in phases)
{
IntCodeComputer(input.ToArray(), new List<int> { phase, inp }, out var output);
inp = output.Last();
}
return inp;
});
part1.Dump();
var part2 = Permutations(Enumerable.Range(5, 5)).Select(o => o.ToList()).Select(phases =>
{
var aIn = new List<int> { phases[0], 0 };
var bIn = new List<int> { phases[1] };
var cIn = new List<int> { phases[2] };
var dIn = new List<int> { phases[3] };
var eIn = new List<int> { phases[4] };
var done = false;
IList<int> output = null;
while (!done)
{
IntCodeComputer(input.ToArray(), aIn, out output);
bIn.Add(output.Last());
IntCodeComputer(input.ToArray(), bIn, out output);
cIn.Add(output.Last());
IntCodeComputer(input.ToArray(), cIn, out output);
dIn.Add(output.Last());
IntCodeComputer(input.ToArray(), dIn, out output);
eIn.Add(output.Last());
done = IntCodeComputer(input.ToArray(), eIn, out output);
aIn.Add(output.Last());
}
return output.Last();
}).Max();
part2.Dump();
Day 6 - [Universal Orbit Map] 
IEnumerable<string> getOrbits(Dictionary<string, string> map, string key)
{
while (key != "COM")
{
yield return map[key];
key = map[key];
}
}
var orbits = input.Select(i => i.Split(')')).ToDictionary(i => i[1], i => i[0]);
var part1 = orbits.Sum(i => getOrbits(orbits, i.Key).Count());
part1.Dump();
var you = getOrbits(orbits, "YOU").ToList();
var santa = getOrbits(orbits, "SAN").ToList();
var common = you.Intersect(santa).First();
var part2 = you.IndexOf(common) + santa.IndexOf(common);
part2.Dump();
Day 5 - [Sunny with a Chance of Asteroids] 
IEnumerable<int> IntCodeComputer(int[] mem, IEnumerable<int> inputStream)
{
var ptr = 0;
var inp = inputStream.GetEnumerator();
(int[] parmModes, int opcode) op;
(int[] parmModes, int opcode) readOpCode(int opCode) => (new[] { (opCode / 100) % 10, (opCode / 1000) % 10, (opCode / 10000) % 10 }, opCode % 100);
int getParmVal(int mode, int value) => mode == 0 ? mem[value] : value;
int getParm(int parm) => getParmVal(op.parmModes[parm - 1], mem[ptr + parm]);
while (mem[ptr] != 99)
{
op = readOpCode(mem[ptr]);
switch (op.opcode)
{
case 1: mem[mem[ptr + 3]] = getParm(1) + getParm(2); ptr += 4; break;
case 2: mem[mem[ptr + 3]] = getParm(1) * getParm(2); ptr += 4; break;
case 3: inp.MoveNext(); mem[mem[ptr + 1]] = inp.Current; ptr += 2; break;
case 4: yield return getParm(1);ptr += 2; break;
case 5: ptr = getParm(1) != 0 ? getParm(2) : ptr + 3; break;
case 6: ptr = getParm(1) == 0 ? getParm(2) : ptr + 3; break;
case 7: mem[mem[ptr + 3]] = getParm(1) < getParm(2) ? 1 : 0; ptr += 4; break;
case 8: mem[mem[ptr + 3]] = getParm(1) == getParm(2) ? 1 : 0;ptr += 4; break;
}
}
}
var part1 = IntCodeComputer(input.ToArray(), new[] { 1 }).Last();
part1.Dump();
var part2 = IntCodeComputer(input.ToArray(), new[] { 5 }).Last();
part2.Dump();
Day 4 - [Secure Container] 
bool HasDoubleDigit(string pw) => pw[0] == pw[1] || pw[1] == pw[2] || pw[2] == pw[3] || pw[3] ==pw[4] || pw[4] == pw[5];
bool IsAscending(string pw) => pw[0] <= pw[1] && pw[1] <= pw[2] && pw[2] <= pw[3] && pw[3] <= pw[4] && pw[4] <= pw[5];
bool HasNotTripledDoubleDigit(string pw) => (pw[0] == pw[1] && pw[2] != pw[1]) ||
(pw[0] != pw[1] && pw[1] == pw[2] && pw[2] != pw[3]) ||
(pw[1] != pw[2] && pw[2] == pw[3] && pw[3] != pw[4]) ||
(pw[2] != pw[3] && pw[3] == pw[4] && pw[4] != pw[5]) ||
(pw[3] != pw[4] && pw[4] == pw[5]);
int part1 = 0, part2 = 0;
for (var pw = 254032; pw <= 789860; pw++){
var pws = pw.ToString();
if (HasDoubleDigit(pws) && IsAscending(pws))
{
part1++;
if (HasNotTripledDoubleDigit(pws))
part2++;
}
}
part1.Dump();
part2.Dump();
Day 3 - [Crossed Wires] 
IEnumerable<((int X, int Y) position, int steps)> getPoints(string path)
{
var steps = path.Split(',').Select(p => (dir: p[0], dist: int.Parse(p.Substring(1))));
int x = 0, y = 0, c = 0;
foreach (var step in steps)
{
switch (step.dir)
{
case 'R': for (var i = 0; i < step.dist; i++) yield return ((x++, y), c++); break;
case 'L': for (var i = 0; i < step.dist; i++) yield return ((x--, y), c++); break;
case 'U': for (var i = 0; i < step.dist; i++) yield return ((x, y++), c++); break;
case 'D': for (var i = 0; i < step.dist; i++) yield return ((x, y--), c++); break;
}
}
}
var path1 = getPoints(input[0]);
var path2 = getPoints(input[1]);
var intersect = path1.GroupBy(p => p.position).Join(path2.GroupBy(p => p.position),
p => p.Key,
p => p.Key,
(o, i) => new { o.Key, path1 = o.Min(s => s.steps), path2 = i.Min(s => s.steps) })
.Skip(1).ToList();
var part1 = intersect.Min(p => Math.Abs(p.Key.X) + Math.Abs(p.Key.Y));
part1.Dump();
var part2 = intersect.Min(i => i.path1 + i.path2);
part2.Dump();
Day 2 - [1202 Program Alarm] 
void IntCodeComputer(int[] instructions) {
var ptr = 0;
while (instructions[ptr] != 99)
{
var regA = instructions[instructions[ptr + 1]];
var regB = instructions[instructions[ptr + 2]];
instructions[instructions[ptr + 3]] =
instructions[ptr] == 1
? regA + regB
: regA * regB;
ptr += 4;
}
}
var part1 = input.Select(i => i).ToArray();
part1[1] = 12;
part1[2] = 2;
IntCodeComputer(part1);
part1[0].Dump();
for (var noun = 0; noun < 100; noun++){
for (var verb = 0; verb < 100; verb++)
{
var part2 = input.Select(i => i).ToArray();
part2[1] = noun;
part2[2] = verb;
IntCodeComputer(part2);
if (part2[0] == 19690720)
{
(100*noun+verb).Dump();
return;
}
}
}
Day 1 - [The Tyranny of the Rocket Equation] 
var part1 = input.Select(mass => (mass/3)-2).Sum();
part1.Dump();
int additionalFuel(int mass) {
var f = (mass/3)-2;
return f > 0 ? f + additionalFuel(f) : 0;
}
var part2 = input.Select(additionalFuel).Sum();
part2.Dump();