123 lines
3.5 KiB
C#
123 lines
3.5 KiB
C#
|
using System;
|
||
|
using static System.Console;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using Xunit;
|
||
|
|
||
|
namespace AdventOfCode {
|
||
|
|
||
|
public class Day08 {
|
||
|
|
||
|
public class GameConsole {
|
||
|
int accumulator = 0;
|
||
|
int pointer = 0;
|
||
|
|
||
|
// Returns a (bool, int) tuple, where the bool indicates whether the program terminated
|
||
|
// normally (if false, it executed an infinite loop), and the int indicates the value of
|
||
|
// the accumulator at the time of termination.
|
||
|
public (bool, int) Execute(List<Instruction> code) {
|
||
|
var instructionsExecuted = new HashSet<int>();
|
||
|
while (pointer < code.Count()) {
|
||
|
if (instructionsExecuted.Contains(pointer)) {
|
||
|
return (false, accumulator); // loop detected
|
||
|
}
|
||
|
instructionsExecuted.Add(pointer);
|
||
|
Instruction instruction = code[pointer];
|
||
|
switch (instruction.Op) {
|
||
|
case "nop":
|
||
|
pointer++;
|
||
|
break;
|
||
|
case "acc":
|
||
|
pointer++;
|
||
|
accumulator += instruction.Value;
|
||
|
break;
|
||
|
case "jmp":
|
||
|
pointer += instruction.Value;
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception("invalid op");
|
||
|
}
|
||
|
}
|
||
|
return (true, accumulator); // normal termination
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public record Instruction(string Op, int Value);
|
||
|
|
||
|
public static int RepairBrokenInstruction(List<Instruction> code) {
|
||
|
for (int i = 0; i < code.Count(); i++) {
|
||
|
if (code[i].Op == "acc") {
|
||
|
continue; // this line can't be the broken one
|
||
|
}
|
||
|
List<Instruction> repaired = new List<Instruction>(code);
|
||
|
if (repaired[i].Op == "nop") {
|
||
|
repaired[i] = new Instruction("jmp", repaired[i].Value);
|
||
|
} else {
|
||
|
repaired[i] = new Instruction("nop", repaired[i].Value);
|
||
|
}
|
||
|
var (success, result) = new GameConsole().Execute(repaired);
|
||
|
if (success) {
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
throw new Exception("didn't find a valid instruction to repair");
|
||
|
}
|
||
|
|
||
|
static List<Instruction> ParseInstructions(string[] code) {
|
||
|
var result = new List<Instruction>();
|
||
|
foreach (string line in code) {
|
||
|
string[] tokens = line.Split(' ');
|
||
|
result.Add(new Instruction(tokens[0], int.Parse(tokens[1])));
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static int Part1() {
|
||
|
string[] input = File.ReadAllLines(Util.RootDir + "day08.txt");
|
||
|
List<Instruction> code = ParseInstructions(input);
|
||
|
(bool finished, int result) = new GameConsole().Execute(code);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static int Part2() {
|
||
|
string[] input = File.ReadAllLines(Util.RootDir + "day08.txt");
|
||
|
List<Instruction> code = ParseInstructions(input);
|
||
|
return RepairBrokenInstruction(code);
|
||
|
}
|
||
|
|
||
|
[Fact]
|
||
|
public static void Test() {
|
||
|
string[] example =
|
||
|
@"nop +0
|
||
|
acc +1
|
||
|
jmp +4
|
||
|
acc +3
|
||
|
jmp -3
|
||
|
acc -99
|
||
|
acc +1
|
||
|
jmp -4
|
||
|
acc +6".Split('\n');
|
||
|
List<Instruction> parsed = ParseInstructions(example);
|
||
|
Assert.Equal(9, parsed.Count());
|
||
|
Assert.Equal((false, 5), new GameConsole().Execute(parsed));
|
||
|
Assert.Equal(1610, Part1());
|
||
|
|
||
|
string[] example2 =
|
||
|
@"nop +0
|
||
|
acc +1
|
||
|
jmp +4
|
||
|
acc +3
|
||
|
jmp -3
|
||
|
acc -99
|
||
|
acc +1
|
||
|
nop -4
|
||
|
acc +6".Split('\n');
|
||
|
List<Instruction> parsed2 = ParseInstructions(example2);
|
||
|
Assert.Equal((true, 8), new GameConsole().Execute(parsed2));
|
||
|
Assert.Equal(1703, Part2());
|
||
|
}
|
||
|
}
|
||
|
}
|