diff --git a/examples/aoc2024/day24/.gitignore b/examples/aoc2024/day24/.gitignore new file mode 100644 index 00000000..8766ee97 --- /dev/null +++ b/examples/aoc2024/day24/.gitignore @@ -0,0 +1,2 @@ +asd.txt +asd.png diff --git a/examples/aoc2024/day24/notes.txt b/examples/aoc2024/day24/notes.txt new file mode 100644 index 00000000..c0451408 --- /dev/null +++ b/examples/aoc2024/day24/notes.txt @@ -0,0 +1,23 @@ +For all bits except the first and the last, there are 5 logic gates. + +InputXor: + Inputs: xnn, ynn (e.g. x05, y05) + Output: 1 if exactly one input is on, 0 if neither or both + +InputAnd: + Inputs: xnn, ynn (e.g. x05, y05) + Output: 1 if both inputs are on, 0 otherwise + +OutputXor (missing for first bit, output taken directly from InputXor) + Inputs: result of InputXor, result of previous OverflowXor + Output: value of znn + +OverflowAnd + Inputs: result of InputXor, overflow n-1 + Output: 1 if we overflow with inputs 1,0 or 0,1 due to carrying + 0 if we either don't overflow, or the overflow doesn't involve carrying + +OverflowOr + Inputs: result of InputAnd, result of OverflowAnd + Output: 1 if we overflow, either with input 1,1 or through OverflowAnd + 0 if we don't overflow diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou new file mode 100644 index 00000000..04fc3a8e --- /dev/null +++ b/examples/aoc2024/day24/part2.jou @@ -0,0 +1,251 @@ +# The following commands can be used to create asd.png, which contains a nice +# picture of the input as a graph: +# +# echo 'digraph G {' > asd.txt +# grep -- '->' sampleinput.txt | awk '{print $1 "->" $5 " [label="$2"]\n"$3"->"$5" [label="$2"]"}' >> asd.txt +# echo '}' >> asd.txt +# dot -Tpng -o asd.png asd.txt + + +import "stdlib/str.jou" +import "stdlib/ascii.jou" +import "stdlib/io.jou" + + +# The sum calculator consists of 45 adders and outputs 46 bits z00,z01,...,z45. +# Each adder (except the first and last) consists of the following 5 logic gates. +enum GateType: + # Inputs: xnn, ynn (e.g. x05 and y05) + # Output: 1 if inputs are 1,0 or 0,1 + # 0 if inputs are 1,1 or 0,0 + # Purpose: Used for both output and overflow checking + InputXor + + # Inputs: xnn, ynn + # Output: 1 if inputs are 1,1 + # 0 if inputs are anything else + # Purpose: Used to detect overflow + InputAnd + + # Inputs: result of InputXor, carry bit from previous level + # Output: value of znn (the output bit of x+y) + # Purpose: Calculates the sum of the two numbers + OutputXor + + # Inputs: result of InputXor, carry bit from previous level + # Output: 1 if we overflow when adding bits 1,0 or 0,1 due to carrying + # 0 if there's no overflow, or the overflow happens due to input 1,1 + # Purpose: Creates intermediate value used when calculating overflow/carry + OverflowAnd + + # Inputs: result of InputAnd, result of OverflowAnd + # Output: 1 if we overflow in any way, due to input 1,1 or carrying + # 0 if there is no overflow + # Purpose: This is the overflow/carry bit sent to the next adder + OverflowOr + + +class Gate: + inputs: byte[4][2] + output: byte[4] + level: int + gtype: GateType + + def print(self) -> None: + if self->gtype == GateType::InputXor: + printf("InputXor") + elif self->gtype == GateType::InputAnd: + printf("InputAnd") + elif self->gtype == GateType::OutputXor: + printf("OutputXor") + elif self->gtype == GateType::OverflowAnd: + printf("OverflowAnd") + elif self->gtype == GateType::OverflowOr: + printf("OverflowOr") + else: + assert False + + printf(": %s XOR %s -> %s", self->inputs[0], self->inputs[1], self->output) + if self->level != -1: + printf(" [level=%d]", self->level) + printf("\n") + + +def check_gtype_counts(gates: Gate*, ngates: int) -> None: + counts = [0, 0, 0, 0, 0] + for i = 0; i < ngates; i++: + counts[gates[i].gtype as int]++ + + assert counts[InputXor + input_xors = 0 + input_ands = 0 + output_xors = 0 + overflow_ands = 0 + overflow_ors = 0 + + +# Due to carrying, every gate affects the last output z45 somehow. +# +def sort_gates_by_distance_from_z45 + + +# Determines how badly the sum machine seems to be wired. +# Zero is a machine that is working as expected. +def calculate_score(gates: Gate*, ngates: int) -> int: + input_xors: Gate*[45] + input_ands: Gate*[45] + # First adder only has input xors (used for output) and input ands (used for overflow/carry) + output_xors: Gate*[44] + overflow_ands: Gate*[44] + overflow_ors: Gate*[44] + + counts = [0, 0, 0, 0, 0] + for i = 0; i < ngates; i++: + counts[gates[i].gtype as int]++ + + assert counts[InputXor as int] == 45 + assert counts[InputAnd as int] == 45 + assert counts[OutputXor as int] == 44 + assert counts[OverflowAnd as int] == 44 + assert counts[OverflowOr as int] == 44 + + # Just put the things to the arrays, in any order + memset(counts, 0, sizeof(counts)) + + + for i = 0; i < ngates; i++: + if gates[i].gtype == GateType::InputXor: + input_xors + + +def goes_to_1_gate( + gates: Gate*, + ngates: int, + wire: byte*, + gtype: GateType, +) -> bool: + found = False + + for i = 0; i < ngates; i++: + if strcmp(gates[i].inputs[0], wire) == 0 or strcmp(gates[i].inputs[1], wire) == 0: + if gates[i].gtype != gtype: + return False + if found: + # it goes to two gates, both of the given type + return False + found = True + + return found + + +def goes_to_2_gates( + gates: Gate*, + ngates: int, + wire: byte*, + gtype1: GateType, + gtype2: GateType, +) -> bool: + assert gtype1 != gtype2 + + found1 = False + found2 = False + + for i = 0; i < ngates; i++: + if strcmp(gates[i].inputs[0], wire) == 0 or strcmp(gates[i].inputs[1], wire) == 0: + if gates[i].gtype == gtype1: + if found1: + return False + found1 = True + elif gates[i].gtype == gtype2: + if found2: + return False + found2 = True + else: + return False + + return found1 and found2 + + +def main() -> int: + gates: Gate[500] + ngates = 0 + + f = fopen("input", "r") + assert f != NULL + + line: byte[100] + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if line[0] == '\0': + # end of initial values, start of logic gates + break + + g: Gate + op: byte[4] + while fscanf(f, "%3s %3s %3s -> %3s\n", &g.inputs[0], op, &g.inputs[1], &g.output) == 4: + # Inputs that don't come from other gates cannot be connected wrong, so + # the wiring mistakes don't mess this up. + if ( + (g.inputs[0][0] == 'x' and g.inputs[1][0] == 'y') + or (g.inputs[0][0] == 'y' and g.inputs[1][0] == 'x') + ): + # Handles input directly, so it must be InputXor or InputAnd + if strcmp(op, "XOR") == 0: + g.gtype = GateType::InputXor + elif strcmp(op, "AND") == 0: + g.gtype = GateType::InputAnd + else: + assert False + g.level = atoi(&g.inputs[0][1]) # Example: x05 --> 5 + else: + if strcmp(op, "XOR") == 0: + g.gtype = GateType::OutputXor + elif strcmp(op, "AND") == 0: + g.gtype = GateType::OverflowAnd + elif strcmp(op, "OR") == 0: + g.gtype = GateType::OverflowOr + else: + assert False + g.level = -1 # unknown + + assert ngates < sizeof(gates)/sizeof(gates[0]) + gates[ngates++] = g + + for i = 0; i < ngates; i++: + # Most InputAnd gates should connect to an OverflowOr. + if ( + gates[i].gtype == GateType::InputAnd + and gates[i].level != 0 + and not goes_to_1_gate(gates, ngates, gates[i].output, GateType::OverflowOr) + ): + printf("sus1 %s\n", gates[i].output) + + # First InputAnd produces the overflow bit directly, so it connects to + # the next OutputXor and OverflowAnd. + if ( + gates[i].gtype == GateType::InputAnd + and gates[i].level == 0 + and not goes_to_2_gates(gates, ngates, gates[i].output, GateType::OutputXor, GateType::OverflowAnd) + ): + printf("sus2 %s\n", gates[i].output) + + # Most InputXor gates should connect to an OutputXor and OverflowAnd. + if ( + gates[i].gtype == GateType::InputXor + and gates[i].level != 0 + and not goes_to_2_gates(gates, ngates, gates[i].output, GateType::OutputXor, GateType::OverflowAnd) + ): + printf("sus3 %s\n", gates[i].output) + + # First InputXor gate produces the first output directly. + if ( + gates[i].gtype == GateType::InputXor + and gates[i].level == 0 + and strcmp(gates[i].output, "z00") != 0 + ): + printf("sus4 %s\n", gates[i].output) + + + + fclose(f) + return 0