Advent of Code

My solutions for the Advent of Code puzzles. View on GitHub

Day 25 - [Let It Snow]

void Main()
{
	// To continue, please consult the code grid in the manual.  Enter the code at row 2947, column 3029.
	var x = FindSequenceNumber(2947, 3029);

	var startCode = 20151125;
	var code = (long)startCode;
	
	for(int i = 1; i < x; i++){
		code = GetNext(code);	
	}

	code.Dump();
}

long GetNext(long code) => (code*252533) % 33554393;

long FindSequenceNumber(int row, int col)
{
	var rowstart = 1;
	for (int r = 0; r < row; r++)
	{
		rowstart += r;
	}

	var cell = rowstart;
	for (int c = 1; c < col; c++)
	{
		cell += row + c;
	}

	return cell;
}

Day 24 - [It Hangs in the Balance]

void Main()
{
	var input =
		File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Util.CurrentQueryPath), "..", "day24.txt"))
		.Select(int.Parse)
		.ToList();

	var part1 = GetPackageConfiguration(input, 3);
	part1.Dump();

	var part2 = GetPackageConfiguration(input, 4);
	part2.Dump();

}

public long CalculateQuantumeEntanglement(IEnumerable<int> packages) => packages.Aggregate(1L, (x, y) => x * y);

public long GetPackageConfiguration(List<int> packages, int groups)
{
	var groupWeight = packages.Sum() / groups;
	for (var packagesInGroup = 0; packagesInGroup < packages.Count; packagesInGroup++)
	{
		var possibleGroups = FindPackageGroups(packages, packagesInGroup, groupWeight);
		if (possibleGroups.Any())
			return possibleGroups.Min(CalculateQuantumeEntanglement);
	}
	return default;
}

public IEnumerable<IImmutableList<int>> FindPackageGroups(List<int> packages, int packagesToInclude, int remainingWeight)
{
	if (remainingWeight == 0)
	{
		yield return ImmutableList.Create<int>();
		yield break;
	}

	if (packagesToInclude < 0 || remainingWeight < 0 || packages.Count == 0)
		yield break;


	if (packages[0] <= remainingWeight)
		foreach (var group in FindPackageGroups(packages.Skip(1).ToList(), packagesToInclude - 1, remainingWeight - packages[0]))
		{
			yield return group.Add(packages[0]);
		}

	foreach (var group in FindPackageGroups(packages.Skip(1).ToList(), packagesToInclude, remainingWeight))
	{
		yield return group;
	}
}

Day 23 - [Opening the Turing Lock]

void Main()
{
	var registers = new Dictionary<char,uint> { {'a', 0}, {'b', 0}};
	
	// part 1
	UseTrulyStateOfTheArtTechnologyToRunProgram(registers, input);
	registers.Dump();
	
	// part 2
	registers = new Dictionary<char,uint> { {'a', 1}, {'b', 0}};
	UseTrulyStateOfTheArtTechnologyToRunProgram(registers, input);
	registers.Dump();
}

void UseTrulyStateOfTheArtTechnologyToRunProgram(Dictionary<char,uint> registers, string[] program)
{
	var index = 0;
	do 
	{
		var i = program[index];
		switch (i.Substring(0,3))
		{
			case "hlf" : registers[i[4]] /= 2; index++; break;
			case "inc" : registers[i[4]] += 1; index++; break;
			case "tpl" : registers[i[4]] *= 3; index++; break;
			case "jmp" : index += int.Parse(i.Substring(4)); break;
			case "jie" : index += registers[i[4]] % 2 == 0 ? int.Parse(i.Substring(7)) : 1; break;
			case "jio" : index += registers[i[4]] == 1 ? int.Parse(i.Substring(7)) : 1; break;
		}
	} while (index < program.Length);	
}

Day 22 - [Wizard Simulator 20XX]

void Main()
{
	var game = new GameState
	{
		playerHitPoints = 50,
		playerMana = 500,
		bossDamage = 10,
		bossHitPoints = 71,
		spellStates = new(),
	};

	var part1 = game.PlayerPlays()
		.Where(p => p.playerHitPoints > 0)
		.OrderBy(p => p.playerManaSpent)
		.First();
	part1.playerManaSpent.Dump();

	var part2 = game.PlayerPlays(true)
		.Where(p => p.playerHitPoints > 0)
		.OrderBy(p => p.playerManaSpent)
		.First();
	part2.playerManaSpent.Dump();
}

public class GameState
{
	public int playerManaSpent;
	public int playerHitPoints;
	public int playerDamage;
	public int playerMana;
	public int playerArmor;

	public int bossHitPoints;
	public int bossDamage;

	public Dictionary<Spell, int> spellStates;
}

static class Helpers
{
	static GameState Clone(this GameState gameState)
	{
		return new GameState
		{
			playerManaSpent = gameState.playerManaSpent,
			playerHitPoints = gameState.playerHitPoints,
			playerDamage = gameState.playerDamage,
			playerMana = gameState.playerMana,
			playerArmor = gameState.playerArmor,
			bossHitPoints = gameState.bossHitPoints,
			bossDamage = gameState.bossDamage,
			spellStates = gameState.spellStates.ToDictionary(s => s.Key, s => s.Value),
		};
	}

	static void CastSpell(this GameState gameState, Spell spell)
	{
		gameState.playerMana -= spell.Cost;
		gameState.playerManaSpent += spell.Cost;
		gameState.spellStates[spell] = spell.Timer;
		spell.Cast(gameState);
	}

	static bool IsOver(this GameState gameState) =>
		gameState.bossHitPoints <= 0 || gameState.playerHitPoints <= 0;

	public static bool CanCast(this GameState state, Spell spell) =>
		state.playerMana >= spell.Cost && (!state.spellStates.TryGetValue(spell, out var s) || s == 0);

	public static IEnumerable<GameState> PlayerPlays(this GameState gameState, bool hardMode = false)
	{
		if (hardMode)
			gameState.playerHitPoints--;

		gameState.ApplySpells();

		foreach (var spell in Spells.Where(s => gameState.CanCast(s)))
		{
			var cloned = gameState.Clone();
			cloned.CastSpell(spell);
			if (cloned.IsOver())
				yield return cloned;
			else
			{
				cloned.BossPlays();
				if (cloned.IsOver())
					yield return cloned;
				else
					foreach (var s in cloned.PlayerPlays(hardMode))
						yield return s;
			}
		}
	}

	static void BossPlays(this GameState gameState)
	{
		gameState.ApplySpells();
		if (gameState.bossHitPoints > 0)
			gameState.playerHitPoints -= (gameState.bossDamage - gameState.playerArmor);
	}

	static void ApplySpells(this GameState gameState)
	{
		foreach (var spell in gameState.spellStates.Where(s => s.Value > 0).Select(s => s.Key))
		{
			gameState.spellStates[spell]--;
			spell.Assess(gameState, spell);
		}
	}
}

public class Spell
{
	public int Timer { get; set; }
	public int Cost { get; set; }
	public Action<GameState> Cast = _ => { };
	public Action<GameState, Spell> Assess = (_, _) => { };
}

public static List<Spell> Spells = new()
{
	new Spell
	{
		// Magic Missile
		Cost = 53,
		Cast = state => state.bossHitPoints -= 4
	},
	new Spell
	{
		// Drain 
		Cost = 73,
		Cast = state =>
		{
			state.bossHitPoints -= 2;
			state.playerHitPoints += 2;
		}
	},
	new Spell
	{
		// Shield
		Cost = 113,
		Timer = 6,
		Cast = state => state.playerArmor = 7,
		Assess = (state, spell) => state.playerArmor = state.spellStates[spell] == 0 ? 0 : state.playerArmor
	},
	new Spell
	{
		// Poison
		Cost = 173,
		Timer = 6,
		Assess = (state, spell) => state.bossHitPoints -= 3
	},
	new Spell
	{
		// Recharge
		Cost = 229,
		Timer = 5,
		Assess = (state, spell) => state.playerMana += 101
	},
};

Day 21 - [RPG Simulator 20XX]

void Main()
{
	var playerChoices =
		from w in Weapons
		from a in Armor.Append(new KeyValuePair<string, Stats>("None", new Stats()))
		from r1 in Rings.Append(new KeyValuePair<string, Stats>("None", new Stats()))
		from r2 in Rings.Append(new KeyValuePair<string, Stats>("None", new Stats()))
		where r1.Key != r2.Key
		select ($"{w.Key}; {a.Key}; {r1.Key}; {r2.Key}", new Stats
		{
			HitPoints = 100,
			Cost = w.Value.Cost + a.Value.Cost + r1.Value.Cost + r2.Value.Cost,
			Armor = w.Value.Armor + a.Value.Armor + r1.Value.Armor + r2.Value.Armor,
			Damage = w.Value.Damage + a.Value.Damage + r1.Value.Damage + r2.Value.Damage,
		});

	var part1 = playerChoices.Where(p => Fight(p.Item2, new Stats { HitPoints = 109, Damage = 8, Armor = 2 })).OrderBy(p => p.Item2.Cost).First().Item2.Cost;
	part1.Dump();

	var part2 = playerChoices.Where(p => !Fight(p.Item2, new Stats { HitPoints = 109, Damage = 8, Armor = 2 })).OrderByDescending(p => p.Item2.Cost).First().Item2.Cost;
	part2.Dump();
}


bool Fight(Stats player, Stats enemy)
{
	while (true)
	{
		enemy.HitPoints -= GetDamage(player, enemy);
		if (enemy.HitPoints <= 0) return true;
		player.HitPoints -= GetDamage(enemy, player);
		if (player.HitPoints <= 0) return false;
	}
}

int GetDamage(Stats attacker, Stats defender) =>
	Math.Max(1, attacker.Damage - defender.Armor);

class Stats
{
	public int Cost { get; set; }
	public int HitPoints { get; set; }
	public int Damage { get; set; }
	public int Armor { get; set; }
}

Dictionary<string, Stats> Weapons = new Dictionary<string, Stats>() {
	{ "Dagger", new() { Cost = 8, Damage = 4 }},
	{ "Shortsword", new() { Cost = 10, Damage = 5 }},
	{ "Warhammer", new() { Cost = 25, Damage = 6 }},
	{ "Longsword", new() { Cost = 40, Damage = 7 }},
	{ "Greataxe", new() { Cost = 74, Damage = 8 }},
};

Dictionary<string, Stats> Armor = new Dictionary<string, Stats>() {
	{ "Leather", new() { Cost = 13, Armor = 1 }},
	{ "Chainmail", new() { Cost = 31, Armor = 2 }},
	{ "Splintmail", new() { Cost = 53, Armor = 3 }},
	{ "Bandedmail", new() { Cost = 75, Armor = 4 }},
	{ "Platemail", new() { Cost = 102, Armor = 5 }},
};

Dictionary<string, Stats> Rings = new Dictionary<string, Stats>() {
	{ "Damage +1", new() { Cost = 25, Damage = 1 }},
	{ "Damage +2", new() { Cost = 50, Damage = 2 }},
	{ "Damage +3", new() { Cost = 100, Damage = 3 }},
	{ "Defense +1", new() { Cost = 20, Armor = 1 }},
	{ "Defense +2", new() { Cost = 40, Armor = 2 }},
	{ "Defense +3", new() { Cost = 80, Armor = 3 }},
};

Day 20 - [Infinite Elves and Infinite Houses]

void Main()
{
	var input = 29000000;

	var guess = 0;
	while(PresentsForHouse(guess) < input)
		guess += 1000 ;
	var house = guess;
	for (var i = 0; i< 50000; i++)
		if (PresentsForHouse(guess-i) >= input)
			house = guess-i;
	
	house.Dump();
	
	
	guess = 0;
	while(PresentsForHouse2(guess) < input)
		guess += 1000 ;
	house = guess;
	for (var i = 0; i< 50000; i++)
		if (PresentsForHouse2(guess-i) >= input)
			house = guess-i;
	
	house.Dump();

}

int PresentsForHouse(int house)
{
	var presents = 0;
	for(var elf = 1; elf <= house; elf++)
		if ((house % elf) == 0)
			presents += (elf * 10);
	return presents;
}

int PresentsForHouse2(int house)
{
	var presents = 0;
	for(var elf = 1; elf <= house; elf++)
		if ((house % elf) == 0 && (house / elf) <= 50)
			presents += (elf * 11);
	return presents;
}

Day 19 - [Medicine for Rudolph]

void Main()
{
	var input = 
			File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day19.txt"));
			
	var replacements = 
			input.Where(i => i.Contains("=>"))
				 .Select(i => i.Split(new[] { " => "},StringSplitOptions.None))
				 .Select(i => new ParticleReplacement { Particle = i[0], Replacement = i[1]  })
				 .ToList();
	
	var molecule = 
			input.Where(i => !i.Contains("=>") && !string.IsNullOrEmpty(i))
				 .FirstOrDefault();
			

	// part 1
	var p = new List<string>();
	foreach (Match m in Regex.Matches(molecule, "("+ string.Join("|", replacements.Select(r => r.Particle).Distinct())+")"))
	{
		var leading = molecule.Substring(0, m.Groups[1].Index);
		var trailing = molecule.Substring(m.Groups[1].Index + m.Groups[1].Length);
		foreach(var s in replacements.Where(r=>r.Particle == m.Groups[1].Value).Select(r=>r.Replacement))
			p.Add(leading + s + trailing);
	}
	p.Distinct().ToList().Count().Dump();
	
	
	// part 2
	var temp = molecule;
	var steps = 0;
	while (temp != "e")
	{
		var w = replacements.OrderByDescending (r => r.Replacement.Length).First(r => temp.Contains(r.Replacement));
		steps += Regex.Matches(temp,w.Replacement).Count;
		temp = Regex.Replace(temp,w.Replacement, w.Particle);
	}	
	steps.Dump();
	
}

public class ParticleReplacement 
{
	public string Particle  { get; set; }
	public string Replacement { get; set; }
}

Day 18 - [Like a GIF For Your Yard]

void Main()
{
	var gridOrigin = new bool [100,100];
	var input =	File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day18.txt")).ToArray();
	
	for(var x = 0; x<input.Length; x++)
		for (var y = 0; y<input[x].Length; y++)
			gridOrigin[x,y] = input[x][y] == '#';


	// part 1
	var grid = gridOrigin;
	for (var steps = 0; steps<100; steps++)
		grid=Step(grid);
	Total(grid).Dump();


	// part 2
	grid = gridOrigin;
	Corners(grid);
	for (var steps = 0; steps<100; steps++)
	{
		grid=Step(grid);
		Corners(grid);
	}
	Total(grid).Dump();
}

void Corners(bool[,] grid)
{
	grid[0,0] = true;
	grid[grid.GetLength(0)-1,0] = true;
	grid[0, grid.GetLength(1)-1] = true;
	grid[grid.GetLength(0)-1, grid.GetLength(1)-1] = true;
}

int Total(bool[,] grid)
{
	var count = 0;
	for(var x = 0; x<grid.GetLength(0); x++)
		for (var y = 0; y<grid.GetLength(1); y++)
			if (grid[x,y])
				count++;
	return count;
}

bool[,] Step(bool[,] current)
{
	var ret = new bool[current.GetLength(0),current.GetLength(1)];
	for(var x=0; x<current.GetLength(0); x++)
		for (var y=0; y<current.GetLength(1); y++)
			ret[x,y] = current[x,y] 
				? (Count(current,x,y) == 2 || Count(current,x,y) == 3)
				: Count(current,x,y) == 3;
	return ret;
}

int Count(bool[,] current, int x, int y)
{
	var maxX = current.GetLength(0)-1;
	var maxY = current.GetLength(1)-1;
	var ret=0;
	for(var xx = (x==0?0:x-1); xx<=(x==maxX?maxX:x+1);xx++)
		for(var yy = (y==0?0:y-1); yy<=(y==maxY?maxY:y+1);yy++)
			if (!(x == xx && y == yy) && current[xx,yy])
				ret++;
	return ret;
}

Day 17 - [No Such Thing as Too Much]

void Main()
{
	var containers = 
			File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day17.txt"))
				.Select (f => int.Parse(f)).OrderByDescending(f => f)
				.ToArray();
	
	var counter = 0;
	var max = 1 << containers.Length;
	var c = new List<int>();

	while (++counter < max)
	{
		var y = GetItemsTotaling(containers, counter, 150);
		if (y > 0)
			c.Add(y);
	}
	
	// part 1
	c.Count().Dump();
	
	// part 2
	c.Where(m => m == c.Min(mm => mm)).Count().Dump();
}

int GetItemsTotaling(int[] source, int bits, int total)
{
	var count = 0;
	var volume = 0;
	for (int i = source.Length - 1; i >=0 ; i--)
		if ((bits & (1 << i)) > 0)
		{
			volume += source[i];
			if (volume > total)
				break;
			count++;
		}
	return volume == total ? count : -1;
}

Day 16 - [Aunt Sue]

void Main()
{
	var auntSues = File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day16.txt")).Select (f => ParseSue(f));

	// part 1
	auntSues.Where(a => a.Children == null || a.Children == 3)
			.Where(a => a.Cats == null || a.Cats == 7)
			.Where(a => a.Samoyeds == null || a.Samoyeds == 2)
			.Where(a => a.Pomeranians == null || a.Pomeranians == 3)
			.Where(a => a.Akitas == null || a.Akitas == 0)
			.Where(a => a.Vizslas == null || a.Vizslas == 0)
			.Where(a => a.Goldfish == null || a.Goldfish == 5)
			.Where(a => a.Trees == null || a.Trees == 3)
			.Where(a => a.Cars == null || a.Cars == 2)
			.Where(a => a.Perfumes == null || a.Perfumes == 1)
			.Single().Id.Dump();
	
	// part 2
	auntSues.Where(a => a.Children == null || a.Children == 3)
			.Where(a => a.Cats == null || a.Cats > 7)
			.Where(a => a.Samoyeds == null || a.Samoyeds == 2)
			.Where(a => a.Pomeranians == null || a.Pomeranians < 3)
			.Where(a => a.Akitas == null || a.Akitas == 0)
			.Where(a => a.Vizslas == null || a.Vizslas == 0)
			.Where(a => a.Goldfish == null || a.Goldfish < 5)
			.Where(a => a.Trees == null || a.Trees > 3)
			.Where(a => a.Cars == null || a.Cars == 2)
			.Where(a => a.Perfumes == null || a.Perfumes == 1)
			.Single().Id.Dump();
}

public AuntSue ParseSue(string input)
{
	var m = Regex.Match(input, @"^Sue (\d+)\: (([a-z]+)\: (\d+)(, )?)+$");
	var sue = new AuntSue { Id = int.Parse(m.Groups[1].Value) };
	
	for( var i = 0; i < m.Groups[2].Captures.Count; i++)
	{
		switch(m.Groups[3].Captures[i].Value)
		{
			case "children": sue.Children=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "cats": sue.Cats=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "samoyeds": sue.Samoyeds=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "pomeranians": sue.Pomeranians=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "akitas": sue.Akitas=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "vizslas": sue.Vizslas=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "goldfish": sue.Goldfish=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "trees": sue.Trees=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "cars": sue.Cars=int.Parse(m.Groups[4].Captures[i].Value); break;
			case "perfumes": sue.Perfumes=int.Parse(m.Groups[4].Captures[i].Value); break;
		}
	}
	return sue;
}

public class AuntSue
{
	public int Id { get; set; }
	public int? Children { get ;set; }
	public int? Cats { get ;set; }
	public int? Samoyeds { get; set; }
	public int? Pomeranians { get ;set; }
	public int? Akitas { get ;set; }
	public int? Vizslas { get ;set; }
	public int? Goldfish { get ;set; }
	public int? Trees { get ;set; }
	public int? Cars { get; set; }
	public int? Perfumes { get; set; }
}

Day 15 - [Science for Hungry People]

void Main()
{
	var ingredients = 
			File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day15.txt"))
				.Select(s => 
				{
					var m = Regex.Match(s, @"(.+)\: capacity (-?\d+), durability (-?\d+), flavor (-?\d+), texture (-?\d+), calories (\d+)" );
					return new Ingredient
					{
						Name = m.Groups[1].Value,
						Capacity = int.Parse(m.Groups[2].Value),
						Durability = int.Parse(m.Groups[3].Value),
						Flavor = int.Parse(m.Groups[4].Value),
						Texture = int.Parse(m.Groups[5].Value),
						Calories = int.Parse(m.Groups[6].Value),
					};
				}).ToArray();

	var recipes = GetAll().Select (x => new [] { new IngredientAmount { Ingredient = ingredients[0], Amount = x[0] },
												 new IngredientAmount { Ingredient = ingredients[1], Amount = x[1] },
												 new IngredientAmount { Ingredient = ingredients[2], Amount = x[2] },
												 new IngredientAmount { Ingredient = ingredients[3], Amount = x[3] } });
	
	var scores = recipes.Select(r => ScoreCookie(r));

	
	// part 1
	scores.OrderByDescending (r => r.Item1).First().Item1.Dump();
	
	// part 2
	scores.Where(r => r.Item2 == 500).OrderByDescending (r => r.Item1).First().Item1.Dump();
}


public IEnumerable<int[]> GetAll()
{
	for (var a=0; a <= 100; a++) {
		for (var b=0; b <= 100 -a ; b++) {
			for (var c=0; c <= 100 -a -b; c++) {
				yield return new [] { a,b,c,100-a-b-c};
			}
		}
	}
}

public class IngredientAmount 
{
	public Ingredient Ingredient { get ;set; }
	public int Amount { get ;set; }
}

public class Ingredient
{
	public string Name { get ;set; }
	public int Capacity { get ;set; }
	public int Durability { get ;set; }
	public int Flavor { get ;set; }
	public int Texture { get ;set; }
	public int Calories { get ;set; }
}

public Tuple<int, int> ScoreCookie(IEnumerable<IngredientAmount> recipe)
{
	var total = new Ingredient
	{
		Capacity = recipe.Sum(r => r.Amount * r.Ingredient.Capacity),
		Durability = recipe.Sum(r => r.Amount * r.Ingredient.Durability),
		Flavor = recipe.Sum(r => r.Amount * r.Ingredient.Flavor),
		Texture = recipe.Sum(r => r.Amount * r.Ingredient.Texture),
		Calories = recipe.Sum(r => r.Amount * r.Ingredient.Calories),
	};
	
	return new Tuple<int, int>( (total.Capacity < 0 ? 0 : total.Capacity) *  (total.Durability < 0 ? 0 : total.Durability) *  (total.Flavor < 0 ? 0 : total.Flavor) *  (total.Texture < 0 ? 0 : total.Texture) , total.Calories);
}

Day 14 - [Reindeer Olympics]

void Main()
{
	var reindeer = 
			File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day14.txt"))
				.Select(s => 
				{
					var m = Regex.Match(s, @"^([^ ]+) .* (\d+) .* (\d+) .* (\d+)" );
					return new Reindeer
					{
						Name = m.Groups[1].Value,
						Rate = int.Parse(m.Groups[2].Value),
						Fly = int.Parse(m.Groups[3].Value),
						Rest = int.Parse(m.Groups[4].Value)
					};
				});

	
	// part 1
	reindeer.Select(r => FlyFor(r, 2503)).OrderByDescending(t => t).First().Dump();
	
	
	// part 2
	var scores = reindeer.ToDictionary (r => r.Name, r => 0);
	for (var t = 1; t < 2503; t++)
	{
		var leaders = reindeer.Select (r => new { r.Name, Distance = FlyFor(r, t) })
							  .GroupBy (r => r.Distance)
							  .OrderByDescending (r => r.Key)
							  .First()
							  .Select(r => r.Name);
		foreach (var l in leaders)
			scores[l]++;
	}
	scores.OrderByDescending (s => s.Value).First().Value.Dump();
}

public class Reindeer
{
	public string Name { get ;set; }
	public int Rate { get ;set; }
	public int Fly { get ;set; }
	public int Rest { get ;set; }
}

public int FlyFor(Reindeer d, int seconds)
{
	var distance = 0;
	var elapsed = 0;
	
	while (elapsed < seconds)
	{
		if (elapsed + d.Fly < seconds)
		{
			elapsed += d.Fly;
			distance += d.Rate * d.Fly;
		}
		else
		{
			distance += d.Rate * (seconds - elapsed);
			elapsed = seconds;
		}
		elapsed += d.Rest;
	}
	
	return distance;
}

Day 13 - [Knights of the Dinner Table]

void Main()
{
	var measurements = File.ReadAllLines(Path.Combine(Path.GetDirectoryName (Util.CurrentQueryPath),"..","day13.txt")).Select(s => 
	{ 
		var m = Regex.Match(s, @"([^ ]+) would (gain|lose) (\d+) .* ([^ ]+)\."); 
		return new Measurement { Person = m.Groups[1].Value, Adjacent = m.Groups[4].Value, HappinessUnits = int.Parse(m.Groups[3].Value) * (m.Groups[2].Value == "lose" ? -1 : 1) };  
	}).ToList();
	
	
	// part 1
	CalculateHappiness(measurements).Dump();
	
	
	// part 2
	measurements.AddRange(measurements.Select(m => m.Person).Distinct().SelectMany(p => new [] { new Measurement { Person = "Me", Adjacent = p, HappinessUnits = 0 }, new Measurement { Person = p, Adjacent="Me" , HappinessUnits = 0}}).ToList());
	CalculateHappiness(measurements).Dump();
}

int CalculateHappiness(List<Measurement> measurements)
{
	var possibilities = Permutations(measurements.Select(m => m.Person).Distinct().ToList()).ToList();
	var happiness = possibilities.Select(p => p.ToArray()).Select(p => 
	{
		var score = 0;
		for (var pos = 0; pos < p.Length; pos ++)
		{
			score += measurements.First (m => m.Person == p[pos] && m.Adjacent == p[(pos+1 < p.Length) ? pos + 1 : 0]).HappinessUnits;
			score += measurements.First (m => m.Person == p[pos] && m.Adjacent == p[(pos > 0) ? pos - 1 : p.Length-1]).HappinessUnits;
			
		}
		return score;
	});
	
	return happiness.OrderByDescending(s => s).First ();
}

public class Measurement
{
	public string Person { get; set; }
	public int HappinessUnits { get; set; }
	public string Adjacent { get; set; }
}

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 }));
}

Day 12 - [JSAbacusFramework.io]

void Main()
{
	// part 1
	var reader = new JsonTextReader(new StringReader(File.ReadAllText(path)));
	long count = 0;
	while (reader.Read())
	{
		if (reader.TokenType == JsonToken.Integer)
			count += (long)reader.Value;
	}
	count.Dump();
	
	
	// part 2
	var s = File.ReadAllText(path);
	reader = new JsonTextReader(new StringReader(s));
	Count(reader).Dump();
}

private long Count(JsonTextReader reader) 
{
	long ret = 0;
	bool isRed= false;
	bool isObject = reader.TokenType == JsonToken.StartObject;
	while(reader.Read())
	{
		if (reader.TokenType == JsonToken.EndObject || reader.TokenType == JsonToken.EndArray)
			return isRed ? 0 : ret;
		if (reader.TokenType == JsonToken.Integer)
			ret += (long)reader.Value;
		if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.StartArray)
			ret += Count(reader);
		if (isObject && string.Equals(reader.Value as string, "red"))
			isRed=true;
	}
	return ret;
}

Day 11 - [Corporate Policy]

void Main()
{
	var password = "hepxcrrq";
	
	password = FindNextPassword(password); 
	password.Dump();
	
	password = FindNextPassword(password); 
	password.Dump();
}

public Regex Incrementing = new Regex("(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)");
public Regex Forbidden = new Regex("[iol]");

public string FindNextPassword(string currentPassword)
{
	do
	{
		currentPassword = Increment(currentPassword);
	} while (Forbidden.IsMatch(currentPassword) || !Incrementing.IsMatch(currentPassword) || !HasRepeatingPairs(currentPassword));
	return currentPassword;
}

public bool HasRepeatingPairs(string input)
{
	var Repeats = new List<char>();
	for (var i = 0; i < input.Length - 1; i++)
	{
		if (input[i] == input[i+1])
			Repeats.Add(input[i]);
	}
	return Repeats.Distinct().Count() >= 2;
}

public string Increment(string input)
{
	var chars = input.ToCharArray();
	for (var i = 1; i <= chars.Length; i++)
	{
		if (chars[chars.Length-i] == 'z')
		{
			chars[chars.Length-i] = 'a';
			continue;
		}
		chars[chars.Length-i]++;
		break;
	}
	return new string(chars);
}

Day 10 - [Elves Look, Elves Say]

void Main()
{
	// part 1
	var value = input;
	for (var counter = 0; counter < 40; counter++)
		value = LookAndSay(value);
	value.Length.Dump();
	
	
	// part 2
	value = input;
	for (var counter = 0; counter < 50; counter++)
		value = LookAndSay(value);
	value.Length.Dump();
	
}

string LookAndSay(string i)
{
	var ret = new StringBuilder();
	int pos = 0;
	while (pos < i.Length)
	{
		var counter = 0;
		var current = i[pos];
		while ((pos+counter) < i.Length && i[pos+counter] == current)
			counter++;
		ret.AppendFormat("{0}{1}", counter, current);
		pos += counter;
	}
	return ret.ToString();
}

Day 9 - [All in a Single Night]

void Main()
{
	var possibilities = Permutations( segments.Select(s => s.PointA).Concat(segments.Select(s => s.PointB)).Distinct());
	var routes = possibilities.Select(p => Calculate(p, segments));
	
	routes.Min().Dump();
	routes.Max().Dump();
}

int Calculate(IEnumerable<string> points, List<Segment> segments)
{
	return points.Zip(points.Skip(1), (f,s) => segments.Single(se => (se.PointA == f && se.PointB == s) || (se.PointB == f && se.PointA == s) ).Distance ).Sum();
}

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 }));
}

public class Route
{
	public IEnumerable<string> Points { get ;set; }
	public IEnumerable<Segment> Legs { get ;set; }
	public int Distance { get ;set; }
}

public class Segment
{
	public string PointA { get ;set; }
	public string PointB { get ;set; }
	public int Distance { get ;set; }
}

Day 8 - [Matchsticks]

// part 1
var totalLength = strings.Sum(s => s.Length);
var unescaped =  strings.Select(s => s.Substring(1, s.Length-2))
						.Select(s => Regex.Replace(s, "\\\\\\\\", @"\"))
						.Select(s => Regex.Replace(s, "\\\\\"", "\""))
						.Select(s => Regex.Replace(s, "\\\\[x]([0-9a-f]{2})", "X"));
var memLength = unescaped.Sum(s => s.Length);
(totalLength - memLength).Dump();



// part 2
var escaped = strings.Select(u => Regex.Replace(u, "\\\\", "\\\\"))
					   .Select(u => Regex.Replace(u, "\"","\\\""));
(escaped.Sum(s => s.Length + 2) - totalLength).Dump();

Day 7 - [Some Assembly Required]

void Main()
{
	// part 1
	Resolve(instructions);
	instructions["a"].Output.Dump();

	// part 2
	var answer = instructions["a"].Output;
	foreach (var i in instructions)
		i.Value.Output = null;
	instructions["b"].Input = answer.ToString();
	Resolve(instructions);
	instructions["a"].Output.Dump();

}

public class Gate
{
	public string Input { get ;set; }
	public UInt16? Output { get; set; }
}

public ushort? Check(Dictionary<string, Gate> instructions, string input)
{
	ushort value;
	if (ushort.TryParse(input, out value))
		return value;
	else 
		if (instructions[input].Output != null)
			return  instructions[input].Output.Value;
	return null;
}

public void Resolve(Dictionary<string, Gate> instructions)
{
	while (instructions["a"].Output == null)
	{
		foreach(var i in instructions)
		{
			if (i.Value.Output != null)
				continue;
			UInt16 x;
			if (UInt16.TryParse(i.Value.Input, out x))
			{
				i.Value.Output =  x;
				continue;
			}
			var reg = Regex.Match(i.Value.Input, @"(.+) (AND|OR|LSHIFT|RSHIFT) (.+)");
			if (reg.Success)
			{
				var operand1 = reg.Groups[1].Value;
				var op = reg.Groups[2].Value;
				var operand2 = reg.Groups[3].Value;
				
				var op1 = Check(instructions, reg.Groups[1].Value);
				var op2 = Check(instructions, reg.Groups[3].Value);
				
				if (op1 == null || op2 == null)
					continue;
					
				switch(op)
				{
					case "AND": i.Value.Output = (UInt16 )(op1 & op2); break;
					case "OR" : i.Value.Output = (UInt16)(op1 | op2); break;
					case "LSHIFT": i.Value.Output = (UInt16)(op1 << op2); break;
					case "RSHIFT" : i.Value.Output = (UInt16)(op1 >> op2); break;
				}
				
				
				continue;
			}
			reg = Regex.Match(i.Value.Input, @"NOT (.*)");
			if (reg.Success) 
			{
				var op1 = Check(instructions, reg.Groups[1].Value);
				if (op1 == null) continue;
				
				i.Value.Output = (UInt16)~op1;
				continue;
			}
			if (instructions[i.Value.Input].Output == null)
				continue;
			i.Value.Output = instructions[i.Value.Input].Output.Value;
			continue;
		}
	
	}
	
}

Day 6 - [Probably a Fire Hazard]

var grid = new Boolean[1000, 1000];
var intensity = new int[1000, 1000];

foreach (var i in instructions)
{
	var coords = Regex.Match(i, @"(\d+),(\d+)");
	var tl = new Point(int.Parse(coords.Groups[1].Value), int.Parse(coords.Groups[2].Value));
	coords = coords.NextMatch();
	var br = new Point(int.Parse(coords.Groups[1].Value), int.Parse(coords.Groups[2].Value));
	
		for (var x = tl.X; x <= br.X; x++)
		{
			for (var y = tl.Y; y <= br.Y; y++)
			{
				if (i.Contains("toggle"))
				{
					grid[x, y] = !grid[x, y];
					intensity[x, y] += 2;
				}
				else
				{
					var val = i.Contains("on");
					grid[x, y] = val;
					if (val)
						intensity[x ,y]++;
					else
						if (intensity[x, y] > 0)
							intensity[x, y]--;
				}
			}
		}

}

int counter = 0;
int brightness = 0;
for (var x = 0; x < 1000; x++)
{
	for (var y = 0; y < 1000; y++)
	{
		brightness += intensity[x, y];
		if (grid[x,y])
			counter++;
	}
}

counter.Dump();
brightness.Dump();

Day 5 - [Doesn’t He Have Intern-Elves For This?]

// part 1
var regexVowels = new Regex(@"(.*[aeiou].*){3,}", RegexOptions.IgnoreCase);
var regexDoubled = new Regex(@"(.)\1", RegexOptions.IgnoreCase);
var regexExclude = new Regex(@"(ab|cd|pq|xy)", RegexOptions.IgnoreCase);

strings.Where (s => regexVowels.IsMatch(s) && regexDoubled.IsMatch(s) && !regexExclude.IsMatch(s)).Count().Dump();


// part 2
var regex1 = new Regex(@"(.{2}).*\1", RegexOptions.IgnoreCase);
var regex2 = new Regex(@"(.).\1", RegexOptions.IgnoreCase);

strings.Where (s => regex1.IsMatch(s) && regex2.IsMatch(s)).Count().Dump();;

Day 4 - [The Ideal Stocking Stuffer]

// part 1
using (MD5 md5Hash = MD5.Create())
{
	byte[] hash;
	var counter = -1;
	do{
		counter++;
		hash = md5Hash.ComputeHash(Encoding.ASCII.GetBytes(key + counter));
	} while (hash[0] != 0 || hash[1] != 0 || hash[2] > 15);
	
	counter.Dump();
}
	
	
// part 2
using (MD5 md5Hash = MD5.Create())
{
	byte[] data = keyBytes;
	byte[] hash;
	int currentLimit = -1;
	var counter = -1;
	int temp;
	int count;
	do{
		counter++;
		if (counter > currentLimit) 
		{
			Array.Resize(ref data, data.Length + 1);
			Array.Copy(keyBytes, data, keyBytes.Length);		
			currentLimit = (int)Math.Pow(10, data.Length - keyBytes.Length) - 1;
		}
		count = 0;
		temp = counter;
		while (temp > 0)
		{
			count++;
			data[data.Length - count] = (byte)(temp % 10 + 48);
			temp = temp / 10;
		}
		hash = md5Hash.ComputeHash(data);
	} while (hash[0] != 0 || hash[1] != 0 || hash[2] != 0);
	
	counter.Dump();
}

Day 3 - [Perfectly Spherical Houses in a Vacuum]

void Main()
{
	// part 1
	
	var pos = new Point(0,0);
	var deliveries = new Dictionary<Point, int>();
	deliveries.Add(pos, 1);
	
	foreach(var route in input.ToCharArray())
	{
		FollowRoute(ref pos, route);
		RecordDelivery(pos, deliveries);
	}
	deliveries.Count().Dump();  
	
	
	
	// part 2
	
	var enumerator = input.GetEnumerator();
	var santa = new Point(0,0);
	var roboSanta = new Point(0,0);
	var deliveries2 = new Dictionary<Point, int>();
	deliveries2.Add(santa,2);
	
	while(enumerator.MoveNext())
	{
		FollowRoute(ref santa, enumerator.Current);
		RecordDelivery(santa, deliveries2);
		if (enumerator.MoveNext())
		{
			FollowRoute(ref roboSanta, enumerator.Current);
			RecordDelivery(roboSanta, deliveries2);
		}
	}
	
	deliveries2.Count().Dump();
}

void FollowRoute(ref Point position, char route)
{
	switch (route)
	{
		case '^': position.Y++; break;
		case 'v': position.Y--; break;
		case '>': position.X++; break;
		case '<': position.X--; break;
	}
}

void RecordDelivery(Point position, Dictionary<Point, int> deliveries)
{
	if (deliveries.ContainsKey(position))
		deliveries[position]++;
	else
		deliveries.Add(position,1);
}

Day 2 - [I Was Told There Would Be No Math]

// part 1
var paper = boxes.Select(b => 
{
	var surfaces = new [] { b[0] * b[1], b[1] * b[2], b[2] * b[0] };
	
	return surfaces.Sum (s => 2 * s) + surfaces.Min ( );
}).Sum();
paper.Dump(); 

// part 2
var ribbon = boxes.Select (b => 
{
	var perimeter = (new [] { b[0] + b[1], b[1] + b[2], b[2] + b[0] }).Min() * 2;
	var bow = b[0] * b[1] * b[2];
	return perimeter + bow;
}).Sum();
ribbon.Dump();

Day 1 - [Not Quite Lisp]

// part 1
var x = instructions.Count (i => i == '(') - instructions.Count (i => i == ')');
x.Dump();

// part 2
var floor = 0;
for (var counter = 0; counter <  instructions.Length; counter++)
{
	var c = instructions[counter];
	if (c =='(')
		floor++;
	if (c == ')')
		floor--;
	if (floor < 0) 
	{
		(counter+1).Dump();
		break;
	}
}